opentimelineio-0.18.1/0000775000175000017500000000000015112014172012401 5ustar memeopentimelineio-0.18.1/README_contrib.md0000664000175000017500000000207215110656141015407 0ustar meme# OpenTimelineIO Contrib Area The contrib area was a place to host adapters and application plugins submitted by the OpenTimelineIO community. Those contributions may have involved challenging external dependencies, and may not have the same level of support as the core. The adapters previously found in the contrib area have been moved to individual repos within the [OpenTimelineIO GitHub Organization](https://github.com/OpenTimelineIO/). New contributions should be created in standalone repos, and those can later be migrated to the OTIO GitHub organization after review. ## Creating New Contrib Adapters and Plugins The best way to get started is to go to the [otio-plugin-template repo](https://github.com/OpenTimelineIO/otio-plugin-template) and click "Use this template". This will get you started with plugin boilerplate and allow you to develop the adapter in your own GitHub account. Once you've developed your plugin, please contact the [OpenTimelineIO team](https://github.com/AcademySoftwareFoundation/OpenTimelineIO) to talk about next steps for the plugin. opentimelineio-0.18.1/.clang-format0000664000175000017500000000676415110656141014777 0ustar meme--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: AlwaysBreak AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: true AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: true AllowAllArgumentsOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false AllowAllConstructorInitializersOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: InlineOnly AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false # AllowShortLambdasOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: TopLevel AlwaysBreakAfterReturnType: TopLevelDefinitions AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: true AfterControlStatement: true AfterEnum: true AfterFunction: true AfterNamespace: false AfterObjCDeclaration: true AfterStruct: true AfterUnion: true AfterExternBlock: true BeforeCatch: true BeforeElse: true IndentBraces: false SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: true ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test|_test)?$' IndentCaseLabels: true IndentPPDirectives: AfterHash IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 8 PenaltyBreakBeforeFirstCallParameter: 8 PenaltyBreakComment: 1000 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 20 PointerAlignment: Left ReflowComments: false SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: c++14 TabWidth: 4 #UseTab: ForIndentation UseTab: Never ... opentimelineio-0.18.1/CODE_OF_CONDUCT.md0000664000175000017500000000622715110656141015215 0ustar meme# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opentimelineio-tsc@aswf.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ opentimelineio-0.18.1/LICENSE.txt0000664000175000017500000002613515110656141014241 0ustar meme Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. opentimelineio-0.18.1/README.md0000664000175000017500000002160615110656141013673 0ustar memeOpenTimelineIO ======= [![OpenTimelineIO](docs/_static/OpenTimelineIO@3xDark.png)](http://opentimeline.io) ============== [![Supported VFX Platform Versions](https://img.shields.io/badge/vfx%20platform-2022--2025-lightgrey.svg)](http://www.vfxplatform.com/) ![Supported Versions](https://img.shields.io/badge/python-3.9%2C%203.10%2C%203.11%2C%203.12-blue) [![Build Status](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/actions/workflows/python-package.yml/badge.svg)](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/actions/workflows/python-package.yml) [![codecov](https://codecov.io/gh/AcademySoftwareFoundation/OpenTimelineIO/branch/main/graph/badge.svg)](https://codecov.io/gh/AcademySoftwareFoundation/OpenTimelineIO) [![docs](https://readthedocs.org/projects/opentimelineio/badge/?version=latest)](https://opentimelineio.readthedocs.io/en/latest/index.html) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2288/badge)](https://bestpractices.coreinfrastructure.org/projects/2288) Links ----- * Main web site: http://opentimeline.io/ * Documentation: https://opentimelineio.readthedocs.io/ * Wiki (more documentation): https://github.com/AcademySoftwareFoundation/OpenTimelineIO/wiki * GitHub: https://github.com/AcademySoftwareFoundation/OpenTimelineIO * [Discussion group](https://lists.aswf.io/g/otio-discussion) * [Slack channel](https://academysoftwarefdn.slack.com/messages/CMQ9J4BQC) * To join, create an account here first: https://slack.aswf.io/ * [Presentations](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/wiki/Presentations) DEVELOPMENT STATUS ------------------ OpenTimelineIO is a mature framework widely deployed across the film and television industries. It is natively supported in most non-linear editing applications, and has deep integration in game engines, digital content creation tools and adjacent industries as well. The API for OpenTimelineIO is considered stable, but is still undergoing active development, refinement and bug fixes. Work is underway to strengthen the mathematical and theoretical underpinnings of OpenTimelineIO. The current version of OpenTimelineIO may be confidently deployed in your domain. We encourage all developers and end users to provide feedback, requests, comments, and/or code contributions. Overview -------- OpenTimelineIO is an interchange format and API for editorial cut information. OTIO contains information about the order and length of cuts and references to external media. It is not however, a container format for media. For integration with applications, the core OTIO library is implemented in C++ and provides an in-memory data model, as well as library functions for interpreting, manipulating, and serializing that data model. Within the core is a dependency-less library for dealing strictly with time, `opentime`. The project also supports an official python binding, which is intended to be an idiomatic and ergonomic binding for python developers. The python binding includes a plugin system which supports a number of different types of plugins, most notably adapters, which can be used to read and write legacy formats into the OTIO data model. Documentation -------------- Documentation, including quick start, architecture, use cases, API docs, and much more, is available on [ReadTheDocs](https://opentimelineio.readthedocs.io/) Supported VFX Platforms ----------------- The current release supports: - VFX platform 2025, 2024, 2023, 2022 - Python 3.9 - 3.12 For more information on our vfxplatform support policy: [Contribution Guidelines Documentation Page](https://opentimelineio.readthedocs.io/en/latest/tutorials/contributing.html) For more information on the vfxplatform: [VFX Platform Homepage](https://vfxplatform.com) Adapter Plugins --------------- To provide interoperability with other file formats or applications lacking a native integration, the opentimelineio community has built a number of python adapter plugins. This includes Final Cut Pro XML, AAF, CMX 3600 EDL, and more. **Note: for releases after v0.16, the [OpenTimelineIO PyPI package](https://pypi.org/project/OpenTimelineIO/) will only include the core libraries and file formats. Users that need the full set of adapter plugins should use the [OpenTimelineIO-Plugins PyPI Package](https://pypi.org/project/OpenTimelineIO-Plugins/). Each OpenTimelineIO release will have a matching OpenTimelineIO-Plugins release.** For more information: https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues/1386 For more information about this, including supported formats, see: https://opentimelineio.readthedocs.io/en/latest/tutorials/adapters.html All adapters except the native `.otio`, `.otioz` and `.otiod` have been relocated to separate repositories under the OpenTimelineIO organization located here: https://github.com/OpenTimelineIO The OTIO python bindings also support several other kinds of plugins, for more information see: * [Media Linkers](https://opentimelineio.readthedocs.io/en/latest/tutorials/write-a-media-linker.html) - Generate media references to local media according to your local conventions. * [HookScripts](https://opentimelineio.readthedocs.io/en/latest/tutorials/write-a-hookscript.html) - Scripts that can run at various points during OTIO execution (_ie_ before the media linker) * [SchemaDefs](https://opentimelineio.readthedocs.io/en/latest/tutorials/write-a-schemadef.html) - Define OTIO schemas. Installing / Quick-Start ------------------------ The Python-wrapped version of OpenTimelineIO is publicly available [via PyPI](https://pypi.org/project/OpenTimelineIO/). You can install OpenTimelineIO via: `python -m pip install opentimelineio` For detailed installation instructions and notes on how to run the included viewer program, see: https://opentimelineio.readthedocs.io/en/latest/tutorials/quickstart.html Example Usage ------------- C++: ```c++ #include #include "opentimelineio/timeline.h" namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; void main() { otio::SerializableObject::Retainer tl( dynamic_cast( otio::Timeline::from_json_file("taco.otio") ) ); for (const auto& cl : tl->find_clips()) { otio::RationalTime dur = cl->duration(); std::cout << "Name: " << cl->name() << " ["; std::cout << dur.value() << "/" << dur.rate() << "]" << std::endl; } } ``` Python: ```python import opentimelineio as otio timeline = otio.adapters.read_from_file("foo.aaf") for clip in timeline.find_clips(): print(clip.name, clip.duration()) ``` There are more code examples here: https://github.com/AcademySoftwareFoundation/OpenTimelineIO/tree/main/examples Also, looking through the unit tests is a great way to see what OTIO can do: https://github.com/AcademySoftwareFoundation/OpenTimelineIO/tree/main/tests Developing ---------- If you want to contribute to the project, please see: https://opentimelineio.readthedocs.io/en/latest/tutorials/contributing.html You can get the latest development version via: `git clone git@github.com:AcademySoftwareFoundation/OpenTimelineIO.git --recursive ` You can install development dependencies with `python -m pip install .[dev]` You can also install the PySide2 dependency with `python -m pip install .[view]` You may need to escape the `[` depending on your shell, `\[view\]` . Currently the code base is written against python 3.9-3.12, in keeping with the pep8 style. We ask that before developers submit pull request, they: - run `make test` -- to ensure that none of the unit tests were broken - run `make lint` -- to ensure that coding conventions conform to pep8 - run `make coverage` -- to detect code which isn't covered PEP8: https://www.python.org/dev/peps/pep-0008/ For advanced developers, arguments can be passed to CMake through the pip commandline by using the `CMAKE_ARGS` environment variable. *nix Example: `env CMAKE_ARGS="-DCMAKE_VAR=VALUE1 -DCMAKE_VAR_2=VALUE2" pip install .` Additionally, to reproduce CI failures regarding the file manifest, run: `make manifest` locally to run the python `check-manifest` program. ## C++ Coverage Builds To enable C++ code coverage reporting via gcov/lcov for builds, set the following environment variables: - `OTIO_CXX_COVERAGE_BUILD=ON` - `OTIO_CXX_BUILD_TMP_DIR=path/to/build/dir` When building/installing through `pip`/`setup.py`, these variables must be set before running the install command (`python -m pip install .` for example). License ------- OpenTimelineIO is open source software. Please see the [LICENSE.txt](LICENSE.txt) for details. Nothing in the license file or this project grants any right to use Pixar or any other contributor’s trade names, trademarks, service marks, or product names. Contact ------- For more information, please visit http://opentimeline.io/ or https://github.com/AcademySoftwareFoundation/OpenTimelineIO or join our discussion forum: https://lists.aswf.io/g/otio-discussion opentimelineio-0.18.1/setup.cfg0000664000175000017500000000412515110656141014232 0ustar meme############################################################################### # Python Distribution ############################################################################### [metadata] description_file = README.md ############################################################################### # flake8 ############################################################################### [flake8] exclude = .tox *.egg build venv __pycache__ docs dist .git deps .venv select = E,W,F max-line-length = 88 # @TODO: for now, ignoring both line continuation before OR after binary # operators. The pep8 style seems to have adopted the W504 style as # correct, which we've been doing conventionally but not 100% uniformly. # At some point in the future, we should remove W504 from this list and # conform all the continuation to be the same. ignore = W503 W504 ############################################################################### # Check-Manifest ############################################################################### [check-manifest] ignore = tests* requirements* ignore-bad-ideas = *.egg-info *egg-info/* ############################################################################### # Python Coverage ############################################################################### [coverage:run] branch = True source = opentimelineio ./tests [coverage:paths] otio = src/py-opentimelineio/opentimelineio *site-packages/opentimelineio [coverage:report] include =* # Regexes for lines to exclude from consideration omit = *aaf2* *pkg_resources* *pbr* *mock* *PIL* *funcsigs* exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain if tests don't hit defensive assertion code: # raise AssertionError # raise NotImplementedError # # Don't complain if non-runnable code isn't run: # if 0: # if __name__ == .__main__.: ignore_errors = True show_missing = True [coverage:html] directory = coverage_html_report opentimelineio-0.18.1/Makefile0000664000175000017500000002242315110656141014052 0ustar meme.PHONY: coverage test test_first_fail clean autopep8 lint doc-html \ python-version wheel manifest lcov lcov-html lcov-reset # Special definition to handle Make from stripping newlines define newline endef # Color highlighting for the shell ccred = $(shell echo "\033[0;31m") ccgreen = $(shell echo "\033[0;32m") ccblue = $(shell echo "\033[0;34m") ccend = $(shell echo "\033[0m") # Helpful link to install development dependencies declared in setup.py define dev_deps_message $(ccred)You can install this and other development dependencies with$(newline)$(ccend)\ $(ccblue) pip install -e .[dev]$(newline)$(ccend) endef # variables MAKE_PROG ?= make # external programs COV_PROG := $(shell command -v coverage 2> /dev/null) LCOV_PROG := $(shell command -v lcov 2> /dev/null) PYCODESTYLE_PROG := $(shell command -v pycodestyle 2> /dev/null) PYFLAKES_PROG := $(shell command -v pyflakes 2> /dev/null) FLAKE8_PROG := $(shell command -v flake8 2> /dev/null) CHECK_MANIFEST_PROG := $(shell command -v check-manifest 2> /dev/null) CLANG_FORMAT_PROG := $(shell command -v clang-format 2> /dev/null) # AUTOPEP8_PROG := $(shell command -v autopep8 2> /dev/null) TEST_ARGS= GIT = git GITSTATUS := $(shell git diff-index --quiet HEAD . 1>&2 2> /dev/null; echo $$?) ifeq ($(VERBOSE), 1) TEST_ARGS:=-v endif # Clear the environment of a preset media linker OTIO_DEFAULT_MEDIA_LINKER = # run all the unit tests test: test-core test-core: python-version @echo "$(ccgreen)Running Core tests...$(ccend)" @python -m unittest discover -s tests $(TEST_ARGS) # CI ################################### ci-prebuild: manifest lint ci-postbuild: coverage ################################### python-version: @python --version coverage: coverage-core coverage-report coverage-report: @${COV_PROG} combine .coverage* @${COV_PROG} xml @${COV_PROG} report -m # NOTE: coverage configuration is done in setup.cfg coverage-core: python-version ifndef COV_PROG $(error $(newline)$(ccred) Coverage is not available please see:$(newline)$(ccend)\ $(ccblue) https://coverage.readthedocs.io/en/coverage-4.2/install.html $(newline)$(ccend)\ $(dev_deps_message)) endif @${COV_PROG} run -p -m unittest discover tests lcov: ifndef LCOV_PROG $(error $(newline)$(ccred) lcov is not available please see:$(newline)$(ccend)\ $(ccblue) https://github.com/linux-test-project/lcov/blob/master/README $(ccend)) endif ifneq (OTIO_CXX_COVERAGE_BUILD, 'ON') $(warning $(newline)Warning: unless compiled with \ OTIO_CXX_COVERAGE_BUILD="ON", C++ coverage will not work.) endif ifndef OTIO_CXX_BUILD_TMP_DIR $(error $(newline)Error: unless compiled with OTIO_CXX_BUILD_TMP_DIR, \ C++ coverage will not work, because intermediate build products will \ not be found.) endif lcov --rc lcov_branch_coverage=1 --rc no_exception_branch=1 --ignore-errors mismatch --capture -b . --directory ${OTIO_CXX_BUILD_TMP_DIR} \ --output-file=${OTIO_CXX_BUILD_TMP_DIR}/coverage.info -q cat ${OTIO_CXX_BUILD_TMP_DIR}/coverage.info | sed "s/SF:.*src/SF:src/g" \ > ${OTIO_CXX_BUILD_TMP_DIR}/coverage.filtered.info rm ${OTIO_CXX_BUILD_TMP_DIR}/coverage.info lcov --list ${OTIO_CXX_BUILD_TMP_DIR}/coverage.filtered.info lcov-html: lcov @echo -e "$(ccgreen)Generating C++ HTML coverage report...$(ccend)" genhtml --quiet --branch-coverage --output-directory lcov_html_report ${OTIO_CXX_BUILD_TMP_DIR}/coverage.filtered.info lcov-reset: @echo "$(tput setaf -Txterm 2)Resetting C++ coverage...$(tput sgr0)" lcov -b . --directory ${OTIO_CXX_BUILD_TMP_DIR} --zerocounters # run all the unit tests, stopping at the first failure test_first_fail: python-version @python -m unittest discover -s tests --failfast clean: ifdef COV_PROG @${COV_PROG} erase endif rm -vf *.whl @cd docs; ${MAKE_PROG} clean # conform all files to pep8 -- WILL CHANGE FILES IN PLACE # autopep8: # ifndef AUTOPEP8_PROG # $(error "autopep8 is not available please see: "\ # "https://pypi.python.org/pypi/autopep8#installation") # endif # find . -name "*.py" | xargs ${AUTOPEP8_PROG} --aggressive --in-place -r # run the codebase through flake8. pep8 and pyflakes are called by flake8. lint: ifndef PYCODESTYLE_PROG $(error $(newline)$(ccred)pycodestyle is not available on $$PATH please see:$(newline)$(ccend)\ $(ccblue) https://pypi.python.org/pypi/pep8#installation$(newline)$(ccend)\ $(dev_deps_message)) endif ifndef PYFLAKES_PROG $(error $(newline)$(ccred)pyflakes is not available on $$PATH please see:$(newline)$(ccend)\ $(ccblue) https://pypi.python.org/pypi/pyflakes#installation$(newline)$(ccend)\ $(dev_deps_message)) endif ifndef FLAKE8_PROG $(error $(newline)$(ccred)flake8 is not available on $$PATH please see:$(newline)$(ccend)\ $(ccblue) http://flake8.pycqa.org/en/latest/index.html#installation$(newline)$(ccend)\ $(dev_deps_message)) endif @python -m flake8 # build python wheel package for the available python version wheel: @pip wheel . --no-deps # format all .h and .cpp files using clang-format format: ifndef CLANG_FORMAT_PROG $(error $(newline)$(ccred)clang-format is not available on $$PATH$(ccend)) endif $(eval DIRS = src/opentime src/opentimelineio) $(eval DIRS += src/py-opentimelineio/opentime-opentime-bindings) $(eval DIRS += src/py-opentimelineio/opentimelineio-opentime-bindings) $(eval FILES_TO_FORMAT = $(wildcard $(addsuffix /*.h, $(DIRS)) $(addsuffix /*.cpp, $(DIRS)))) $(shell clang-format -i -style=file $(FILES_TO_FORMAT)) manifest: ifndef CHECK_MANIFEST_PROG $(error $(newline)$(ccred)check-manifest is not available on $$PATH please see:$(newline)$(ccend)\ $(ccblue) https://github.com/mgedmin/check-manifest#quick-start$(newline)$(ccend)\ $(dev_deps_message)) endif @check-manifest @echo "check-manifest succeeded" doc-model: @python src/py-opentimelineio/opentimelineio/console/autogen_serialized_datamodel.py --dryrun doc-model-update: @python src/py-opentimelineio/opentimelineio/console/autogen_serialized_datamodel.py -o docs/tutorials/otio-serialized-schema.md doc-plugins: @python src/py-opentimelineio/opentimelineio/console/autogen_plugin_documentation.py --dryrun doc-plugins-update: @python src/py-opentimelineio/opentimelineio/console/autogen_plugin_documentation.py -o docs/tutorials/otio-plugins.md --public-only --sanitized-paths # build the CORE_VERSION_MAP cpp file version-map: @python src/py-opentimelineio/opentimelineio/console/autogen_version_map.py -i src/opentimelineio/CORE_VERSION_MAP.last.cpp --dryrun version-map-update: @echo "updating the CORE_VERSION_MAP..." @python src/py-opentimelineio/opentimelineio/console/autogen_version_map.py -i src/opentimelineio/CORE_VERSION_MAP.last.cpp -o src/opentimelineio/CORE_VERSION_MAP.cpp # generate documentation in html doc-html: @# if you just want to build the docs yourself outside of RTD @cd docs; ${MAKE_PROG} html doc-cpp: @cd doxygen ; doxygen config/dox_config ; cd .. @echo "wrote doxygen output to: doxygen/output/html/index.html" # release related targets confirm-release-intent: ifndef OTIO_DO_RELEASE $(error \ "If you are sure you want to perform a release, set OTIO_DO_RELEASE=1") endif @echo "Starting release process..." check-git-status: ifneq ($(GITSTATUS), 0) $(error \ "Git repository is dirty, cannot create release. Run 'git status' \ for more info") endif @echo "Git status is clean, ready to proceed with release." verify-license: @echo "Verifying licenses in files..." @python maintainers/verify_license.py -s . fix-license: @python maintainers/verify_license.py -s . -f freeze-ci-versions: @echo "freezing CI versions..." @python maintainers/freeze_ci_versions.py -f unfreeze-ci-versions: @echo "unfreezing CI versions..." @python maintainers/freeze_ci_versions.py -u # needs to happen _before_ version-map-update so that version in # CORE_VERSION_MAP does not have the .dev1 suffix at release time remove-dev-suffix: @echo "Removing .dev1 suffix" @python maintainers/remove_dev_suffix.py -r check-github-token: ifndef OTIO_RELEASE_GITHUB_TOKEN $(error \ OTIO_RELEASE_GITHUB_TOKEN is not set, unable to update contributors) endif update-contributors: check-github-token @echo "Updating CONTRIBUTORS.md..." @python maintainers/fetch_contributors.py \ --repo AcademySoftwareFoundation/OpenTimelineIO \ --token "${OTIO_RELEASE_GITHUB_TOKEN}" dev-python-install: @python setup.py install # make target for preparing a release candidate release: \ confirm-release-intent \ check-git-status \ check-github-token \ verify-license \ freeze-ci-versions \ remove-dev-suffix \ format \ dev-python-install \ version-map-update \ test-core \ update-contributors @echo "Release is ready. Commit, push and open a PR!" # targets for creating a new version (after making a release, to start the next # development cycle) bump-otio-minor-version: @python maintainers/bump_version_number.py -i minor shuffle-core-version-map: @cp -f src/opentimelineio/CORE_VERSION_MAP.cpp \ src/opentimelineio/CORE_VERSION_MAP.last.cpp @echo "set the current version map as the next one" add-dev-suffix: @echo "Adding .dev1 suffix" @python maintainers/remove_dev_suffix.py -a # make target for starting a new version (after a release is completed) start-dev-new-minor-version: \ check-git-status \ unfreeze-ci-versions \ bump-otio-minor-version \ shuffle-core-version-map \ add-dev-suffix \ dev-python-install \ version-map-update \ test-core @echo "New version made. Commit, push and open a PR!" opentimelineio-0.18.1/tests/0000775000175000017500000000000015110656141013551 5ustar memeopentimelineio-0.18.1/tests/test_plugin_detection.py0000664000175000017500000001653115110656141020524 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import os from pathlib import Path import sys from unittest import mock from importlib import reload as import_reload import importlib.metadata as metadata import opentimelineio as otio from tests import baseline_reader class TestSetuptoolsPlugin(unittest.TestCase): def setUp(self): # Get the location of the mock plugin module metadata mock_module_path = os.path.join( os.path.normpath(baseline_reader.path_to_baseline_directory()), 'plugin_module', ) self.mock_module_manifest_path = Path( mock_module_path, "otio_jsonplugin", "plugin_manifest.json" ).absolute().as_posix() self.override_adapter_manifest_path = Path( mock_module_path, "otio_override_adapter", "plugin_manifest.json" ).absolute().as_posix() # Create a WorkingSet as if the module were installed entries = [mock_module_path] + sys.path self.original_sysmodule_keys = set(sys.modules.keys()) self.sys_patch = mock.patch('sys.path', entries) self.sys_patch.start() def tearDown(self): self.sys_patch.stop() # Remove any modules added under test. We cannot replace sys.modules with # a copy from setUp. For more, see: https://bugs.python.org/msg188914 for key in set(sys.modules.keys()) ^ self.original_sysmodule_keys: sys.modules.pop(key) def test_detect_plugin(self): """This manifest uses the plugin_manifest function""" # Create a manifest and ensure it detected the mock adapter and linker man = otio.plugins.manifest.load_manifest() # Make sure the adapter is included in the adapter list adapter_names = [adapter.name for adapter in man.adapters] self.assertIn('mock_adapter', adapter_names) # Make sure the linker is included in the linker list linker_names = [linker.name for linker in man.media_linkers] self.assertIn('mock_linker', linker_names) # Make sure adapters and linkers landed in the proper place for adapter in man.adapters: self.assertIsInstance(adapter, otio.adapters.Adapter) for linker in man.media_linkers: self.assertIsInstance(linker, otio.media_linker.MediaLinker) def test_override_adapter(self): # Test that entrypoint plugins load before builtin man = otio.plugins.manifest.load_manifest() # The override_adapter creates another otiod adapter adapters = [adapter for adapter in man.adapters if adapter.name == "otiod"] # More then one otiod adapter should exist. self.assertTrue(len(adapters) > 1) # Override adapter should be the first adapter found manifest = adapters[0].plugin_info_map().get('from manifest', None) self.assertEqual(manifest, self.override_adapter_manifest_path) self.assertTrue( any( True for p in man.source_files if self.override_adapter_manifest_path in p ) ) def test_entrypoints_disabled(self): os.environ["OTIO_DISABLE_ENTRYPOINTS_PLUGINS"] = "1" import_reload(otio.plugins.manifest) # detection of the environment variable happens on import, force a # reload to ensure that it is triggered with self.assertRaises(AssertionError): self.test_detect_plugin() # override adapter should not be loaded either with self.assertRaises(AssertionError): self.test_override_adapter() # remove the environment variable and reload again for usage in the # other tests del os.environ["OTIO_DISABLE_ENTRYPOINTS_PLUGINS"] import_reload(otio.plugins.manifest) def test_detect_plugin_json_manifest(self): # Test detecting a plugin that rather than exposing the plugin_manifest # function, just simply has a plugin_manifest.json provided at the # package top level. man = otio.plugins.manifest.load_manifest() # Make sure the adapter is included in the adapter list adapter_names = [adapter.name for adapter in man.adapters] self.assertIn('mock_adapter_json', adapter_names) # Make sure the linker is included in the linker list linker_names = [linker.name for linker in man.media_linkers] self.assertIn('mock_linker_json', linker_names) # Make sure adapters and linkers landed in the proper place for adapter in man.adapters: self.assertIsInstance(adapter, otio.adapters.Adapter) for linker in man.media_linkers: self.assertIsInstance(linker, otio.media_linker.MediaLinker) self.assertTrue( any( True for p in man.source_files if self.mock_module_manifest_path in p ) ) def test_deduplicate_env_variable_paths(self): "Ensure that duplicate entries in the environment variable are ignored" # back up existing manifest bak_env = os.environ.get('OTIO_PLUGIN_MANIFEST_PATH') relative_path = self.mock_module_manifest_path.replace(os.getcwd(), '.') # set where to find the new manifest os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = os.pathsep.join( ( # absolute self.mock_module_manifest_path, # relative relative_path ) ) result = otio.plugins.manifest.load_manifest() self.assertEqual( len( [ p for p in result.source_files if self.mock_module_manifest_path in p ] ), 1 ) if relative_path != self.mock_module_manifest_path: self.assertNotIn(relative_path, result.source_files) if bak_env: os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = bak_env else: del os.environ['OTIO_PLUGIN_MANIFEST_PATH'] def test_plugin_load_failure(self): """When a plugin fails to load, ensure the exception message is logged (and no exception thrown) """ sys.modules['otio_mock_bad_module'] = mock.Mock( name='otio_mock_bad_module', plugin_manifest=mock.Mock( side_effect=Exception("Mock Exception") ) ) entry_points = mock.patch( 'opentimelineio.plugins.manifest.metadata.entry_points', return_value=[ metadata.EntryPoint( 'mock_bad_module', 'otio_mock_bad_module', 'opentimelineio.plugins' ) ] ) with self.assertLogs() as cm, entry_points: # Load the above mock entrypoint, expect it to fail and log otio.plugins.manifest.load_manifest() load_errors = [ r for r in cm.records if r.message.startswith( "could not load plugin: mock_bad_module. " "Exception is: Mock Exception" ) ] self.assertEqual(len(load_errors), 1) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_image_sequence_reference.py0000664000175000017500000005573715110656141022173 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test harness for Image Sequence References.""" import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class ImageSequenceReferenceTests( unittest.TestCase, otio_test_utils.OTIOAssertions ): def test_create(self): frame_policy = otio.schema.ImageSequenceReference.MissingFramePolicy.hold ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=5, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 30), otio.opentime.RationalTime(60, 30), ), frame_step=3, missing_frame_policy=frame_policy, rate=30, metadata={"custom": {"foo": "bar"}}, ) # Check Values self.assertEqual(ref.target_url_base, "file:///show/seq/shot/rndr/") self.assertEqual(ref.name_prefix, "show_shot.") self.assertEqual(ref.name_suffix, ".exr") self.assertEqual(ref.frame_zero_padding, 5) self.assertEqual( ref.available_range, otio.opentime.TimeRange( otio.opentime.RationalTime(0, 30), otio.opentime.RationalTime(60, 30), ) ) self.assertEqual(ref.frame_step, 3) self.assertEqual(ref.rate, 30) self.assertEqual(ref.metadata, {"custom": {"foo": "bar"}}) self.assertEqual( ref.missing_frame_policy, otio.schema.ImageSequenceReference.MissingFramePolicy.hold, ) def test_str(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", start_frame=1, frame_step=3, rate=30, frame_zero_padding=5, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 30), otio.opentime.RationalTime(60, 30), ), metadata={"custom": {"foo": "bar"}}, available_image_bounds=otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(16.0, 9.0) ), ) self.assertEqual( str(ref), 'ImageSequenceReference(' '"file:///show/seq/shot/rndr/", ' '"show_shot.", ' '".exr", ' '1, ' '3, ' '30.0, ' '5, ' 'MissingFramePolicy.error, ' 'TimeRange(RationalTime(0, 30), RationalTime(60, 30)), ' 'Box2d(V2d(0.0, 0.0), V2d(16.0, 9.0)), ' "{'custom': {'foo': 'bar'}}" ')' ) def test_repr(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", start_frame=1, frame_step=3, rate=30, frame_zero_padding=5, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 30), otio.opentime.RationalTime(60, 30), ), available_image_bounds=otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(16.0, 9.0) ), metadata={"custom": {"foo": "bar"}}, ) ref_value = ( 'ImageSequenceReference(' "target_url_base='file:///show/seq/shot/rndr/', " "name_prefix='show_shot.', " "name_suffix='.exr', " 'start_frame=1, ' 'frame_step=3, ' 'rate=30.0, ' 'frame_zero_padding=5, ' 'missing_frame_policy=, ' 'available_range={}, ' 'available_image_bounds=otio.schema.Box2d(' 'min=otio.schema.V2d(x=0.0, y=0.0), ' 'max=otio.schema.V2d(x=16.0, y=9.0)), ' "metadata={{'custom': {{'foo': 'bar'}}}}" ')'.format(repr(ref.available_range)) ) self.assertEqual(repr(ref), ref_value) def test_serialize_roundtrip(self): frame_policy = otio.schema.ImageSequenceReference.MissingFramePolicy.hold ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=5, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 30), otio.opentime.RationalTime(60, 30), ), frame_step=3, rate=30, missing_frame_policy=frame_policy, metadata={"custom": {"foo": "bar"}}, ) encoded = otio.adapters.otio_json.write_to_string(ref) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(ref, decoded) encoded2 = otio.adapters.otio_json.write_to_string(decoded) self.assertEqual(encoded, encoded2) # Check Values self.assertEqual( decoded.target_url_base, "file:///show/seq/shot/rndr/" ) self.assertEqual(decoded.name_prefix, "show_shot.") self.assertEqual(decoded.name_suffix, ".exr") self.assertEqual(decoded.frame_zero_padding, 5) self.assertEqual( decoded.available_range, otio.opentime.TimeRange( otio.opentime.RationalTime(0, 30), otio.opentime.RationalTime(60, 30), ) ) self.assertEqual(decoded.frame_step, 3) self.assertEqual(decoded.rate, 30) self.assertEqual( decoded.missing_frame_policy, otio.schema.ImageSequenceReference.MissingFramePolicy.hold ) self.assertEqual(decoded.metadata, {"custom": {"foo": "bar"}}) def test_deserialize_invalid_enum_value(self): encoded = """{ "OTIO_SCHEMA": "ImageSequenceReference.1", "metadata": { "custom": { "foo": "bar" } }, "name": "", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 60.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "target_url_base": "file:///show/seq/shot/rndr/", "name_prefix": "show_shot.", "name_suffix": ".exr", "start_frame": 1, "frame_step": 3, "rate": 30.0, "frame_zero_padding": 5, "missing_frame_policy": "BOGUS" }""" with self.assertRaises(ValueError): otio.adapters.otio_json.read_from_string(encoded) def test_number_of_images_in_sequence(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(48, 24), ), rate=24, ) self.assertEqual(ref.number_of_images_in_sequence(), 48) def test_number_of_images_in_sequence_with_skip(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(48, 24), ), frame_step=2, rate=24, ) self.assertEqual(ref.number_of_images_in_sequence(), 24) ref.frame_step = 3 self.assertEqual(ref.number_of_images_in_sequence(), 16) def test_target_url_for_image_number(self): all_images_urls = [ f"file:///show/seq/shot/rndr/show_shot.{i:04}.exr" for i in range(1, 49) ] ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=1, rate=24, ) generated_urls = [ ref.target_url_for_image_number(i) for i in range(ref.number_of_images_in_sequence()) ] self.assertEqual(all_images_urls, generated_urls) def test_target_url_for_image_number_steps(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=2, rate=24, ) all_images_urls = [ f"file:///show/seq/shot/rndr/show_shot.{i:04}.exr" for i in range(1, 49, 2) ] generated_urls = [ ref.target_url_for_image_number(i) for i in range(ref.number_of_images_in_sequence()) ] self.assertEqual(all_images_urls, generated_urls) ref.frame_step = 3 all_images_urls_threes = [ f"file:///show/seq/shot/rndr/show_shot.{i:04}.exr" for i in range(1, 49, 3) ] generated_urls_threes = [ ref.target_url_for_image_number(i) for i in range(ref.number_of_images_in_sequence()) ] self.assertEqual(all_images_urls_threes, generated_urls_threes) ref.frame_step = 2 ref.start_frame = 0 all_images_urls_zero_first = [ f"file:///show/seq/shot/rndr/show_shot.{i:04}.exr" for i in range(0, 48, 2) ] generated_urls_zero_first = [ ref.target_url_for_image_number(i) for i in range(ref.number_of_images_in_sequence()) ] self.assertEqual(all_images_urls_zero_first, generated_urls_zero_first) def test_target_url_for_image_number_with_missing_slash(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=1, rate=24, ) self.assertEqual( ref.target_url_for_image_number(0), "file:///show/seq/shot/rndr/show_shot.0001.exr" ) def test_abstract_target_url(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=1, rate=24, ) self.assertEqual( ref.abstract_target_url("@@@@"), "file:///show/seq/shot/rndr/show_shot.@@@@.exr" ) def test_abstract_target_url_with_missing_slash(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=1, rate=24, ) self.assertEqual( ref.abstract_target_url("@@@@"), "file:///show/seq/shot/rndr/show_shot.@@@@.exr" ) def test_presentation_time_for_image_number(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=2, rate=24, ) reference_values = [ otio.opentime.RationalTime(i * 2, 24) for i in range(24) ] generated_values = [ ref.presentation_time_for_image_number(i) for i in range(ref.number_of_images_in_sequence()) ] self.assertEqual(generated_values, reference_values) def test_presentation_time_for_image_number_with_offset(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=2, rate=24, ) first_frame_time = otio.opentime.RationalTime(12, 24) reference_values = [ first_frame_time + otio.opentime.RationalTime(i * 2, 24) for i in range(24) ] generated_values = [ ref.presentation_time_for_image_number(i) for i in range(ref.number_of_images_in_sequence()) ] self.assertEqual(generated_values, reference_values) def test_end_frame(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=1, rate=24, ) self.assertEqual(ref.end_frame(), 48) # Frame step should not affect this ref.frame_step = 2 self.assertEqual(ref.end_frame(), 48) def test_end_frame_with_offset(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(48, 24), ), start_frame=101, frame_step=1, rate=24, ) self.assertEqual(ref.end_frame(), 148) # Frame step should not affect this ref.frame_step = 2 self.assertEqual(ref.end_frame(), 148) def test_frame_for_time(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(48, 24), ), start_frame=1, frame_step=1, rate=24, ) # The start time should be frame 1 self.assertEqual( ref.frame_for_time(ref.available_range.start_time), 1 ) # Test a sample in the middle self.assertEqual( ref.frame_for_time(otio.opentime.RationalTime(15, 24)), 4 ) # The end time (inclusive) should map to the last frame number self.assertEqual( ref.frame_for_time(ref.available_range.end_time_inclusive()), 48 ) # make sure frame step and RationalTime rate have no effect ref.frame_step = 2 self.assertEqual( ref.frame_for_time(otio.opentime.RationalTime(118, 48)), 48 ) def test_frame_for_time_out_of_range(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(12, 30), otio.opentime.RationalTime(60, 30), ), start_frame=1, frame_step=1, rate=30, ) with self.assertRaises(ValueError): ref.frame_for_time(otio.opentime.RationalTime(73, 30)) def test_frame_range_for_time_range(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(60, 24), ), start_frame=1, frame_step=1, rate=24, ) time_range = otio.opentime.TimeRange( otio.opentime.RationalTime(24, 24), otio.opentime.RationalTime(17, 24), ) self.assertEqual(ref.frame_range_for_time_range(time_range), (13, 29)) def test_frame_range_for_time_range_out_of_available_image_bounds(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(60, 24), ), start_frame=1, frame_step=1, rate=24, ) time_range = otio.opentime.TimeRange( otio.opentime.RationalTime(24, 24), otio.opentime.RationalTime(60, 24), ) with self.assertRaises(ValueError): ref.frame_range_for_time_range(time_range) def test_negative_frame_numbers(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(48, 24), ), start_frame=-1, frame_step=2, rate=24, ) self.assertEqual(ref.number_of_images_in_sequence(), 24) self.assertEqual( ref.presentation_time_for_image_number(0), otio.opentime.RationalTime(12, 24), ) self.assertEqual( ref.presentation_time_for_image_number(1), otio.opentime.RationalTime(14, 24), ) self.assertEqual( ref.presentation_time_for_image_number(2), otio.opentime.RationalTime(16, 24), ) self.assertEqual( ref.presentation_time_for_image_number(23), otio.opentime.RationalTime(58, 24), ) self.assertEqual( ref.target_url_for_image_number(0), "file:///show/seq/shot/rndr/show_shot.-0001.exr", ) self.assertEqual( ref.target_url_for_image_number(1), "file:///show/seq/shot/rndr/show_shot.0001.exr", ) self.assertEqual( ref.target_url_for_image_number(2), "file:///show/seq/shot/rndr/show_shot.0003.exr", ) self.assertEqual( ref.target_url_for_image_number(17), "file:///show/seq/shot/rndr/show_shot.0033.exr", ) self.assertEqual( ref.target_url_for_image_number(23), "file:///show/seq/shot/rndr/show_shot.0045.exr", ) # Check values by ones ref.frame_step = 1 for i in range(1, ref.number_of_images_in_sequence()): self.assertEqual( ref.target_url_for_image_number(i), f"file:///show/seq/shot/rndr/show_shot.{i - 1:04}.exr", ) def test_target_url_for_image_number_with_missing_timing_info(self): ref = otio.schema.ImageSequenceReference( "file:///show/seq/shot/rndr/", "show_shot.", ".exr", frame_zero_padding=4, start_frame=1, frame_step=1, rate=24, ) # Make sure the right error and a useful message raised when # source_range is either un-set or zero duration. with self.assertRaises(IndexError) as exception_manager: ref.target_url_for_image_number(0) self.assertEqual( str(exception_manager.exception), "Zero duration sequences has no frames.", ) ref.available_range = otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(0, 1), ) with self.assertRaises(IndexError) as exception_manager: ref.target_url_for_image_number(0) self.assertEqual( str(exception_manager.exception), "Zero duration sequences has no frames.", ) # Set the duration and make sure a similarly useful message comes # when rate is un-set. ref.available_range = otio.opentime.TimeRange( otio.opentime.RationalTime(12, 24), otio.opentime.RationalTime(48, 24), ) ref.rate = 0 with self.assertRaises(IndexError) as exception_manager: ref.target_url_for_image_number(0) self.assertEqual( str(exception_manager.exception), "Zero rate sequence has no frames.", ) def test_clone(self): """ ensure that deeopcopy/clone function """ isr = otio.schema.ImageSequenceReference() try: import copy cln = copy.deepcopy(isr) cln = isr.clone() except ValueError as exc: self.fail(f"Cloning raised an exception: {exc}") self.assertJsonEqual(isr, cln) def test_target_url_for_image_number_with_blank_target_url_base(self): ref = otio.schema.ImageSequenceReference( name_prefix="myfilename.", name_suffix=".exr", start_frame=101, rate=24, frame_zero_padding=4, available_range=otio.opentime.TimeRange( otio.opentime.from_timecode("01:25:30:04", rate=24), duration=otio.opentime.from_frames(48, 24) ), ) self.assertEqual( ref.target_url_for_image_number(0), "myfilename.0101.exr" ) if __name__ == "__main__": unittest.main() opentimelineio-0.18.1/tests/test_media_reference.py0000775000175000017500000000546215110656141020271 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test harness for Media References.""" import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils import unittest class MediaReferenceTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): tr = otio.opentime.TimeRange( otio.opentime.RationalTime(5, 24), otio.opentime.RationalTime(10, 24.0) ) mr = otio.schema.MissingReference( available_range=tr, metadata={'show': 'OTIOTheMovie'} ) self.assertEqual(mr.available_range, tr) mr = otio.schema.MissingReference() self.assertIsNone(mr.available_range) def test_str_missing(self): missing = otio.schema.MissingReference() self.assertMultiLineEqual( str(missing), "MissingReference(\'\', None, None, {})" ) self.assertMultiLineEqual( repr(missing), "otio.schema.MissingReference(" "name='', available_range=None, available_image_bounds=None, metadata={}" ")" ) encoded = otio.adapters.otio_json.write_to_string(missing) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(missing, decoded) def test_filepath(self): filepath = otio.schema.ExternalReference("/var/tmp/foo.mov") self.assertMultiLineEqual( str(filepath), 'ExternalReference("/var/tmp/foo.mov")' ) self.assertMultiLineEqual( repr(filepath), "otio.schema.ExternalReference(" "target_url='/var/tmp/foo.mov'" ")" ) # round trip serialize encoded = otio.adapters.otio_json.write_to_string(filepath) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(filepath, decoded) def test_equality(self): filepath = otio.schema.ExternalReference(target_url="/var/tmp/foo.mov") filepath2 = otio.schema.ExternalReference( target_url="/var/tmp/foo.mov" ) self.assertIsOTIOEquivalentTo(filepath, filepath2) bl = otio.schema.MissingReference() self.assertNotEqual(filepath, bl) filepath2 = otio.schema.ExternalReference( target_url="/var/tmp/foo2.mov" ) self.assertNotEqual(filepath, filepath2) self.assertEqual(filepath == filepath2, False) def test_is_missing(self): mr = otio.schema.ExternalReference(target_url="/var/tmp/foo.mov") self.assertFalse(mr.is_missing_reference) mr = otio.schema.MissingReference() self.assertTrue(mr.is_missing_reference) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_track.cpp0000664000175000017500000001474415110656141016432 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include #include #include namespace otime = opentime::OPENTIME_VERSION; namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; int main(int argc, char** argv) { Tests tests; tests.add_test( "test_find_children", [] { using namespace otio; otio::SerializableObject::Retainer cl = new otio::Clip(); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl); OTIO_NS::ErrorStatus err; auto result = tr->find_children(&err); assertEqual(result.size(), 1); assertEqual(result[0].value, cl.value); }); tests.add_test( "test_find_children_search_range", [] { using namespace otio; const TimeRange range(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)); otio::SerializableObject::Retainer cl0 = new otio::Clip(); cl0->set_source_range(range); otio::SerializableObject::Retainer cl1 = new otio::Clip(); cl1->set_source_range(range); otio::SerializableObject::Retainer cl2 = new otio::Clip(); cl2->set_source_range(range); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl0); tr->append_child(cl1); tr->append_child(cl2); OTIO_NS::ErrorStatus err; auto result = tr->find_children( &err, TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0))); assertEqual(result.size(), 1); assertEqual(result[0].value, cl0.value); result = tr->find_children( &err, TimeRange(RationalTime(24.0, 24.0), RationalTime(24.0, 24.0))); assertEqual(result.size(), 1); assertEqual(result[0].value, cl1.value); result = tr->find_children( &err, TimeRange(RationalTime(48.0, 24.0), RationalTime(24.0, 24.0))); assertEqual(result.size(), 1); assertEqual(result[0].value, cl2.value); result = tr->find_children( &err, TimeRange(RationalTime(0.0, 24.0), RationalTime(48.0, 24.0))); assertEqual(result.size(), 2); assertEqual(result[0].value, cl0.value); assertEqual(result[1].value, cl1.value); result = tr->find_children( &err, TimeRange(RationalTime(24.0, 24.0), RationalTime(48.0, 24.0))); assertEqual(result.size(), 2); assertEqual(result[0].value, cl1.value); assertEqual(result[1].value, cl2.value); result = tr->find_children( &err, TimeRange(RationalTime(0.0, 24.0), RationalTime(72.0, 24.0))); assertEqual(result.size(), 3); assertEqual(result[0].value, cl0.value); assertEqual(result[1].value, cl1.value); assertEqual(result[2].value, cl2.value); }); tests.add_test( "test_find_children_shallow_search", [] { using namespace otio; otio::SerializableObject::Retainer cl0 = new otio::Clip(); otio::SerializableObject::Retainer cl1 = new otio::Clip(); otio::SerializableObject::Retainer st = new otio::Stack(); st->append_child(cl1); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl0); tr->append_child(st); OTIO_NS::ErrorStatus err; auto result = tr->find_children(&err, std::nullopt, true); assertEqual(result.size(), 1); assertEqual(result[0].value, cl0.value); result = tr->find_children(&err, std::nullopt, false); assertEqual(result.size(), 2); assertEqual(result[0].value, cl0.value); assertEqual(result[1].value, cl1.value); }); tests.add_test( "test_find_children_stack", [] { using namespace otio; SerializableObject::Retainer stack = new Stack(); SerializableObject::Retainer track = new Track; SerializableObject::Retainer clip = new Clip; stack->append_child(track); track->append_child(clip); // Simple find. clip->set_source_range( TimeRange(RationalTime(0.0, 24.0), RationalTime(3.0, 24.0))); otio::ErrorStatus err; auto items = stack->find_children( &err, TimeRange(RationalTime(0.0, 24.0), RationalTime(1.0, 24.0))); assertFalse(is_error(err)); assertEqual(items.size(), 2); assertTrue( std::find(items.begin(), items.end(), track.value) != items.end()); assertTrue( std::find(items.begin(), items.end(), clip.value) != items.end()); // Set a short source range on the track. track->set_source_range( TimeRange(RationalTime(0.0, 24.0), RationalTime(2.0, 24.0))); items = stack->find_children( &err, TimeRange(RationalTime(2.0, 24.0), RationalTime(1.0, 24.0))); assertFalse(is_error(err)); assertEqual(items.size(), 0); // Set a source range with a positive offset on the track. track->set_source_range( TimeRange(RationalTime(3.0, 24.0), RationalTime(3.0, 24.0))); items = stack->find_children( &err, TimeRange(RationalTime(2.0, 24.0), RationalTime(1.0, 24.0))); assertFalse(is_error(err)); assertEqual(items.size(), 1); assertTrue( std::find(items.begin(), items.end(), track.value) != items.end()); // Set a source range with a negative offset on the track. track->set_source_range( TimeRange(RationalTime(-1.0, 24.0), RationalTime(3.0, 24.0))); items = stack->find_children( &err, TimeRange(RationalTime(1.0, 24.0), RationalTime(1.0, 24.0))); assertFalse(is_error(err)); assertEqual(items.size(), 2); assertTrue( std::find(items.begin(), items.end(), track.value) != items.end()); assertTrue( std::find(items.begin(), items.end(), clip.value) != items.end()); }); tests.run(argc, argv); return 0; } opentimelineio-0.18.1/tests/__init__.py0000664000175000017500000000013715110656141015663 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project opentimelineio-0.18.1/tests/test_builtin_adapters.py0000775000175000017500000000713515110656141020524 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test builtin adapters.""" import os import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils from opentimelineio.adapters import ( otio_json, ) import pathlib import tempfile SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") SCREENING_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "screening_example.otio") class BuiltInAdapterTest(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_disk_io(self): edl_path = SCREENING_EXAMPLE_PATH timeline = otio.adapters.read_from_file(edl_path) with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, "test_disk_io.otio") otio.adapters.write_to_file(timeline, temp_file) decoded = otio.adapters.read_from_file(temp_file) self.assertJsonEqual(timeline, decoded) def test_otio_round_trip(self): tl = otio.adapters.read_from_file(SCREENING_EXAMPLE_PATH) baseline_json = otio.adapters.otio_json.write_to_string(tl) self.assertEqual(tl.name, "Example_Screening.01") with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, 'test_otio_round_trip.otio') otio.adapters.otio_json.write_to_file(tl, temp_file) new = otio.adapters.otio_json.read_from_file(temp_file) new_json = otio.adapters.otio_json.write_to_string(new) self.assertMultiLineEqual(baseline_json, new_json) self.assertIsOTIOEquivalentTo(tl, new) def test_disk_vs_string(self): """ Writing to disk and writing to a string should produce the same result """ timeline = otio.adapters.read_from_file(SCREENING_EXAMPLE_PATH) with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, "test_disk_vs_string.otio") otio.adapters.write_to_file(timeline, temp_file) in_memory = otio.adapters.write_to_string(timeline, 'otio_json') with open(temp_file) as f: on_disk = f.read() self.maxDiff = None # for debugging # with open("/var/tmp/in_memory.otio", "w") as fo: # fo.write(in_memory) # # with open("/var/tmp/on_disk.otio", "w") as fo: # fo.write(on_disk) self.assertEqual(in_memory, on_disk) def test_adapters_fetch(self): """ Test the dynamic string based adapter fetching """ self.assertEqual( otio.adapters.from_name('otio_json').module(), otio_json ) def test_otio_json_default(self): tl = otio.adapters.read_from_file(SCREENING_EXAMPLE_PATH) self.assertMultiLineEqual( otio.adapters.write_to_string(tl, 'otio_json'), otio.adapters.write_to_string(tl) ) test_str = otio.adapters.write_to_string(tl) self.assertJsonEqual(tl, otio.adapters.read_from_string(test_str)) def test_otio_pathlib_filepath(self): """Tests reading / writing with a filepath that's a Path object.""" tl = otio.adapters.read_from_file(pathlib.Path(SCREENING_EXAMPLE_PATH)) with tempfile.TemporaryDirectory() as temp_dir: tmp_path = pathlib.Path(temp_dir) / "tmp_pathlib.otio" otio.adapters.write_to_file(input_otio=tl, filepath=tmp_path) self.assertJsonEqual(tl, otio.adapters.read_from_file(filepath=tmp_path)) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/utils.py0000664000175000017500000000201615110656141015262 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Reusable utilities for tests.""" # import built-in modules import os import tempfile # import local modules import opentimelineio as otio from tests import baseline_reader MANIFEST_PATH = "adapter_plugin_manifest.plugin_manifest" def create_manifest(): """Create a temporary manifest.""" full_baseline = baseline_reader.json_baseline_as_string(MANIFEST_PATH) temp_dir = tempfile.mkdtemp(prefix='test_otio_manifest') man_path = os.path.join(temp_dir, 'manifest') with open(man_path, 'w') as fo: fo.write(full_baseline) man = otio.plugins.manifest_from_file(man_path) man._update_plugin_source(baseline_reader.path_to_baseline(MANIFEST_PATH)) return man def remove_manifest(manifest): """Remove the manifest source files.""" for file_path in manifest.source_files: # don't accidentally blow away python if not file_path.endswith('.py'): os.remove(file_path) opentimelineio-0.18.1/tests/test_composition.cpp0000664000175000017500000000367115110656141017666 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include #include #include #include #include #include namespace otime = opentime::OPENTIME_VERSION; namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; int main(int argc, char** argv) { Tests tests; // test a basic case of find_children tests.add_test( "test_find_children", [] { using namespace otio; SerializableObject::Retainer comp = new Composition; SerializableObject::Retainer item = new Item; comp->append_child(item); OTIO_NS::ErrorStatus err; auto result = comp->find_children<>(&err); assertEqual(result.size(), 1); assertEqual(result[0].value, item.value); }); // test stack and track correctly calls find_clips from composition parent class tests.add_test( "test_find_clips", [] { using namespace otio; SerializableObject::Retainer stack = new Stack(); SerializableObject::Retainer track = new Track; SerializableObject::Retainer clip = new Clip; SerializableObject::Retainer transition = new Transition; stack->append_child(track); track->append_child(transition); track->append_child(clip); OTIO_NS::ErrorStatus err; auto items = stack->find_clips(&err); assertFalse(is_error(err)); assertEqual(items.size(), 1); assertEqual(items[0].value, clip.value); items = track->find_clips(&err); assertFalse(is_error(err)); assertEqual(items.size(), 1); assertEqual(items[0].value, clip.value); }); tests.run(argc, argv); return 0; } opentimelineio-0.18.1/tests/test_schemadef_plugin.py0000775000175000017500000000660415110656141020470 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import os import opentimelineio as otio from tests import baseline_reader """Unit tests for the schemadef plugin system.""" SCHEMADEF_NAME = "schemadef_example" EXAMPLE_ARG = "exampleArg" EXCLASS = "" TEST_STRING = """ { "OTIO_SCHEMA": "exampleSchemaDef.1", "exampleArg": "foobar" } """ def _clean_plugin_module(): """Remove the example_schemadef if its already been loaded to test autoload/explicit load behavior. """ try: del otio.schemadef.example_schemadef except AttributeError: pass try: plugin = otio.schema.schemadef.from_name("example_schemadef") plugin._module = None except otio.exceptions.NotSupportedError: pass class TestPluginSchemadefs(unittest.TestCase): def setUp(self): self.save_manifest = otio.plugins.manifest._MANIFEST self.save_manifest_path = os.environ.get('OTIO_PLUGIN_MANIFEST_PATH') # find the path to the baselines/schemadef_example.json self.manifest_path = baseline_reader.path_to_baseline(SCHEMADEF_NAME) os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = self.manifest_path otio.plugins.manifest.ActiveManifest(force_reload=True) _clean_plugin_module() def tearDown(self): # restore original state if self.save_manifest_path: os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = self.save_manifest_path else: del os.environ['OTIO_PLUGIN_MANIFEST_PATH'] otio.plugins.manifest._MANIFEST = self.save_manifest _clean_plugin_module() def test_autoloaded_plugin(self): with self.assertRaises(AttributeError): otio.schemadef.example_schemadef # should force an autoload thing = otio.adapters.read_from_string(TEST_STRING, "otio_json") self.assertEqual(thing.exampleArg, "foobar") def test_plugin_schemadef(self): with self.assertRaises(AttributeError): otio.schemadef.example_schemadef # force loading the module otio.schema.schemadef.module_from_name("example_schemadef") # Our test manifest should have been loaded, including # the example_schemadef. # Try creating a schema object using the instance_from_schema method. peculiar_value = "something One-derful" example = otio.core.instance_from_schema("exampleSchemaDef", 1, { EXAMPLE_ARG: peculiar_value }) self.assertEqual(str(type(example)), EXCLASS) self.assertEqual(example.exampleArg, peculiar_value) def test_plugin_schemadef_namespace(self): with self.assertRaises(AttributeError): otio.schemadef.example_schemadef # force loading the module plugin_module = otio.schema.schemadef.module_from_name( "example_schemadef" ) # Try creating schema object with the direct class definition method: peculiar_value = "something Two-derful" example = otio.schemadef.example_schemadef.exampleSchemaDef(peculiar_value) self.assertEqual(plugin_module, otio.schemadef.example_schemadef) self.assertEqual(str(type(example)), EXCLASS) self.assertEqual(example.exampleArg, peculiar_value) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_media_linker.py0000664000175000017500000000555415110656141017616 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import os import unittest from tests import baseline_reader import opentimelineio as otio from tests import utils LINKER_PATH = "media_linker_example" class TestPluginMediaLinker(unittest.TestCase): def setUp(self): self.bak = otio.plugins.ActiveManifest() self.man = utils.create_manifest() otio.plugins.manifest._MANIFEST = self.man self.jsn = baseline_reader.json_baseline_as_string(LINKER_PATH) self.mln = otio.adapters.otio_json.read_from_string(self.jsn) self.mln._json_path = os.path.join( baseline_reader.MODPATH, "baselines", LINKER_PATH ) def tearDown(self): otio.plugins.manifest._MANIFEST = self.bak utils.remove_manifest(self.man) def test_plugin_adapter(self): self.assertEqual(self.mln.name, "example") self.assertEqual(self.mln.filepath, "example.py") def test_load_adapter_module(self): target = os.path.join( baseline_reader.MODPATH, "baselines", "example.py" ) self.assertEqual(self.mln.module_abs_path(), target) self.assertTrue(hasattr(self.mln.module(), "link_media_reference")) def test_run_linker(self): cl = otio.schema.Clip(name="foo") linked_mr = self.mln.link_media_reference(cl, {"extra_data": True}) self.assertIsInstance(linked_mr, otio.schema.MissingReference) self.assertEqual(linked_mr.name, cl.name + "_tweaked") self.assertEqual(linked_mr.metadata.get("extra_data"), True) def test_serialize(self): self.assertEqual( str(self.mln), "MediaLinker({}, {})".format( repr(self.mln.name), repr(self.mln.filepath) ) ) self.assertEqual( repr(self.mln), "otio.media_linker.MediaLinker(" "name={}, " "filepath={}" ")".format( repr(self.mln.name), repr(self.mln.filepath) ) ) def test_available_media_linker_names(self): # for now just assert that it returns a non-empty list self.assertTrue(otio.media_linker.available_media_linker_names()) def test_default_media_linker(self): os.environ['OTIO_DEFAULT_MEDIA_LINKER'] = 'foo' self.assertEqual(otio.media_linker.default_media_linker(), 'foo') with self.assertRaises(otio.exceptions.NoDefaultMediaLinkerError): del os.environ['OTIO_DEFAULT_MEDIA_LINKER'] otio.media_linker.default_media_linker() def test_from_name_fail(self): with self.assertRaises(otio.exceptions.NotSupportedError): otio.media_linker.from_name("should not exist") if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_cxx_sdk_bindings.py0000664000175000017500000000050415110656141020501 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio class CxxSDKTests(unittest.TestCase): def test_cpp_big_ints(self): self.assertTrue(otio._otio._testing.test_big_uint()) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_unknown_schema.py0000664000175000017500000000367215110656141020211 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils has_undefined_schema = """ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 140 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 91 } }, "metadata": { "stuff": { "OTIO_SCHEMA": "MyOwnDangSchema.3", "some_data": 895, "howlongami": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30, "value": 100 } } }, "name": null, "target_url": "/usr/tmp/some_media.mov" }, "metadata": {}, "name": null, "source_range": null } """ class UnknownSchemaTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def setUp(self): # make an OTIO data structure containing an undefined schema object self.orig = otio.adapters.otio_json.read_from_string(has_undefined_schema) def test_serialize_deserialize(self): serialized = otio.adapters.otio_json.write_to_string(self.orig) test_otio = otio.adapters.otio_json.read_from_string(serialized) self.assertIsOTIOEquivalentTo(self.orig, test_otio) def test_is_unknown_schema(self): self.assertFalse(self.orig.is_unknown_schema) unknown = self.orig.media_reference.metadata["stuff"] self.assertTrue(unknown.is_unknown_schema) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_clip.py0000664000175000017500000002233515110656141016116 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class ClipTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): name = "test" rt = otio.opentime.RationalTime(5, 24) tr = otio.opentime.TimeRange(rt, rt) mr = otio.schema.ExternalReference( available_range=otio.opentime.TimeRange( rt, otio.opentime.RationalTime(10, 24) ), target_url="/var/tmp/test.mov" ) ltw = otio.schema.LinearTimeWarp( name="linear_time_warp", time_scalar=1.5) effects = [] effects.append(ltw) red = otio.schema.MarkerColor.RED m = otio.schema.Marker( name="red_marker", color=red) markers = [] markers.append(m) cl = otio.schema.Clip( name=name, media_reference=mr, source_range=tr, effects=effects, markers=markers, # transition_in # transition_out ) self.assertEqual(cl.name, name) self.assertEqual(cl.source_range, tr) self.assertIsOTIOEquivalentTo(cl.media_reference, mr) self.assertTrue(isinstance(cl.effects[0], otio.schema.LinearTimeWarp)) self.assertEqual(cl.markers[0].color, red) encoded = otio.adapters.otio_json.write_to_string(cl) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(cl, decoded) def test_find_clips(self): cl = otio.schema.Clip(name="test_clip") self.assertEqual(list(cl.find_clips()), [cl]) def test_str(self): cl = otio.schema.Clip(name="test_clip") self.assertMultiLineEqual( str(cl), 'Clip("test_clip", ' 'MissingReference(\'\', None, None, {}), None, {}, [], [])' ) self.assertMultiLineEqual( repr(cl), 'otio.schema.Clip(' "name='test_clip', " 'media_reference={}, ' 'source_range=None, ' 'color=None, ' 'metadata={{}}, ' 'effects=[], ' 'markers=[]' ')'.format( repr(cl.media_reference) ) ) def test_str_with_filepath(self): cl = otio.schema.Clip( name="test_clip", media_reference=otio.schema.ExternalReference( "/var/tmp/foo.mov" ) ) self.assertMultiLineEqual( str(cl), 'Clip(' '"test_clip", ' 'ExternalReference("/var/tmp/foo.mov"), None, {}, [], []' ')' ) self.assertMultiLineEqual( repr(cl), 'otio.schema.Clip(' "name='test_clip', " "media_reference=otio.schema.ExternalReference(" "target_url='/var/tmp/foo.mov'" "), " 'source_range=None, ' 'color=None, ' 'metadata={}, ' 'effects=[], ' 'markers=[]' ')' ) def test_ranges(self): tr = otio.opentime.TimeRange( # 1 hour in at 24 fps start_time=otio.opentime.RationalTime(86400, 24), duration=otio.opentime.RationalTime(200, 24) ) cl = otio.schema.Clip( name="test_clip", media_reference=otio.schema.ExternalReference( "/var/tmp/foo.mov", available_range=tr ) ) self.assertEqual(cl.duration(), cl.trimmed_range().duration) self.assertEqual(cl.duration(), tr.duration) self.assertEqual(cl.trimmed_range(), tr) self.assertEqual(cl.available_range(), tr) self.assertIsNot(cl.trimmed_range(), tr) self.assertIsNot(cl.available_range(), tr) cl.source_range = otio.opentime.TimeRange( # 1 hour + 100 frames start_time=otio.opentime.RationalTime(86500, 24), duration=otio.opentime.RationalTime(50, 24) ) self.assertNotEqual(cl.duration(), tr.duration) self.assertNotEqual(cl.trimmed_range(), tr) self.assertEqual(cl.duration(), cl.source_range.duration) self.assertIsNot(cl.duration(), cl.source_range.duration) self.assertEqual(cl.trimmed_range(), cl.source_range) self.assertIsNot(cl.trimmed_range(), cl.source_range) def test_available_image_bounds(self): available_image_bounds = otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(16.0, 9.0) ) media_reference = otio.schema.ExternalReference( "/var/tmp/foo.mov", available_image_bounds=available_image_bounds ) cl = otio.schema.Clip( name="test_available_image_bounds", media_reference=media_reference ) self.assertEqual(available_image_bounds, cl.available_image_bounds) self.assertEqual( cl.available_image_bounds, media_reference.available_image_bounds ) self.assertEqual(0.0, cl.available_image_bounds.min.x) self.assertEqual(0.0, cl.available_image_bounds.min.y) self.assertEqual(16.0, cl.available_image_bounds.max.x) self.assertEqual(9.0, cl.available_image_bounds.max.y) # test range exceptions cl.media_reference.available_image_bounds = None with self.assertRaises(otio.exceptions.CannotComputeAvailableRangeError): cl.available_range() def test_ref_default(self): cl = otio.schema.Clip() self.assertIsOTIOEquivalentTo( cl.media_reference, otio.schema.MissingReference() ) cl.media_reference = None self.assertIsOTIOEquivalentTo( cl.media_reference, otio.schema.MissingReference() ) cl.media_reference = otio.schema.ExternalReference() self.assertIsOTIOEquivalentTo( cl.media_reference, otio.schema.ExternalReference() ) def test_multi_ref(self): cl = otio.schema.Clip() self.assertEqual( otio.schema.Clip.DEFAULT_MEDIA_KEY, cl.active_media_reference_key ) self.assertIsOTIOEquivalentTo( cl.media_reference, otio.schema.MissingReference() ) mrs = cl.media_references() self.assertIsOTIOEquivalentTo( mrs[otio.schema.Clip.DEFAULT_MEDIA_KEY], otio.schema.MissingReference() ) cl.set_media_references( { otio.schema.Clip.DEFAULT_MEDIA_KEY: otio.schema.ExternalReference(), "high_quality": otio.schema.GeneratorReference(), "proxy_quality": otio.schema.ImageSequenceReference(), }, otio.schema.Clip.DEFAULT_MEDIA_KEY) mrs = cl.media_references() self.assertIsOTIOEquivalentTo( mrs[otio.schema.Clip.DEFAULT_MEDIA_KEY], otio.schema.ExternalReference() ) self.assertIsOTIOEquivalentTo( mrs["high_quality"], otio.schema.GeneratorReference() ) self.assertIsOTIOEquivalentTo( mrs["proxy_quality"], otio.schema.ImageSequenceReference() ) cl.active_media_reference_key = "high_quality" self.assertIsOTIOEquivalentTo( cl.media_reference, otio.schema.GeneratorReference() ) self.assertEqual( cl.active_media_reference_key, "high_quality" ) # we should get an exception if we try to use a key that is # not in the media_references with self.assertRaises(ValueError): cl.active_media_reference_key = "cloud" self.assertEqual( cl.active_media_reference_key, "high_quality" ) # we should also get an exception if we set the references without # the active key with self.assertRaises(ValueError): cl.set_media_references( { "cloud": otio.schema.ExternalReference() }, "high_quality" ) self.assertEqual( cl.active_media_reference_key, "high_quality" ) # we should also get an exception if we set the references with # an empty key with self.assertRaises(ValueError): cl.set_media_references( { "": otio.schema.ExternalReference() }, "" ) self.assertEqual( cl.active_media_reference_key, "high_quality" ) # setting the references and the active key should resolve the problem cl.set_media_references( { "cloud": otio.schema.ExternalReference() }, "cloud" ) self.assertEqual(cl.active_media_reference_key, "cloud") self.assertIsOTIOEquivalentTo( cl.media_reference, otio.schema.ExternalReference()) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/consumer/0000775000175000017500000000000015110656141015404 5ustar memeopentimelineio-0.18.1/tests/consumer/opentime/0000775000175000017500000000000015110656141017224 5ustar memeopentimelineio-0.18.1/tests/consumer/opentime/CMakeLists.txt0000664000175000017500000000014315110656141021762 0ustar memefind_package(OpenTime REQUIRED) message(STATUS "Found OpenTime successfully at '${OpenTime_DIR}'") opentimelineio-0.18.1/tests/consumer/opentimeline/0000775000175000017500000000000015110656141020074 5ustar memeopentimelineio-0.18.1/tests/consumer/opentimeline/CMakeLists.txt0000664000175000017500000000016515110656141022636 0ustar memefind_package(OpenTimelineIO REQUIRED) message(STATUS "Found OpenTimelineIO successfully at '${OpenTimelineIO_DIR}'") opentimelineio-0.18.1/tests/consumer/CMakeLists.txt0000664000175000017500000000020715110656141020143 0ustar memecmake_minimum_required(VERSION 3.18.2 FATAL_ERROR) project(consumer_tests) add_subdirectory(opentime) add_subdirectory(opentimeline) opentimelineio-0.18.1/tests/test_serializable_object.py0000775000175000017500000002301215110656141021157 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils import unittest import json class OpenTimeTypeSerializerTest(unittest.TestCase): def test_serialize_time(self): rt = otio.opentime.RationalTime(15, 24) encoded = otio.adapters.otio_json.write_to_string(rt) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertEqual(rt, decoded) rt_dur = otio.opentime.RationalTime(10, 20) tr = otio.opentime.TimeRange(rt, rt_dur) encoded = otio.adapters.otio_json.write_to_string(tr) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertEqual(tr, decoded) tt = otio.opentime.TimeTransform(rt, scale=1.5) encoded = otio.adapters.otio_json.write_to_string(tt) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertEqual(tt, decoded) class SerializableObjTest(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): so = otio.core.SerializableObjectWithMetadata() so.metadata['foo'] = 'bar' self.assertEqual(so.metadata['foo'], 'bar') def test_update(self): so = otio.core.SerializableObjectWithMetadata() so.metadata.update({"foo": "bar"}) self.assertEqual(so.metadata["foo"], "bar") so_2 = otio.core.SerializableObjectWithMetadata() so_2.metadata["foo"] = "not bar" so.metadata.update(so_2.metadata) self.assertEqual(so.metadata["foo"], "not bar") def test_copy_lib(self): so = otio.core.SerializableObjectWithMetadata() so.metadata["meta_data"] = {"foo": "bar"} import copy # shallow copy is an error with self.assertRaises(ValueError): so_cp = copy.copy(so) # deep copy so_cp = copy.deepcopy(so) self.assertIsNotNone(so_cp) self.assertIsOTIOEquivalentTo(so, so_cp) so_cp.metadata["foo"] = "bar" self.assertNotEqual(so, so_cp) def test_copy_subclass(self): @otio.core.register_type class Foo(otio.core.SerializableObjectWithMetadata): _serializable_label = "Foof.1" foo = Foo() foo.metadata["meta_data"] = {"foo": "bar"} import copy with self.assertRaises(ValueError): foo_copy = copy.copy(foo) foo_copy = copy.deepcopy(foo) self.assertEqual(Foo, type(foo_copy)) def test_equality(self): o1 = otio.core.SerializableObject() o2 = otio.core.SerializableObject() self.assertTrue(o1 is not o2) self.assertTrue(o1.is_equivalent_to(o2)) self.assertIsOTIOEquivalentTo(o1, o2) def test_equivalence_symmetry(self): def test_equivalence(A, B, msg): self.assertTrue(A.is_equivalent_to(B), f"{msg}: A ~= B") self.assertTrue(B.is_equivalent_to(A), f"{msg}: B ~= A") def test_difference(A, B, msg): self.assertFalse(A.is_equivalent_to(B), f"{msg}: A ~= B") self.assertFalse(B.is_equivalent_to(A), f"{msg}: B ~= A") A = otio.core.Composable() B = otio.core.Composable() test_equivalence(A, B, "blank objects") A.metadata["key"] = {"a": 0} test_difference(A, B, "A has different metadata") B.metadata["key"] = {"a": 0} test_equivalence(A, B, "add metadata to B") A.metadata["key"]["sub-key"] = 1 test_difference(A, B, "Add dict within A with specific metadata") def test_truthiness(self): o = otio.core.SerializableObject() self.assertTrue(o) def test_instancing_without_instancing_support(self): o = otio.core.SerializableObjectWithMetadata() c = otio.core.SerializableObjectWithMetadata() o.metadata["child1"] = c o.metadata["child2"] = c self.assertTrue(o.metadata["child1"] is o.metadata["child2"]) oCopy = o.clone() # Note: If we ever enable INSTANCING_SUPPORT in the C++ code, # then this will (and should) fail self.assertTrue(oCopy.metadata["child1"] is not oCopy.metadata["child2"]) def test_cycle_detection(self): o = otio.core.SerializableObjectWithMetadata() o.metadata["myself"] = o # Note: If we ever enable INSTANCING_SUPPORT in the C++ code, # then modify the code below to be: # oCopy = o.clone() # self.assertTrue(oCopy is oCopy.metadata["myself"]) with self.assertRaises(ValueError): o.clone() def test_imath(self): b = otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(16.0, 9.0)) so = otio.core.SerializableObjectWithMetadata() so.metadata["box"] = b self.assertEqual(repr(so.metadata["box"]), repr(b)) v = [b.min, b.max] so.metadata["vectors"] = v self.assertEqual(repr(so.metadata["vectors"]), repr(v)) class VersioningTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_schema_definition(self): """define a schema and instantiate it from python""" # ensure that the type hasn't already been registered self.assertNotIn("Stuff", otio.core.type_version_map()) @otio.core.register_type class FakeThing(otio.core.SerializableObject): _serializable_label = "Stuff.1" foo_two = otio.core.serializable_field("foo_2", doc="test") ft = FakeThing() self.assertEqual(ft.schema_name(), "Stuff") self.assertEqual(ft.schema_version(), 1) with self.assertRaises(otio.exceptions.UnsupportedSchemaError): otio.core.instance_from_schema( "Stuff", 2, {"foo": "bar"} ) version_map = otio.core.type_version_map() self.assertEqual(version_map["Stuff"], 1) ft = otio.core.instance_from_schema("Stuff", 1, {"foo": "bar"}) self.assertEqual(ft._dynamic_fields['foo'], "bar") @unittest.skip("@TODO: disabled pending discussion") def test_double_register_schema(self): @otio.core.register_type class DoubleReg(otio.core.SerializableObject): _serializable_label = "Stuff.1" foo_two = otio.core.serializable_field("foo_2", doc="test") _ = DoubleReg() # quiet pyflakes # not allowed to register a type twice with self.assertRaises(ValueError): @otio.core.register_type class DoubleReg(otio.core.SerializableObject): _serializable_label = "Stuff.1" def test_upgrade_versions(self): """Test adding an upgrade functions for a type""" @otio.core.register_type class FakeThing(otio.core.SerializableObject): _serializable_label = "NewStuff.4" foo_two = otio.core.serializable_field("foo_2") @otio.core.upgrade_function_for(FakeThing, 2) def upgrade_one_to_two(_data_dict): return {"foo_2": _data_dict["foo"]} @otio.core.upgrade_function_for(FakeThing, 3) def upgrade_one_to_two_three(_data_dict): return {"foo_3": _data_dict["foo_2"]} # @TODO: further discussion required # not allowed to overwrite registered functions # with self.assertRaises(ValueError): # @otio.core.upgrade_function_for(FakeThing, 3) # def upgrade_one_to_two_three_again(_data_dict): # raise RuntimeError("shouldn't see this ever") ft = otio.core.instance_from_schema("NewStuff", 1, {"foo": "bar"}) self.assertEqual(ft._dynamic_fields['foo_3'], "bar") ft = otio.core.instance_from_schema("NewStuff", 3, {"foo_2": "bar"}) self.assertEqual(ft._dynamic_fields['foo_3'], "bar") ft = otio.core.instance_from_schema("NewStuff", 4, {"foo_3": "bar"}) self.assertEqual(ft._dynamic_fields['foo_3'], "bar") def test_upgrade_rename(self): """test that upgrading system handles schema renames correctly""" @otio.core.register_type class FakeThingToRename(otio.core.SerializableObject): _serializable_label = "ThingToRename.2" my_field = otio.core.serializable_field("my_field", doc="example") thing = otio.core.type_version_map() self.assertTrue(thing) def test_downgrade_version(self): """ test a python defined downgrade function""" @otio.core.register_type class FakeThing(otio.core.SerializableObject): _serializable_label = "FakeThingToDowngrade.2" foo_two = otio.core.serializable_field("foo_2") @otio.core.downgrade_function_from(FakeThing, 2) def downgrade_2_to_1(_data_dict): return {"foo": _data_dict["foo_2"]} # @TODO: further discussion required # # not allowed to overwrite registered functions # with self.assertRaises(ValueError): # @otio.core.downgrade_function_from(FakeThing, 2) # def downgrade_2_to_1_again(_data_dict): # raise RuntimeError("shouldn't see this ever") f = FakeThing() f.foo_two = "a thing here" downgrade_target = {"FakeThingToDowngrade": 1} result = json.loads( otio.adapters.otio_json.write_to_string(f, downgrade_target) ) self.assertDictEqual( result, { "OTIO_SCHEMA": "FakeThingToDowngrade.1", "foo": "a thing here", } ) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_hooks_plugins.py0000664000175000017500000001761215110656141020055 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import os from copy import deepcopy import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils from tests import ( baseline_reader, utils, ) import tempfile HOOKSCRIPT_PATH = "hookscript_example" POST_WRITE_HOOKSCRIPT_PATH = "post_write_hookscript_example" CUSTOM_ADAPTER_HOOKSCRIPT_PATH = "custom_adapter_hookscript_example" POST_RUN_NAME = "hook ran and did stuff" TEST_METADATA = {'extra_data': True} class HookScriptTest(unittest.TestCase, otio_test_utils.OTIOAssertions): """Tests for the hook function plugins.""" def setUp(self): self.jsn = baseline_reader.json_baseline_as_string(HOOKSCRIPT_PATH) self.hook_script = otio.adapters.read_from_string( self.jsn, 'otio_json' ) self.hook_script._json_path = os.path.join( baseline_reader.MODPATH, "baselines", HOOKSCRIPT_PATH ) def test_plugin_hook(self): self.assertEqual(self.hook_script.name, "example hook") self.assertEqual(self.hook_script.filepath, "example.py") def test_plugin_hook_runs(self): tl = otio.schema.Timeline() tl = self.hook_script.run(tl) self.assertEqual(tl.name, POST_RUN_NAME) class TestPluginHookSystem(unittest.TestCase): """ Test the hook point definition system """ def setUp(self): self.man = utils.create_manifest() self.jsn = baseline_reader.json_baseline_as_string(HOOKSCRIPT_PATH) self.hsf = otio.adapters.otio_json.read_from_string(self.jsn) self.hsf._json_path = os.path.join( baseline_reader.MODPATH, "baselines", HOOKSCRIPT_PATH ) self.post_jsn = baseline_reader.json_baseline_as_string( POST_WRITE_HOOKSCRIPT_PATH ) self.post_hsf = otio.adapters.otio_json.read_from_string( self.post_jsn ) self.post_hsf._json_path = os.path.join( baseline_reader.MODPATH, "baselines", POST_WRITE_HOOKSCRIPT_PATH ) self.adapter_hook_jsn = baseline_reader.json_baseline_as_string( CUSTOM_ADAPTER_HOOKSCRIPT_PATH ) self.adapter_hookscript = otio.adapters.otio_json.read_from_string( self.adapter_hook_jsn) self.adapter_hookscript._json_path = os.path.join( baseline_reader.MODPATH, "baselines", HOOKSCRIPT_PATH ) self.man.hook_scripts = [self.hsf, self.post_hsf, self.adapter_hookscript] self.orig_manifest = otio.plugins.manifest._MANIFEST otio.plugins.manifest._MANIFEST = self.man def tearDown(self): utils.remove_manifest(self.man) otio.plugins.manifest._MANIFEST = self.orig_manifest def test_plugin_adapter(self): self.assertEqual(self.hsf.name, "example hook") self.assertEqual(self.hsf.filepath, "example.py") self.assertEqual(otio.adapters.from_name("example").adapter_hook_names(), ["custom_adapter_hook"]) def test_load_adapter_module(self): target = os.path.join( baseline_reader.MODPATH, "baselines", "example.py" ) self.assertEqual(self.hsf.module_abs_path(), target) self.assertTrue(hasattr(self.hsf.module(), "hook_function")) def test_run_hook_function(self): tl = otio.schema.Timeline() result = self.hsf.run(tl, TEST_METADATA) self.assertEqual(result.name, POST_RUN_NAME) self.assertEqual(result.metadata.get("extra_data"), True) def test_run_custom_hook_function(self): tl = otio.schema.Timeline() result = otio.hooks.run(hook="custom_adapter_hook", tl=tl, extra_args=TEST_METADATA) self.assertEqual(result.metadata["custom_hook"], TEST_METADATA) def test_run_hook_through_adapters(self): hook_map = dict(TEST_METADATA) hook_map["run_custom_hook"] = True result = otio.adapters.read_from_string( 'foo', adapter_name='example', media_linker_name='example', hook_function_argument_map=hook_map ) self.assertEqual(result.name, POST_RUN_NAME) self.assertEqual(result.metadata.get("extra_data"), True) self.assertEqual(result.metadata["custom_hook"]["extra_data"], True) def test_post_write_hook(self): self.man.adapters.extend(self.orig_manifest.adapters) tl = otio.schema.Timeline() tl_copy = deepcopy(tl) with tempfile.TemporaryDirectory() as temp_dir: filename = os.path.join(temp_dir, "post_hook_unittest.otio") arg_map = dict() otio.adapters.write_to_file( tl, filename, adapter_name='otio_json', hook_function_argument_map=arg_map ) self.assertTrue(os.path.exists(filename)) self.assertEqual( os.path.getsize(filename), tl.metadata.get('filesize') ) # In this case the post hook actually changed the metadata. # Users must take care to catch changes they do in their hooks. self.assertNotEqual(tl, tl_copy) def test_serialize(self): self.assertEqual( str(self.hsf), "HookScript({}, {})".format( repr(self.hsf.name), repr(self.hsf.filepath) ) ) self.assertEqual( repr(self.hsf), "otio.hooks.HookScript(" "name={}, " "filepath={}" ")".format( repr(self.hsf.name), repr(self.hsf.filepath) ) ) def test_available_hookscript_names(self): # for not just assert that it returns a non-empty list self.assertEqual( list(otio.hooks.available_hookscripts()), [self.hsf, self.post_hsf, self.adapter_hookscript] ) self.assertEqual( otio.hooks.available_hookscript_names(), [self.hsf.name, self.post_hsf.name, self.adapter_hookscript.name] ) def test_manifest_hooks(self): self.assertEqual( sorted(list(otio.hooks.names())), sorted( ["post_adapter_read", "post_media_linker", "pre_adapter_write", "post_adapter_write", "custom_adapter_hook"] ) ) self.assertEqual( list(otio.hooks.scripts_attached_to("pre_adapter_write")), [ self.hsf.name, self.hsf.name ] ) self.assertEqual( list(otio.hooks.scripts_attached_to("post_adapter_read")), [] ) self.assertEqual( list(otio.hooks.scripts_attached_to("post_media_linker")), [ self.hsf.name ] ) self.assertEqual( list(otio.hooks.scripts_attached_to("post_adapter_write")), [ self.post_hsf.name ] ) self.assertEqual( list(otio.hooks.scripts_attached_to("custom_adapter_hook")), [ self.adapter_hookscript.name ] ) tl = otio.schema.Timeline() result = otio.hooks.run("pre_adapter_write", tl, TEST_METADATA) self.assertEqual(result.name, POST_RUN_NAME) self.assertEqual(result.metadata, TEST_METADATA) # test deleting all the functions del otio.hooks.scripts_attached_to("pre_adapter_write")[:] tl = otio.schema.Timeline() tl.name = "ORIGINAL" result = otio.hooks.run("pre_adapter_write", tl, TEST_METADATA) self.assertEqual(result.name, "ORIGINAL") if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_serializableCollection.cpp0000664000175000017500000000660715110656141022007 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include #include #include #include namespace otime = opentime::OPENTIME_VERSION; namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; int main(int argc, char** argv) { Tests tests; tests.add_test( "test_find_children", [] { using namespace otio; otio::SerializableObject::Retainer cl = new otio::Clip(); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl); otio::SerializableObject::Retainer tl = new otio::Timeline(); tl->tracks()->append_child(tr); otio::SerializableObject::Retainer sc = new otio::SerializableCollection(); sc->insert_child(0, tl); OTIO_NS::ErrorStatus err; auto result = sc->find_children(&err, {}, false); assertEqual(result.size(), 1); assertEqual(result[0].value, cl.value); }); tests.add_test( "test_find_children_search_range", [] { using namespace otio; const TimeRange range(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)); otio::SerializableObject::Retainer cl0 = new otio::Clip(); cl0->set_source_range(range); otio::SerializableObject::Retainer cl1 = new otio::Clip(); cl1->set_source_range(range); otio::SerializableObject::Retainer cl2 = new otio::Clip(); cl2->set_source_range(range); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl0); tr->append_child(cl1); tr->append_child(cl2); otio::SerializableObject::Retainer tl = new otio::Timeline(); tl->tracks()->append_child(tr); otio::SerializableObject::Retainer sc = new otio::SerializableCollection(); sc->insert_child(0, tl); OTIO_NS::ErrorStatus err; auto result = sc->find_children(&err, range); assertEqual(result.size(), 1); assertEqual(result[0].value, cl0.value); }); tests.add_test( "test_find_children_shallow_search", [] { using namespace otio; otio::SerializableObject::Retainer cl = new otio::Clip(); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl); otio::SerializableObject::Retainer tl = new otio::Timeline(); tl->tracks()->append_child(tr); otio::SerializableObject::Retainer sc = new otio::SerializableCollection(); sc->insert_child(0, tl); OTIO_NS::ErrorStatus err; auto result = sc->find_children(&err, std::nullopt, true); assertEqual(result.size(), 0); result = sc->find_children(&err, std::nullopt, false); assertEqual(result.size(), 1); assertEqual(result[0].value, cl.value); }); tests.run(argc, argv); return 0; } opentimelineio-0.18.1/tests/test_box2d.py0000664000175000017500000000550715110656141016207 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class Box2dTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): box = otio.schema.Box2d() self.assertEqual(box.min, otio.schema.V2d(0.0, 0.0)) self.assertEqual(box.max, otio.schema.V2d(0.0, 0.0)) v1 = otio.schema.V2d(1.0, 2.0) v2 = otio.schema.V2d(3.0, 4.0) box = otio.schema.Box2d(v1, v2) self.assertEqual(box.min, v1) self.assertEqual(box.max, v2) def test_str(self): v1 = otio.schema.V2d(1.0, 2.0) v2 = otio.schema.V2d(3.0, 4.0) box = otio.schema.Box2d(v1, v2) self.assertMultiLineEqual( str(box), 'Box2d(V2d(1.0, 2.0), V2d(3.0, 4.0))' ) self.assertMultiLineEqual( repr(box), 'otio.schema.Box2d(' 'min=otio.schema.V2d(x=1.0, y=2.0), ' 'max=otio.schema.V2d(x=3.0, y=4.0)' ')' ) def test_center(self): v1 = otio.schema.V2d(1.0, 2.0) v2 = otio.schema.V2d(3.0, 4.0) box1 = otio.schema.Box2d(v1, v2) self.assertEqual(box1.center(), otio.schema.V2d(2.0, 3.0) ) def test_extend(self): v1 = otio.schema.V2d(1.0, 2.0) v2 = otio.schema.V2d(3.0, 4.0) box1 = otio.schema.Box2d(v1, v2) box1.extendBy(otio.schema.V2d(5.0, 5.0)) self.assertEqual(box1, otio.schema.Box2d( otio.schema.V2d(1.0, 2.0), otio.schema.V2d(5.0, 5.0) ) ) v3 = otio.schema.V2d(2.0, 3.0) v4 = otio.schema.V2d(6.0, 6.0) box2 = otio.schema.Box2d(v3, v4) box1.extendBy(box2) self.assertEqual(box1, otio.schema.Box2d( otio.schema.V2d(1.0, 2.0), otio.schema.V2d(6.0, 6.0) ) ) def test_intersects(self): v1 = otio.schema.V2d(1.0, 2.0) v2 = otio.schema.V2d(3.0, 4.0) box1 = otio.schema.Box2d(v1, v2) self.assertTrue(box1.intersects(otio.schema.V2d(2.0, 3.0))) self.assertFalse(box1.intersects(otio.schema.V2d(0.0, 3.0))) v3 = otio.schema.V2d(1.1, 1.9) v4 = otio.schema.V2d(3.1, 3.9) box2 = otio.schema.Box2d(v3, v4) self.assertTrue(box1.intersects(box2)) v5 = otio.schema.V2d(3.1, 4.1) v6 = otio.schema.V2d(4.1, 5.1) box3 = otio.schema.Box2d(v5, v6) self.assertFalse(box1.intersects(box3)) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_core_utils.py0000664000175000017500000002003515110656141017332 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import copy import unittest import opentimelineio._otio import opentimelineio.core._core_utils class AnyDictionaryTests(unittest.TestCase): def test_main(self): d = opentimelineio.core._core_utils.AnyDictionary() d['a'] = 1 self.assertTrue('a' in d) self.assertFalse('asdasdasd' in d) self.assertEqual(len(d), 1) self.assertEqual(d['a'], 1) # New key with self.assertRaisesRegex(KeyError, "'non-existent'"): d['non-existent'] # TODO: Test different type of values to exercise the any_to_py function? d['a'] = 'newvalue' self.assertEqual(d['a'], 'newvalue') self.assertTrue('a' in d) # Test __contains__ self.assertFalse('b' in d) with self.assertRaises(TypeError): d[1] # AnyDictionary.__getitem__ only supports strings del d['a'] self.assertEqual(dict(d), {}) with self.assertRaisesRegex(KeyError, "'non-existent'"): del d['non-existent'] for key in iter(d): # Test AnyDictionaryProxy.Iterator.iter self.assertTrue(key) class CustomClass(object): pass with self.assertRaises(TypeError): d['custom'] = CustomClass() with self.assertRaises(ValueError): # Integer bigger than C++ int64_t can accept. d['super big int'] = 9223372036854775808 with self.assertRaises(ValueError): # Integer smaller than C++ int64_t can accept. d['super big int'] = -9223372036854775809 def test_raise_on_mutation_during_iter(self): d = opentimelineio.core._core_utils.AnyDictionary() d['a'] = 'test' d['b'] = 'asdasda' with self.assertRaisesRegex(ValueError, "container mutated during iteration"): for key in d: del d['b'] def test_raises_if_ref_destroyed(self): d1 = opentimelineio.core._core_utils.AnyDictionary() opentimelineio._otio._testing.test_AnyDictionary_destroy(d1) with self.assertRaisesRegex(ValueError, r"Underlying C\+\+ AnyDictionary has been destroyed"): # noqa d1['asd'] d2 = opentimelineio.core._core_utils.AnyDictionary() opentimelineio._otio._testing.test_AnyDictionary_destroy(d2) with self.assertRaisesRegex(ValueError, r"Underlying C\+\+ AnyDictionary has been destroyed"): # noqa d2['asd'] = 'asd' d3 = opentimelineio.core._core_utils.AnyDictionary() opentimelineio._otio._testing.test_AnyDictionary_destroy(d3) with self.assertRaisesRegex(ValueError, r"Underlying C\+\+ AnyDictionary has been destroyed"): # noqa del d3['asd'] d4 = opentimelineio.core._core_utils.AnyDictionary() d4['asd'] = 1 it = iter(d4) opentimelineio._otio._testing.test_AnyDictionary_destroy(d4) with self.assertRaisesRegex(ValueError, r"Underlying C\+\+ AnyDictionary has been destroyed"): # noqa next(it) class AnyVectorTests(unittest.TestCase): def test_main(self): v = opentimelineio.core._core_utils.AnyVector() with self.assertRaises(IndexError): del v[0] # There is a special case in the C++ code for empty vector v.append(1) self.assertEqual(len(v), 1) v.append(2) self.assertEqual(len(v), 2) self.assertEqual([value for value in v], [1, 2]) v.insert(0, 5) self.assertEqual([value for value in v], [5, 1, 2]) self.assertEqual(v[0], 5) self.assertEqual(v[-3], 5) with self.assertRaises(IndexError): v[100] with self.assertRaises(IndexError): v[-100] v[-1] = 100 self.assertEqual(v[2], 100) with self.assertRaises(IndexError): v[-4] = -1 with self.assertRaises(IndexError): v[100] = 100 del v[0] self.assertEqual(len(v), 2) # Doesn't work... # assert v == [1, 100] self.assertEqual([value for value in v], [1, 100]) del v[1000] # This will surprisingly delete the last item... self.assertEqual(len(v), 1) self.assertEqual([value for value in v], [1]) # Will delete the last item even if the index doesn't match. # It's a surprising behavior. # This is caused by size_t(index) del v[-1000] v.extend([1, '234', {}]) items = [] for value in iter(v): # Test AnyVector.Iterator.iter items.append(value) self.assertEqual(items, [1, '234', {}]) self.assertFalse(v == [1, '234', {}]) # __eq__ is not implemented self.assertTrue(1 in v) # Test __contains__ self.assertTrue('234' in v) self.assertTrue({} in v) self.assertFalse(5 in v) self.assertEqual(list(reversed(v)), [{}, '234', 1]) self.assertEqual(v.index('234'), 1) v += [1, 2] self.assertEqual(v.count(1), 2) self.assertEqual(v + ['new'], [1, '234', {}, 1, 2, 'new']) # __add__ self.assertEqual(['new'] + v, [1, '234', {}, 1, 2, 'new']) # __radd__ self.assertEqual(v + ('new',), [1, '234', {}, 1, 2, 'new']) # noqa __add__ with non list type v2 = opentimelineio.core._core_utils.AnyVector() v2.append('v2') self.assertEqual(v + v2, [1, '234', {}, 1, 2, 'v2']) # __add__ with AnyVector with self.assertRaises(TypeError): v + 'asd' # __add__ invalid type self.assertEqual(str(v), "[1, '234', {}, 1, 2]") self.assertEqual(repr(v), "[1, '234', {}, 1, 2]") v3 = opentimelineio.core._core_utils.AnyVector() v3.extend(range(10)) self.assertEqual(v3[2:], [2, 3, 4, 5, 6, 7, 8, 9]) self.assertEqual(v3[4:8], [4, 5, 6, 7]) self.assertEqual(v3[1:7:2], [1, 3, 5]) del v3[2:7] self.assertEqual(list(v3), [0, 1, 7, 8, 9]) v4 = opentimelineio.core._core_utils.AnyVector() v4.extend(range(10)) del v4[::2] self.assertEqual(list(v4), [1, 3, 5, 7, 9]) v5 = opentimelineio.core._core_utils.AnyVector() tmplist = [1, 2] v5.append(tmplist) # If AnyVector was a pure list, this would fail. But it's not a real list. # Appending copies data, completely removing references to it. self.assertIsNot(v5[0], tmplist) def test_raises_if_ref_destroyed(self): v1 = opentimelineio.core._core_utils.AnyVector() opentimelineio._otio._testing.test_AnyVector_destroy(v1) with self.assertRaisesRegex(ValueError, r"Underlying C\+\+ AnyVector object has been destroyed"): # noqa v1[0] v2 = opentimelineio.core._core_utils.AnyVector() opentimelineio._otio._testing.test_AnyVector_destroy(v2) with self.assertRaisesRegex(ValueError, r"Underlying C\+\+ AnyVector object has been destroyed"): # noqa v2[0] = 1 v3 = opentimelineio.core._core_utils.AnyVector() opentimelineio._otio._testing.test_AnyVector_destroy(v3) with self.assertRaisesRegex(ValueError, r"Underlying C\+\+ AnyVector object has been destroyed"): # noqa del v3[0] v4 = opentimelineio.core._core_utils.AnyVector() v4.append(1) it = iter(v4) opentimelineio._otio._testing.test_AnyVector_destroy(v4) with self.assertRaisesRegex(ValueError, r"Underlying C\+\+ AnyVector object has been destroyed"): # noqa next(it) def test_copy(self): list1 = [1, 2, [3, 4], 5] copied = copy.copy(list1) self.assertEqual(list(list1), list(copied)) v = opentimelineio.core._core_utils.AnyVector() v.extend([1, 2, [3, 4], 5]) copied = copy.copy(v) self.assertIsNot(v, copied) # AnyVector can only deep copy. So it's __copy__ # does a deepcopy. self.assertIsNot(v[2], copied[2]) deepcopied = copy.deepcopy(v) self.assertIsNot(v, deepcopied) self.assertIsNot(v[2], deepcopied[2]) opentimelineio-0.18.1/tests/test_composable.py0000664000175000017500000000362715110656141017316 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test harness for Composable.""" import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class ComposableTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_constructor(self): seqi = otio.core.Composable( name="test", metadata={"foo": "bar"} ) self.assertEqual(seqi.name, "test") self.assertEqual(seqi.metadata, {'foo': 'bar'}) def test_serialize(self): b = otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(16.0, 9.0)) seqi = otio.core.Composable( name="test", metadata={"box": b} ) encoded = otio.adapters.otio_json.write_to_string(seqi) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(seqi, decoded) def test_stringify(self): seqi = otio.core.Composable() self.assertMultiLineEqual( str(seqi), "Composable(" "{}, " "{}" ")".format( str(seqi.name), str(seqi.metadata), ) ) self.assertMultiLineEqual( repr(seqi), "otio.core.Composable(" "name={}, " "metadata={}" ")".format( repr(seqi.name), repr(seqi.metadata), ) ) def test_metadata(self): seqi = otio.core.Composable() seqi.metadata["foo"] = "bar" encoded = otio.adapters.otio_json.write_to_string(seqi) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(seqi, decoded) self.assertEqual(decoded.metadata["foo"], seqi.metadata["foo"]) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/baseline_reader.py0000775000175000017500000000233615110656141017236 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Utilities for reading baseline json.""" import os import json MODPATH = os.path.dirname(__file__) def test_hook(dct): if "FROM_TEST_FILE" in dct: # fetch the baseline result = json_from_file_as_string( os.path.join( MODPATH, "baselines", str(dct["FROM_TEST_FILE"]) ) ) # allow you to overlay values onto the baseline in the test, if they # store non-default for the baseline values. del dct["FROM_TEST_FILE"] result.update(dct) return result return dct def json_from_string(jsonstr): return json.loads(jsonstr, object_hook=test_hook) def json_from_file_as_string(fpath): with open(fpath) as fo: return json_from_string(fo.read()) def path_to_baseline_directory(): return os.path.join(MODPATH, "baselines") def path_to_baseline(name): return os.path.join(path_to_baseline_directory(), f"{name}.json") def json_baseline(name): return json_from_file_as_string(path_to_baseline(name)) def json_baseline_as_string(name): return json.dumps(json_baseline(name)) opentimelineio-0.18.1/tests/test_composition.py0000775000175000017500000021577215110656141017546 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import os import copy import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") TRANSITION_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "transition_test.otio") class CompositionTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): it = otio.core.Item() co = otio.core.Composition(name="test", children=[it]) self.assertEqual(co.name, "test") self.assertEqual(list(co), [it]) self.assertEqual(co.composition_kind, "Composition") def test_iterable(self): it = otio.core.Item() co = otio.core.Composition(children=[it]) self.assertIs(co[0], it) self.assertEqual([i for i in co], [it]) self.assertEqual(len(co), 1) self.assertEqual(list(co.find_children()), [it]) self.assertEqual( list(co.find_children(descended_from_type=otio.schema.Clip)), [] ) def test_equality(self): co0 = otio.core.Composition() co00 = otio.core.Composition() self.assertIsOTIOEquivalentTo(co0, co00) a = otio.core.Item(name="A") b = otio.core.Item(name="B") c = otio.core.Item(name="C") co1 = otio.core.Composition(children=[a, b, c]) x = otio.core.Item(name="X") y = otio.core.Item(name="Y") z = otio.core.Item(name="Z") co2 = otio.core.Composition(children=[x, y, z]) a2 = otio.core.Item(name="A") b2 = otio.core.Item(name="B") c2 = otio.core.Item(name="C") co3 = otio.core.Composition(children=[a2, b2, c2]) self.assertTrue(co1 is not co2) self.assertNotEqual(co1, co2) self.assertTrue(co1 is not co3) self.assertIsOTIOEquivalentTo(co1, co3) def test_truthiness(self): # An empty Composition is False (since it behaves like a list) o = otio.core.Composition() self.assertFalse(o) # A Composition with anything in it is True o.append(otio.core.Composition()) self.assertTrue(o) def test_replacing_children(self): a = otio.core.Item(name="A") b = otio.core.Item(name="B") c = otio.core.Item(name="C") co = otio.core.Composition(children=[a, b]) self.assertEqual(2, len(co)) self.assertIs(co[0], a) self.assertIs(co[1], b) del co[1] self.assertEqual(1, len(co)) self.assertIs(co[0], a) co[0] = c self.assertEqual(1, len(co)) self.assertIs(co[0], c) co[:] = [a, b] self.assertEqual(2, len(co)) self.assertIs(co[0], a) self.assertIs(co[1], b) co[0:2] = [c] self.assertEqual(1, len(co)) self.assertIs(co[0], c) co[:] = [c, b, a] self.assertEqual(3, len(co)) self.assertIs(co[0], c) self.assertIs(co[1], b) self.assertIs(co[2], a) def test_is_parent_of(self): co = otio.core.Composition() co_2 = otio.core.Composition() self.assertFalse(co.is_parent_of(co_2)) co.append(co_2) self.assertTrue(co.is_parent_of(co_2)) def test_parent_manip(self): it = otio.core.Item() co = otio.core.Composition(children=[it]) self.assertIs(it.parent(), co) def test_move_child(self): it = otio.core.Item() co = otio.core.Composition(children=[it]) self.assertIs(it.parent(), co) co2 = otio.core.Composition() with self.assertRaises(ValueError): co2.append(it) del co[0] co2.append(it) self.assertIs(it.parent(), co2) def test_find_children_recursion(self): tl = otio.schema.Timeline(name="TL") tr1 = otio.schema.Track(name="tr1") tl.tracks.append(tr1) c1 = otio.schema.Clip(name="c1") tr1.append(c1) c2 = otio.schema.Clip(name="c2") tr1.append(c2) c3 = otio.schema.Clip(name="c3") tr1.append(c3) tr2 = otio.schema.Track(name="tr2") tl.tracks.append(tr2) c4 = otio.schema.Clip(name="c4") tr2.append(c4) c5 = otio.schema.Clip(name="c5") tr2.append(c5) st = otio.schema.Stack(name="st") tr2.append(st) c6 = otio.schema.Clip(name="c6") st.append(c6) tr3 = otio.schema.Track(name="tr3") c7 = otio.schema.Clip(name="c7") tr3.append(c7) c8 = otio.schema.Clip(name="c8") tr3.append(c8) st.append(tr3) self.assertEqual(2, len(tl.tracks)) self.assertEqual(3, len(tr1)) self.assertEqual(3, len(tr2)) self.assertEqual(2, len(st)) self.assertEqual(2, len(tr3)) clips = list(tl.find_clips()) self.assertListEqual( [c1, c2, c3, c4, c5, c6, c7, c8], clips ) all_tracks = list(tl.find_children( descended_from_type=otio.schema.Track )) self.assertListEqual( [tr1, tr2, tr3], all_tracks ) all_stacks = list(tl.find_children( descended_from_type=otio.schema.Stack )) self.assertListEqual( [st], all_stacks ) all_children = list(tl.find_children()) self.assertListEqual( [tr1, c1, c2, c3, tr2, c4, c5, st, c6, tr3, c7, c8], all_children ) def test_find_children_options(self): tl = otio.schema.Timeline(name="tl") tr = otio.schema.Track(name="tr") tl.tracks.append(tr) c1 = otio.schema.Clip( name="c1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=0, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ) tr.append(c1) c2 = otio.schema.Clip( name="c2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=0, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ) tr.append(c2) st = otio.schema.Stack( name="st", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=0, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ) tr.append(st) c3 = otio.schema.Clip( name="c3", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=0, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ) st.append(c3) # Test the search range self.assertListEqual( [c1], list( tr.find_children( search_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=0, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ) ) ) self.assertListEqual( [c2], list( tr.find_children( search_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=50, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ) ) ) self.assertListEqual( [c1, c2], list( tr.find_children( search_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=0, rate=24), duration=otio.opentime.RationalTime(value=100, rate=24) ) ) ) ) self.assertListEqual( [c1, c2, st, c3], list( tr.find_children( search_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=25, rate=24), duration=otio.opentime.RationalTime(value=100, rate=24) ) ) ) ) # Test descended from type self.assertListEqual( [c1, c2, c3], list(tl.find_children(descended_from_type=otio.schema.Clip)) ) self.assertListEqual( [st], list(tl.find_children(descended_from_type=otio.schema.Stack)) ) # Test shallow search self.assertListEqual( [c1, c2], list( tr.find_children( descended_from_type=otio.schema.Clip, shallow_search=True ) ) ) def test_remove_actually_removes(self): """Test that removed item is no longer 'in' composition.""" tr = otio.schema.Track() cl = otio.schema.Clip() # test inclusion tr.append(cl) self.assertIn(cl, tr) # delete by index del tr[0] self.assertNotIn(cl, tr) # delete by slice tr = otio.schema.Track() tr.append(cl) del tr[:] self.assertNotIn(cl, tr) # delete by setting over item tr = otio.schema.Track() tr.append(cl) cl2 = otio.schema.Clip() tr[0] = cl2 self.assertNotIn(cl, tr) # delete by pop tr = otio.schema.Track() tr.insert(0, cl) tr.pop() self.assertNotIn(cl, tr) def test_insert_slice(self): """Test that inserting by slice actually correctly inserts""" st = otio.schema.Stack() cl = otio.schema.Clip() st[:] = [cl] self.assertEqual(cl, st[0]) del st[0] self.assertEqual(len(st), 0) # again, this time deleting with a slice as well. st[:] = [cl] self.assertEqual(cl, st[0]) del st[:] self.assertEqual(len(st), 0) st = otio.schema.Stack() cl = otio.schema.Clip() cl2 = otio.schema.Clip() st[:] = [cl] st[:] = [cl2] self.assertNotIn(cl, st) self.assertIn(cl2, st) def test_has_clip(self): st = otio.schema.Stack(name="ST") tr1 = otio.schema.Track(name="tr1") st.append(tr1) self.assertFalse(st.has_clips()) self.assertFalse(tr1.has_clips()) c1 = otio.schema.Clip(name="c1") tr1.append(c1) self.assertTrue(st.has_clips()) self.assertTrue(tr1.has_clips()) tr2 = otio.schema.Track(name="tr2") st.append(tr2) self.assertTrue(st.has_clips()) self.assertTrue(tr1.has_clips()) self.assertFalse(tr2.has_clips()) g1 = otio.schema.Gap(name="g1") tr2.append(g1) self.assertTrue(st.has_clips()) self.assertTrue(tr1.has_clips()) self.assertFalse(tr2.has_clips()) c2 = otio.schema.Clip(name="c2") tr2.append(c2) self.assertTrue(st.has_clips()) self.assertTrue(tr1.has_clips()) self.assertTrue(tr2.has_clips()) class StackTest(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): st = otio.schema.Stack(name="test") self.assertEqual(st.name, "test") def test_serialize(self): st = otio.schema.Stack( name="test", children=[otio.schema.Clip(name="testClip")] ) encoded = otio.adapters.otio_json.write_to_string(st) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(st, decoded) self.assertIsNotNone(decoded[0].parent()) def test_str(self): st = otio.schema.Stack(name="foo", children=[]) self.assertMultiLineEqual( str(st), "Stack(" + str(st.name) + ", " + str(list(st)) + ", " + str(st.source_range) + ", " + str(st.metadata) + ")" ) def test_repr(self): st = otio.schema.Stack(name="foo", children=[]) self.assertMultiLineEqual( repr(st), "otio.schema.Stack(" + "name=" + repr(st.name) + ", " + "children=" + repr(list(st)) + ", " + "source_range=" + repr(st.source_range) + ", " + "color=None, " "metadata=" + repr(st.metadata) + ")" ) def test_trim_child_range(self): for st in [ otio.schema.Track(name="foo"), otio.schema.Stack(name="foo") ]: st.source_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=100, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) r = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=110, rate=24), duration=otio.opentime.RationalTime(value=30, rate=24) ) self.assertEqual(st.trim_child_range(r), r) r = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=0, rate=24), duration=otio.opentime.RationalTime(value=30, rate=24) ) self.assertEqual(st.trim_child_range(r), None) r = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=1000, rate=24), duration=otio.opentime.RationalTime(value=30, rate=24) ) self.assertEqual(st.trim_child_range(r), None) r = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=90, rate=24), duration=otio.opentime.RationalTime(value=30, rate=24) ) self.assertEqual( st.trim_child_range(r), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=100, rate=24), duration=otio.opentime.RationalTime(value=20, rate=24) ) ) r = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=110, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) self.assertEqual( st.trim_child_range(r), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=110, rate=24), duration=otio.opentime.RationalTime(value=40, rate=24) ) ) r = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=90, rate=24), duration=otio.opentime.RationalTime(value=1000, rate=24) ) self.assertEqual( st.trim_child_range(r), st.source_range ) def test_range_of_child(self): st = otio.schema.Stack(name="foo", children=[ otio.schema.Clip( name="clip1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=100, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ), otio.schema.Clip( name="clip2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=101, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ), otio.schema.Clip( name="clip3", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(value=102, rate=24), duration=otio.opentime.RationalTime(value=50, rate=24) ) ) ]) # The Stack should be as long as the longest child self.assertEqual( st.duration(), otio.opentime.RationalTime(value=50, rate=24) ) # Stacked items should all start at time zero self.assertEqual( st.range_of_child_at_index(0).start_time, otio.opentime.RationalTime() ) self.assertEqual( st.range_of_child_at_index(1).start_time, otio.opentime.RationalTime() ) self.assertEqual( st.range_of_child_at_index(2).start_time, otio.opentime.RationalTime() ) self.assertEqual( st.range_of_child_at_index(0).duration, otio.opentime.RationalTime(value=50, rate=24) ) self.assertEqual( st.range_of_child_at_index(1).duration, otio.opentime.RationalTime(value=50, rate=24) ) self.assertEqual( st.range_of_child_at_index(2).duration, otio.opentime.RationalTime(value=50, rate=24) ) self.assertEqual( st.range_of_child_at_index(2), st.range_of_child(st[2]) ) def test_range_of_child_with_duration(self): st_sourcerange = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(5, 24), duration=otio.opentime.RationalTime(5, 24), ) st = otio.schema.Stack( name="foo", children=[ otio.schema.Clip( name="clip1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=101, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip3", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=102, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ) ], source_range=st_sourcerange ) # range always returns the pre-trimmed range. To get the post-trim # range, call .trimmed_range() self.assertEqual( # get the pre-trimmed range in the reference space of the parent st.range_of_child(st[0], reference_space=st), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(50, 24) ) ) self.assertEqual( st.transformed_time(otio.opentime.RationalTime(25, 24), st[0]), otio.opentime.RationalTime(125, 24) ) self.assertEqual( (st[0]).transformed_time(otio.opentime.RationalTime(125, 24), st), otio.opentime.RationalTime(25, 24) ) # # in the space of the child # self.assertEqual( # # get the pre-trimmed range in the reference space of the parent # st.range_of_child(st[0], reference_space=st[0]), # otio.opentime.TimeRange( # start_time=otio.opentime.RationalTime(105,24), # duration=otio.opentime.RationalTime(5,24) # ) # ) # trimmed_ functions take into account the source_range self.assertEqual( st.trimmed_range_of_child_at_index(0), st.source_range ) self.assertEqual( st.trimmed_range_of_child(st[0], reference_space=st), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(5, 24), duration=otio.opentime.RationalTime(5, 24) ) ) # get the trimmed range in the parent self.assertEqual( st[0].trimmed_range_in_parent(), st.trimmed_range_of_child(st[0], reference_space=st), ) # same test but via iteration for i, c in enumerate(st): self.assertEqual( st[i].trimmed_range_in_parent(), st.trimmed_range_of_child(st[i], reference_space=st), ) with self.assertRaises(otio.exceptions.NotAChildError): otio.schema.Clip().trimmed_range_in_parent() def test_transformed_time(self): st = otio.schema.Stack( name="foo", children=[ otio.schema.Clip( name="clip1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=101, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip3", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=102, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ) ], source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(5, 24), duration=otio.opentime.RationalTime(5, 24), ) ) clip1 = st[0] clip2 = st[1] clip3 = st[2] self.assertEqual(clip1.name, "clip1") self.assertEqual(clip2.name, "clip2") self.assertEqual(clip3.name, "clip3") test_time = otio.opentime.RationalTime(0, 24) self.assertEqual( st.transformed_time(test_time, clip1), otio.opentime.RationalTime(100, 24) ) # ensure that transformed_time does not edit in place self.assertEqual(test_time, otio.opentime.RationalTime(0, 24)) self.assertEqual( st.transformed_time(otio.opentime.RationalTime(0, 24), clip2), otio.opentime.RationalTime(101, 24) ) self.assertEqual( st.transformed_time(otio.opentime.RationalTime(0, 24), clip3), otio.opentime.RationalTime(102, 24) ) self.assertEqual( st.transformed_time(otio.opentime.RationalTime(50, 24), clip1), otio.opentime.RationalTime(150, 24) ) self.assertEqual( st.transformed_time(otio.opentime.RationalTime(50, 24), clip2), otio.opentime.RationalTime(151, 24) ) self.assertEqual( st.transformed_time(otio.opentime.RationalTime(50, 24), clip3), otio.opentime.RationalTime(152, 24) ) self.assertEqual( clip1.transformed_time(otio.opentime.RationalTime(100, 24), st), otio.opentime.RationalTime(0, 24) ) self.assertEqual( clip2.transformed_time(otio.opentime.RationalTime(101, 24), st), otio.opentime.RationalTime(0, 24) ) self.assertEqual( clip3.transformed_time(otio.opentime.RationalTime(102, 24), st), otio.opentime.RationalTime(0, 24) ) self.assertEqual( clip1.transformed_time(otio.opentime.RationalTime(150, 24), st), otio.opentime.RationalTime(50, 24) ) self.assertEqual( clip2.transformed_time(otio.opentime.RationalTime(151, 24), st), otio.opentime.RationalTime(50, 24) ) self.assertEqual( clip3.transformed_time(otio.opentime.RationalTime(152, 24), st), otio.opentime.RationalTime(50, 24) ) def test_available_image_bounds_single_clip(self): st = otio.schema.Stack(name="foo", children=[ otio.schema.Gap(name="GAP1") ]) # There's noting valid, we should have no available_image_bounds self.assertEqual(st.available_image_bounds, None) clip = otio.schema.Clip( media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(1, 1), otio.schema.V2d(2, 2) ), target_url="/var/tmp/test.mov" ), name="clip1" ) # The Stack available_image_bounds should be equal to # the single clip that's in it st.append(clip) self.assertEqual(st.available_image_bounds, clip.available_image_bounds) def test_available_image_bounds_multi_clip(self): st = otio.schema.Stack(name="foo", children=[ otio.schema.Gap(name="GAP1"), otio.schema.Clip( media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(1, 1), otio.schema.V2d(2, 2) ), target_url="/var/tmp/test.mov" ), name="clip1" ), otio.schema.Gap(name="GAP2"), otio.schema.Clip( media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(2, 2), otio.schema.V2d(3, 3) ), target_url="/var/tmp/test.mov" ), name="clip2" ), otio.schema.Gap(name="GAP3"), otio.schema.Clip( media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(3, 3), otio.schema.V2d(4, 4) ), target_url="/var/tmp/test.mov" ), name="clip3" ), otio.schema.Gap(name="GAP4") ]) # The Stack available_image_bounds should cover the overlapping boxes, # the gaps should be ignored self.assertEqual(st.available_image_bounds, otio.schema.Box2d( otio.schema.V2d(1, 1), otio.schema.V2d(4, 4) ) ) def test_available_image_bounds_multi_layer(self): tr1 = otio.schema.Track(name="tr1", children=[ otio.schema.Gap(name="GAP1") ]) st = otio.schema.Stack(name="foo", children=[tr1]) self.assertEqual(st.available_image_bounds, tr1.available_image_bounds) self.assertEqual(st.available_image_bounds, None) cl1 = otio.schema.Clip( media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(0, 0), otio.schema.V2d(2, 2) ), target_url="/var/tmp/test.mov" ), name="clip1" ) tr1.append(cl1) self.assertEqual(st.available_image_bounds, cl1.available_image_bounds) self.assertEqual(st.available_image_bounds, tr1.available_image_bounds) tr2 = otio.schema.Track(name="tr2", children=[ otio.schema.Gap(name="GAP2") ]) st.append(tr2) self.assertEqual(st.available_image_bounds, cl1.available_image_bounds) self.assertEqual(st.available_image_bounds, tr1.available_image_bounds) cl2 = otio.schema.Clip( media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(1, 1), otio.schema.V2d(3, 3) ), target_url="/var/tmp/test.mov" ), name="clip2" ) tr2.append(cl2) # Each track should have available_image_bounds equal to its single clip, # but the stack available_image_bounds should use both tracks self.assertEqual(tr1.available_image_bounds, cl1.available_image_bounds) self.assertEqual(tr2.available_image_bounds, cl2.available_image_bounds) union = st.available_image_bounds self.assertEqual(union, otio.schema.Box2d( otio.schema.V2d(0, 0), otio.schema.V2d(3, 3) ) ) # Appending a track with no available_image_bounds should do nothing st.append(otio.schema.Track()) union2 = st.available_image_bounds self.assertEqual(union, union2) class TrackTest(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_serialize(self): sq = otio.schema.Track(name="foo", children=[]) encoded = otio.adapters.otio_json.write_to_string(sq) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(sq, decoded) def test_str(self): sq = otio.schema.Track(name="foo", children=[]) self.assertMultiLineEqual( str(sq), "Track(" + str(sq.name) + ", " + str(list(sq)) + ", " + str(sq.source_range) + ", " + str(sq.metadata) + ")" ) def test_repr(self): sq = otio.schema.Track(name="foo", children=[]) self.assertMultiLineEqual( repr(sq), "otio.schema.Track(" + "name=" + repr(sq.name) + ", " + "children=" + repr(list(sq)) + ", " + "source_range=" + repr(sq.source_range) + ", " + "color=None, " + "metadata=" + repr(sq.metadata) + ")" ) def test_instancing(self): length = otio.opentime.RationalTime(5, 1) tr = otio.opentime.TimeRange(otio.opentime.RationalTime(), length) it = otio.core.Item(source_range=tr) sq = otio.schema.Track(children=[it]) self.assertEqual(sq.range_of_child_at_index(0), tr) # Can't put item on a composition if it's already in one with self.assertRaises(ValueError): otio.schema.Track(children=[it]) # Instancing is not allowed with self.assertRaises(ValueError): otio.schema.Track(children=[it, it, it]) # inserting duplicates should raise and have no # side effects self.assertEqual(len(sq), 1) with self.assertRaises(ValueError): sq.append(it) self.assertEqual(len(sq), 1) self.assertEqual(len(sq), 1) with self.assertRaises(ValueError): sq[:] = [it, it] self.assertEqual(len(sq), 1) self.assertEqual(len(sq), 1) with self.assertRaises(ValueError): sq.insert(1, it) self.assertEqual(len(sq), 1) sq[0] = it self.assertEqual(len(sq), 1) sq[:] = [it] self.assertEqual(len(sq), 1) sq.append(copy.deepcopy(it)) self.assertEqual(len(sq), 2) with self.assertRaises(ValueError): sq[1:] = [it, copy.deepcopy(it)] self.assertEqual(len(sq), 2) def test_delete_parent_container(self): # deleting the parent container should null out the parent pointer it = otio.core.Item() sq = otio.schema.Track(children=[it]) del sq self.assertIsNone(it.parent()) def test_transactional(self): item = otio.core.Item() trackA = otio.core.Track() trackB = otio.core.Track() trackA.extend([item.clone(), item.clone(), item.clone()]) self.assertEqual(len(trackA), 3) trackB.extend([item.clone(), item.clone(), item.clone()]) self.assertEqual(len(trackB), 3) cached_contents = list(trackA) with self.assertRaises(ValueError): trackA[1:] = [ item.clone(), item.clone(), item.clone(), item.clone(), trackB[0] ] self.assertEqual(len(trackA), 3) with self.assertRaises(ValueError): trackA[-1:] = [item.clone(), item.clone(), trackB[0]] self.assertEqual(len(trackA), 3) self.assertEqual(cached_contents, list(trackA)) def test_range(self): length = otio.opentime.RationalTime(5, 1) tr = otio.opentime.TimeRange(otio.opentime.RationalTime(), length) it = otio.core.Item(source_range=tr) sq = otio.schema.Track(children=[it]) self.assertEqual(sq.range_of_child_at_index(0), tr) # It is an error to add an item to composition if it is already in # another composition. This clears out the old test composition # (and also clears out its parent pointers). del sq[0] sq = otio.schema.Track( children=[it, it.clone(), it.clone(), it.clone()], ) self.assertEqual( sq.range_of_child_at_index(index=1), otio.opentime.TimeRange( otio.opentime.RationalTime(5, 1), otio.opentime.RationalTime(5, 1) ) ) self.assertEqual( sq.range_of_child_at_index(index=0), otio.opentime.TimeRange( otio.opentime.RationalTime(0, 1), otio.opentime.RationalTime(5, 1) ) ) self.assertEqual( sq.range_of_child_at_index(index=-1), otio.opentime.TimeRange( otio.opentime.RationalTime(15, 1), otio.opentime.RationalTime(5, 1) ) ) with self.assertRaises(IndexError): sq.range_of_child_at_index(index=11) self.assertEqual(sq.duration(), length + length + length + length) # add a transition to either side in_offset = otio.opentime.RationalTime(10, 24) out_offset = otio.opentime.RationalTime(12, 24) range_of_item = sq.range_of_child_at_index(index=3) trx = otio.schema.Transition() trx.in_offset = in_offset trx.out_offset = out_offset sq.insert(0, copy.deepcopy(trx)) sq.insert(3, copy.deepcopy(trx)) sq.append(copy.deepcopy(trx)) # range of Transition self.assertEqual( sq.range_of_child_at_index(index=3), otio.opentime.TimeRange( otio.opentime.RationalTime(230, 24), otio.opentime.RationalTime(22, 24) ) ) self.assertEqual( sq.range_of_child_at_index(index=-1), otio.opentime.TimeRange( otio.opentime.RationalTime(470, 24), otio.opentime.RationalTime(22, 24) ) ) # range of Item is not altered by insertion of transitions self.assertEqual( sq.range_of_child_at_index(index=5), range_of_item ) # in_offset and out_offset for the beginning and ending self.assertEqual( sq.duration(), in_offset + length + length + length + length + out_offset ) def test_range_of_child(self): sq = otio.schema.Track( name="foo", children=[ otio.schema.Clip( name="clip1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=101, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip3", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=102, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ) ] ) # The Track should be as long as the children summed up self.assertEqual( sq.duration(), otio.opentime.RationalTime(value=150, rate=24) ) # @TODO: should include time transforms # Sequenced items should all land end-to-end self.assertEqual( sq.range_of_child_at_index(0).start_time, otio.opentime.RationalTime() ) self.assertEqual( sq.range_of_child_at_index(1).start_time, otio.opentime.RationalTime(value=50, rate=24) ) self.assertEqual( sq.range_of_child_at_index(2).start_time, otio.opentime.RationalTime(value=100, rate=24) ) self.assertEqual( sq.range_of_child(sq[2]), sq.range_of_child_at_index(2) ) self.assertEqual( sq.range_of_child_at_index(0).duration, otio.opentime.RationalTime(value=50, rate=24) ) self.assertEqual( sq.range_of_child_at_index(1).duration, otio.opentime.RationalTime(value=50, rate=24) ) self.assertEqual( sq.range_of_child_at_index(2).duration, otio.opentime.RationalTime(value=50, rate=24) ) # should trim 5 frames off the front, and 5 frames off the back sq_sourcerange = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(5, 24), duration=otio.opentime.RationalTime(140, 24), ) sq.source_range = sq_sourcerange self.assertEqual( sq.trimmed_range_of_child_at_index(0), otio.opentime.TimeRange( otio.opentime.RationalTime(5, 24), otio.opentime.RationalTime(45, 24) ) ) self.assertEqual( sq.trimmed_range_of_child_at_index(1), sq.range_of_child_at_index(1) ) self.assertEqual( sq.trimmed_range_of_child_at_index(2), otio.opentime.TimeRange( otio.opentime.RationalTime(100, 24), otio.opentime.RationalTime(45, 24), ) ) # get the trimmed range in the parent self.assertEqual( sq[0].trimmed_range_in_parent(), sq.trimmed_range_of_child(sq[0], reference_space=sq), ) # same tesq but via iteration for i, c in enumerate(sq): self.assertEqual( c.trimmed_range_in_parent(), sq.trimmed_range_of_child_at_index(i) ) with self.assertRaises(otio.exceptions.NotAChildError): otio.schema.Clip().trimmed_range_in_parent() def test_range_trimmed_out(self): track = otio.schema.Track( name="top_track", children=[ otio.schema.Clip( name="clip1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=101, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), ], # should trim out clip 1 source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(60, 24), duration=otio.opentime.RationalTime(10, 24) ) ) # should be trimmed out, at the moment, the sentinel for that is None with self.assertRaises(ValueError): track.trimmed_range_of_child_at_index(0) not_nothing = track.trimmed_range_of_child_at_index(1) self.assertEqual(not_nothing, track.source_range) # should trim out second clip track.source_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(10, 24) ) with self.assertRaises(ValueError): track.trimmed_range_of_child_at_index(1) with self.assertRaises(ValueError): track[1].trimmed_range_in_parent() not_nothing = track.trimmed_range_of_child_at_index(0) self.assertEqual(not_nothing, track.source_range) def test_range_nested(self): track = otio.schema.Track( name="inner", children=[ otio.schema.Clip( name="clip1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=101, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip3", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=102, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ) ] ) self.assertEqual(len(track), 3) self.assertEqual(len(track.deepcopy()), 3) # make a nested track with 3 sub-tracks, each with 3 clips outer_track = otio.schema.Track(name="outer", children=[ track.deepcopy(), track.deepcopy(), track.deepcopy() ]) # make one long track with 9 clips long_track = otio.schema.Track(name="long", children=( track.deepcopy()[:] + track.deepcopy()[:] + track.deepcopy()[:] )) # the original track's children should have been copied with self.assertRaises(otio.exceptions.NotAChildError): outer_track.range_of_child(track[1]) with self.assertRaises(otio.exceptions.NotAChildError): long_track.range_of_child(track[1]) # the nested and long tracks should be the same length self.assertEqual( outer_track.duration(), long_track.duration() ) # the 9 clips within both compositions should have the same # overall timing, even though the nesting is different. self.assertListEqual( [ outer_track.range_of_child(clip) for clip in outer_track.find_clips() ], [ long_track.range_of_child(clip) for clip in long_track.find_clips() ] ) def test_setitem(self): seq = otio.schema.Track() it = otio.schema.Clip() it_2 = otio.schema.Clip() seq.append(it) self.assertEqual(len(seq), 1) seq[0] = it_2 self.assertEqual(len(seq), 1) def test_transformed_time(self): sq = otio.schema.Track( name="foo", children=[ otio.schema.Clip( name="clip1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=101, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ), otio.schema.Clip( name="clip3", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=102, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ) ] ) fl = otio.schema.Gap( name="GAP", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=0, rate=24 ), duration=otio.opentime.RationalTime( value=50, rate=24 ) ) ) self.assertFalse(fl.visible()) clip1 = sq[0] clip2 = sq[1] clip3 = sq[2] self.assertEqual(clip1.name, "clip1") self.assertEqual(clip2.name, "clip2") self.assertEqual(clip3.name, "clip3") self.assertEqual( list( sq.find_clips( otio.opentime.TimeRange( otio.opentime.RationalTime(-1, 24) ) ) ), [] ) self.assertEqual( list( sq.find_clips( otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24) ) ) ), [clip1] ) self.assertEqual( list( sq.find_clips( otio.opentime.TimeRange( otio.opentime.RationalTime(49, 24) ) ) ), [clip1] ) self.assertEqual( list( sq.find_clips( otio.opentime.TimeRange( otio.opentime.RationalTime(50, 24) ) ) ), [clip2] ) self.assertEqual( list( sq.find_clips( otio.opentime.TimeRange( otio.opentime.RationalTime(99, 24) ) ) ), [clip2] ) self.assertEqual( list( sq.find_clips( otio.opentime.TimeRange( otio.opentime.RationalTime(100, 24) ) ) ), [clip3] ) self.assertEqual( list( sq.find_clips( otio.opentime.TimeRange( otio.opentime.RationalTime(149, 24) ) ) ), [clip3] ) self.assertEqual( list( sq.find_clips( otio.opentime.TimeRange( otio.opentime.RationalTime(150, 24) ) ) ), [] ) self.assertEqual( sq.transformed_time(otio.opentime.RationalTime(0, 24), clip1), otio.opentime.RationalTime(100, 24) ) self.assertEqual( sq.transformed_time(otio.opentime.RationalTime(0, 24), clip2), otio.opentime.RationalTime(51, 24) ) self.assertEqual( sq.transformed_time(otio.opentime.RationalTime(0, 24), clip3), otio.opentime.RationalTime(2, 24) ) self.assertEqual( sq.transformed_time(otio.opentime.RationalTime(50, 24), clip1), otio.opentime.RationalTime(150, 24) ) self.assertEqual( sq.transformed_time(otio.opentime.RationalTime(50, 24), clip2), otio.opentime.RationalTime(101, 24) ) self.assertEqual( sq.transformed_time(otio.opentime.RationalTime(50, 24), clip3), otio.opentime.RationalTime(52, 24) ) self.assertEqual( clip1.transformed_time(otio.opentime.RationalTime(100, 24), sq), otio.opentime.RationalTime(0, 24) ) self.assertEqual( clip2.transformed_time(otio.opentime.RationalTime(101, 24), sq), otio.opentime.RationalTime(50, 24) ) self.assertEqual( clip3.transformed_time(otio.opentime.RationalTime(102, 24), sq), otio.opentime.RationalTime(100, 24) ) self.assertEqual( clip1.transformed_time(otio.opentime.RationalTime(150, 24), sq), otio.opentime.RationalTime(50, 24) ) self.assertEqual( clip2.transformed_time(otio.opentime.RationalTime(151, 24), sq), otio.opentime.RationalTime(100, 24) ) self.assertEqual( clip3.transformed_time(otio.opentime.RationalTime(152, 24), sq), otio.opentime.RationalTime(150, 24) ) def test_neighbors_of_simple(self): seq = otio.schema.Track() trans = otio.schema.Transition( in_offset=otio.opentime.RationalTime(10, 24), out_offset=otio.opentime.RationalTime(10, 24) ) seq.append(trans) # neighbors of first transition neighbors = seq.neighbors_of( seq[0], otio.schema.NeighborGapPolicy.never ) self.assertEqual(neighbors, (None, None)) # test with the neighbor filling policy on neighbors = seq.neighbors_of( seq[0], otio.schema.NeighborGapPolicy.around_transitions ) fill = otio.schema.Gap( source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, trans.in_offset.rate), duration=trans.in_offset ) ) self.assertJsonEqual(neighbors, (fill, fill.clone())) def test_neighbors_of_no_expand(self): seq = otio.schema.Track() seq.append(otio.schema.Clip()) n = seq.neighbors_of(seq[0]) self.assertEqual(n, (None, None)) self.assertIs(n[0], (None)) self.assertIs(n[1], (None)) def test_neighbors_of_from_data(self): self.maxDiff = None edl_path = TRANSITION_EXAMPLE_PATH timeline = otio.adapters.read_from_file(edl_path) # track is [t, clip, t, clip, clip, t] seq = timeline.tracks[0] # neighbors of first transition neighbors = seq.neighbors_of( seq[0], otio.schema.NeighborGapPolicy.never ) self.assertEqual(neighbors, (None, seq[1])) fill = otio.schema.Gap( source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, seq[0].in_offset.rate), duration=seq[0].in_offset ) ) neighbors = seq.neighbors_of( seq[0], otio.schema.NeighborGapPolicy.around_transitions ) self.assertJsonEqual(neighbors, (fill, seq[1])) # neighbor around second transition neighbors = seq.neighbors_of( seq[2], otio.schema.NeighborGapPolicy.never ) self.assertEqual(neighbors, (seq[1], seq[3])) # no change w/ different policy neighbors = seq.neighbors_of( seq[2], otio.schema.NeighborGapPolicy.around_transitions ) self.assertEqual(neighbors, (seq[1], seq[3])) # neighbor around third transition neighbors = seq.neighbors_of( seq[5], otio.schema.NeighborGapPolicy.never ) self.assertEqual(neighbors, (seq[4], None)) fill = otio.schema.Gap( source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, seq[5].out_offset.rate), duration=seq[5].out_offset ) ) neighbors = seq.neighbors_of( seq[5], otio.schema.NeighborGapPolicy.around_transitions ) self.assertJsonEqual(neighbors, (seq[4], fill)) def test_track_range_of_all_children(self): edl_path = TRANSITION_EXAMPLE_PATH timeline = otio.adapters.read_from_file(edl_path) tr = timeline.tracks[0] mp = tr.range_of_all_children() # fetch all the valid children that should be in the map vc = list(tr.find_clips()) self.assertEqual(mp[vc[0]].start_time.value, 0) self.assertEqual(mp[vc[1]].start_time, mp[vc[0]].duration) for track in timeline.tracks: for child in track: self.assertEqual(child.range_in_parent(), mp[child]) track = otio.schema.Track() self.assertEqual(track.range_of_all_children(), {}) class EdgeCases(unittest.TestCase): def test_empty_compositions(self): timeline = otio.schema.Timeline() self.assertEqual(len(timeline.tracks), 0) self.assertEqual( timeline.tracks.duration(), otio.opentime.RationalTime(0, 24) ) def test_iterating_over_dupes(self): timeline = otio.schema.Timeline() track = otio.schema.Track() timeline.tracks.append(track) clip = otio.schema.Clip( name="Dupe", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(10, 30), duration=otio.opentime.RationalTime(15, 30) ) ) # make several identical copies for i in range(10): dupe = copy.deepcopy(clip) track.append(dupe) self.assertEqual(len(track), 10) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 30), duration=otio.opentime.RationalTime(150, 30) ), track.trimmed_range() ) # test normal iteration previous = None for item in track: self.assertEqual( track.range_of_child(item), item.range_in_parent() ) self.assertNotEqual( item.range_in_parent(), previous ) previous = item.range_in_parent() # test recursive iteration previous = None for item in track.find_clips(): self.assertEqual( track.range_of_child(item), item.range_in_parent() ) self.assertNotEqual( item.range_in_parent(), previous ) previous = item.range_in_parent() # compare to iteration by index previous = None for i, item in enumerate(track): self.assertEqual( track.range_of_child(item), track.range_of_child_at_index(i) ) self.assertEqual( track.range_of_child(item), item.range_in_parent() ) self.assertNotEqual( item.range_in_parent(), previous ) previous = item.range_in_parent() # compare recursive to iteration by index previous = None for i, item in enumerate(track.find_clips()): self.assertEqual( track.range_of_child(item), track.range_of_child_at_index(i) ) self.assertEqual( track.range_of_child(item), item.range_in_parent() ) self.assertNotEqual( item.range_in_parent(), previous ) previous = item.range_in_parent() class NestingTest(unittest.TestCase): def test_deeply_nested(self): # Take a single clip of media (frames 100-200) and nest it into a bunch # of layers # Nesting it should not shift the media at all. # At one level: # Timeline: # Stack: [0-99] # Track: [0-99] # Clip: [100-199] # Media Reference: [100-199] # here are some times in the top-level coordinate system zero = otio.opentime.RationalTime(0, 24) one = otio.opentime.RationalTime(1, 24) fifty = otio.opentime.RationalTime(50, 24) ninetynine = otio.opentime.RationalTime(99, 24) onehundred = otio.opentime.RationalTime(100, 24) top_level_range = otio.opentime.TimeRange( start_time=zero, duration=onehundred) # here are some times in the media-level coordinate system first_frame = otio.opentime.RationalTime(100, 24) middle = otio.opentime.RationalTime(150, 24) last = otio.opentime.RationalTime(199, 24) media_range = otio.opentime.TimeRange( start_time=first_frame, duration=onehundred) timeline = otio.schema.Timeline() stack = timeline.tracks track = otio.schema.Track() clip = otio.schema.Clip() media = otio.schema.MissingReference() media.available_range = media_range clip.media_reference = media track.append(clip) stack.append(track) self.assertIs(track, clip.parent()) self.assertIs(stack, track.parent()) # the clip and track should auto-size to fit the media, since we # haven't trimmed anything self.assertEqual(clip.duration(), onehundred) self.assertEqual(track.duration(), onehundred) self.assertEqual(stack.duration(), onehundred) # the ranges should match our expectations... self.assertEqual(clip.trimmed_range(), media_range) self.assertEqual(track.trimmed_range(), top_level_range) self.assertEqual(stack.trimmed_range(), top_level_range) # verify that the media is where we expect self.assertEqual(stack.transformed_time(zero, clip), first_frame) self.assertEqual(stack.transformed_time(fifty, clip), middle) self.assertEqual(stack.transformed_time(ninetynine, clip), last) def _nest(self, item): self.assertIsNotNone(item) self.assertIsNotNone(item.parent()) parent = item.parent() index = parent.index(item) wrapper = otio.schema.Stack() self.assertIs(parent[index], item) # swap out the item for the wrapper parent[index] = wrapper self.assertIs(parent[index], wrapper) self.assertIsNot(parent[index], item) self.assertIs(wrapper.parent(), parent) # now put the item inside the wrapper wrapper.append(item) self.assertIs(item.parent(), wrapper) return wrapper # now nest it many layers deeper wrappers = [] num_wrappers = 10 for i in range(num_wrappers): # print "Nesting", (i+1), "levels deep" wrapper = _nest(self, clip) wrappers.append(wrapper) # nothing should have shifted at all # print otio.adapters.otio_json.write_to_string(timeline) # the clip and track should auto-size to fit the media, since we # haven't trimmed anything self.assertEqual(clip.duration(), onehundred) self.assertEqual(track.duration(), onehundred) self.assertEqual(stack.duration(), onehundred) # the ranges should match our expectations... self.assertEqual(clip.trimmed_range(), media_range) self.assertEqual(track.trimmed_range(), top_level_range) self.assertEqual(stack.trimmed_range(), top_level_range) # verify that the media is where we expect self.assertEqual(stack.transformed_time(zero, clip), first_frame) self.assertEqual(stack.transformed_time(fifty, clip), middle) self.assertEqual(stack.transformed_time(ninetynine, clip), last) # now trim them all by one frame at each end self.assertEqual(ninetynine, ninetynine) self.assertEqual(ninetynine + one, onehundred) trim = otio.opentime.TimeRange( start_time=one, duration=(ninetynine - one) ) self.assertEqual(trim.duration, otio.opentime.RationalTime(98, 24)) for wrapper in wrappers: wrapper.source_range = trim # print otio.adapters.otio_json.write_to_string(timeline) # the clip should be the same self.assertEqual(clip.duration(), onehundred) # the parents should have shrunk by only 2 frames self.assertEqual(track.duration(), otio.opentime.RationalTime(98, 24)) self.assertEqual(stack.duration(), otio.opentime.RationalTime(98, 24)) # but the media should have shifted over by 1 one frame for each level # of nesting ten = otio.opentime.RationalTime(num_wrappers, 24) self.assertEqual( stack.transformed_time(zero, clip), first_frame + ten ) self.assertEqual(stack.transformed_time(fifty, clip), middle + ten) self.assertEqual(stack.transformed_time(ninetynine, clip), last + ten) def test_child_at_time_with_children(self): sq = otio.schema.Track( name="foo", children=[ otio.schema.Clip( name="leader", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=24 ), duration=otio.opentime.RationalTime( value=10, rate=24 ) ) ), otio.schema.Track( name="body", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=9, rate=24 ), duration=otio.opentime.RationalTime( value=12, rate=24 ) ), children=[ otio.schema.Clip( name="clip1", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=24 ), duration=otio.opentime.RationalTime( value=10, rate=24 ) ) ), otio.schema.Clip( name="clip2", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=101, rate=24 ), duration=otio.opentime.RationalTime( value=10, rate=24 ) ) ), otio.schema.Clip( name="clip3", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=102, rate=24 ), duration=otio.opentime.RationalTime( value=10, rate=24 ) ) ) ] ), otio.schema.Clip( name="credits", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=102, rate=24 ), duration=otio.opentime.RationalTime( value=10, rate=24 ) ) ) ] ) """ Looks like this: [ leader ][ body ][ credits ] 10 f 12f 10f body: (source range starts: 9f duration: 12f) [ clip1 ][ clip2 ][ clip 3] 1f 11f """ leader = sq[0] body = sq[1] credits = sq[2] clip1 = body[0] clip2 = body[1] clip3 = body[2] self.assertEqual(leader.name, "leader") self.assertEqual(body.name, "body") self.assertEqual(credits.name, "credits") self.assertEqual(clip1.name, "clip1") self.assertEqual(clip2.name, "clip2") self.assertEqual(clip3.name, "clip3") expected = [ ('leader', 100), ('leader', 101), ('leader', 102), ('leader', 103), ('leader', 104), ('leader', 105), ('leader', 106), ('leader', 107), ('leader', 108), ('leader', 109), ('clip1', 109), ('clip2', 101), ('clip2', 102), ('clip2', 103), ('clip2', 104), ('clip2', 105), ('clip2', 106), ('clip2', 107), ('clip2', 108), ('clip2', 109), ('clip2', 110), ('clip3', 102), ('credits', 102), ('credits', 103), ('credits', 104), ('credits', 105), ('credits', 106), ('credits', 107), ('credits', 108), ('credits', 109), ('credits', 110), ('credits', 111) ] for frame, expected_val in enumerate(expected): # first test child_at_time playhead = otio.opentime.RationalTime(frame, 24) item = sq.child_at_time(playhead) mediaframe = sq.transformed_time(playhead, item) measured_val = (item.name, otio.opentime.to_frames(mediaframe, 24)) self.assertEqual( measured_val, expected_val, msg="Error with Search Time: {}, expected: {}, " "got {}".format(playhead, expected_val, measured_val) ) # then test find_clips search_range = otio.opentime.TimeRange( otio.opentime.RationalTime(frame, 24), # with a 0 duration, should have the same result as above ) item = list(sq.find_clips(search_range))[0] mediaframe = sq.transformed_time(playhead, item) measured_val = (item.name, otio.opentime.to_frames(mediaframe, 24)) self.assertEqual( measured_val, expected_val, msg="Error with Search Time: {}, expected: {}, " "got {}".format(playhead, expected_val, measured_val) ) class MembershipTest(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_remove_actually_removes(self): """Test that removed item is no longer 'in' composition.""" tr = otio.schema.Track() cl = otio.schema.Clip() # test inclusion tr.append(cl) self.assertIn(cl, tr) # delete by index del tr[0] self.assertNotIn(cl, tr) # delete by slice tr = otio.schema.Track() tr.append(cl) del tr[:] self.assertNotIn(cl, tr) # delete by setting over item tr = otio.schema.Track() tr.append(cl) cl2 = otio.schema.Clip() tr[0] = cl2 self.assertNotIn(cl, tr) # delete by pop tr = otio.schema.Track() tr.insert(0, cl) tr.pop() self.assertNotIn(cl, tr) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_opentime.py0000775000175000017500000014532515110656141017017 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test Harness for the otio.opentime library.""" import opentimelineio as otio import unittest import copy class TestTime(unittest.TestCase): def test_create(self): t_val = 30.2 t = otio.opentime.RationalTime(t_val) self.assertIsNotNone(t) self.assertEqual(t.value, t_val) t_val = -30.2 t = otio.opentime.RationalTime(t_val) self.assertIsNotNone(t) self.assertEqual(t.value, t_val) t = otio.opentime.RationalTime() self.assertEqual(t.value, 0) self.assertEqual(t.rate, 1.0) def test_valid(self): t1 = otio.opentime.RationalTime(0, 0) self.assertTrue(t1.is_invalid_time()) self.assertFalse(t1.is_valid_time()) t2 = otio.opentime.RationalTime(24) self.assertTrue(t2.is_valid_time()) self.assertFalse(t2.is_invalid_time()) def test_equality(self): t1 = otio.opentime.RationalTime(30.2) self.assertEqual(t1, t1) t2 = otio.opentime.RationalTime(30.2) self.assertTrue(t1 is not t2) self.assertEqual(t1, t2) t3 = otio.opentime.RationalTime(60.4, 2.0) self.assertEqual(t1, t3) def test_inequality(self): t1 = otio.opentime.RationalTime(30.2) self.assertEqual(t1, t1) t2 = otio.opentime.RationalTime(33.2) self.assertTrue(t1 is not t2) self.assertNotEqual(t1, t2) t3 = otio.opentime.RationalTime(30.2) self.assertTrue(t1 is not t3) self.assertFalse(t1 != t3) def test_strict_equality(self): t1 = otio.opentime.RationalTime(30.2) self.assertTrue(t1.strictly_equal(t1)) t2 = otio.opentime.RationalTime(30.2) self.assertTrue(t1.strictly_equal(t2)) t3 = otio.opentime.RationalTime(60.4, 2.0) self.assertFalse(t1.strictly_equal(t3)) def test_rounding(self): t1 = otio.opentime.RationalTime(30.2) self.assertEqual(t1.floor(), otio.opentime.RationalTime(30.0)) self.assertEqual(t1.ceil(), otio.opentime.RationalTime(31.0)) self.assertEqual(t1.round(), otio.opentime.RationalTime(30.0)) t2 = otio.opentime.RationalTime(30.8) self.assertEqual(t2.floor(), otio.opentime.RationalTime(30.0)) self.assertEqual(t2.ceil(), otio.opentime.RationalTime(31.0)) self.assertEqual(t2.round(), otio.opentime.RationalTime(31.0)) def test_comparison(self): t1 = otio.opentime.RationalTime(15.2) t2 = otio.opentime.RationalTime(15.6) self.assertTrue(t1 < t2) self.assertTrue(t1 <= t2) self.assertFalse(t1 > t2) self.assertFalse(t1 >= t2) # Ensure the equality case of the comparisons works correctly t3 = otio.opentime.RationalTime(30.4, 2) self.assertTrue(t1 <= t3) self.assertTrue(t1 >= t3) self.assertTrue(t3 <= t1) self.assertTrue(t3 >= t1) # test implicit base conversion t2 = otio.opentime.RationalTime(15.6, 48) self.assertTrue(t1 > t2) self.assertTrue(t1 >= t2) self.assertFalse(t1 < t2) self.assertFalse(t1 <= t2) def test_copy(self): t1 = otio.opentime.RationalTime(18, 24) t2 = copy.copy(t1) self.assertEqual(t2, otio.opentime.RationalTime(18, 24)) def test_deepcopy(self): t1 = otio.opentime.RationalTime(18, 24) t2 = copy.deepcopy(t1) self.assertEqual(t2, otio.opentime.RationalTime(18, 24)) def test_base_conversion(self): # from a number t = otio.opentime.RationalTime(10, 24) with self.assertRaises(TypeError): t.rescaled_to("foo") self.assertEqual(t.rate, 24) t = t.rescaled_to(48) self.assertEqual(t.rate, 48) # from another RationalTime t = otio.opentime.RationalTime(10, 24) t2 = otio.opentime.RationalTime(20, 48) t = t.rescaled_to(t2) self.assertEqual(t.rate, t2.rate) def test_time_timecode_convert(self): timecode = "00:06:56:17" t = otio.opentime.from_timecode(timecode, 24) self.assertEqual(timecode, otio.opentime.to_timecode(t)) def test_negative_timecode(self): with self.assertRaises(ValueError): otio.opentime.from_timecode('-01:00:13:13', 24) def test_bogus_timecode(self): with self.assertRaises(ValueError): otio.opentime.from_timecode('pink elephants', 13) def test_time_timecode_convert_bad_rate(self): with self.assertRaises(ValueError) as exception_manager: otio.opentime.from_timecode('01:00:13:24', 24) exc_message = str(exception_manager.exception) self.assertEqual( exc_message, "Frame rate mismatch. Timecode '01:00:13:24' has frames beyond 23", ) def test_timecode_24(self): timecode = "00:00:01:00" t = otio.opentime.RationalTime(value=24, rate=24) self.assertEqual(t, otio.opentime.from_timecode(timecode, 24)) timecode = "00:01:00:00" t = otio.opentime.RationalTime(value=24 * 60, rate=24) self.assertEqual(t, otio.opentime.from_timecode(timecode, 24)) timecode = "01:00:00:00" t = otio.opentime.RationalTime(value=24 * 60 * 60, rate=24) self.assertEqual(t, otio.opentime.from_timecode(timecode, 24)) timecode = "24:00:00:00" t = otio.opentime.RationalTime(value=24 * 60 * 60 * 24, rate=24) self.assertEqual(t, otio.opentime.from_timecode(timecode, 24)) timecode = "23:59:59:23" t = otio.opentime.RationalTime(value=24 * 60 * 60 * 24 - 1, rate=24) self.assertEqual(t, otio.opentime.from_timecode(timecode, 24)) def test_plus_equals(self): sum1 = otio.opentime.RationalTime() sum2 = otio.opentime.RationalTime() for i in range(10): incr = otio.opentime.RationalTime(i + 1, 24) sum1 += incr sum2 = sum2 + incr self.assertEqual(sum1, sum2) def test_time_timecode_zero(self): t = otio.opentime.RationalTime() timecode = "00:00:00:00" self.assertEqual(timecode, otio.opentime.to_timecode(t, 24)) self.assertEqual(t, otio.opentime.from_timecode(timecode, 24)) def test_long_running_timecode_24(self): final_frame_number = 24 * 60 * 60 * 24 - 1 final_time = otio.opentime.from_frames(final_frame_number, 24) self.assertEqual( otio.opentime.to_timecode(final_time), "23:59:59:23" ) step_time = otio.opentime.RationalTime(value=1, rate=24) # fetching this test function from the c++ module directly cumulative_time = otio._opentime._testing.add_many( step_time, final_frame_number ) self.assertEqual(cumulative_time, final_time) # Adding by a non-multiple of 24 for fnum in range(1113, final_frame_number, 1113): rt = otio.opentime.from_frames(fnum, 24) tc = otio.opentime.to_timecode(rt) rt2 = otio.opentime.from_timecode(tc, 24) self.assertEqual(rt, rt2) self.assertEqual(tc, otio.opentime.to_timecode(rt2)) def test_timecode_23976_fps(self): # This should behave exactly like 24 fps ntsc_23976 = 24000 / 1001.0 timecode = "00:00:01:00" t = otio.opentime.RationalTime(value=24, rate=ntsc_23976) self.assertEqual(t, otio.opentime.from_timecode(timecode, ntsc_23976)) timecode = "00:01:00:00" t = otio.opentime.RationalTime(value=24 * 60, rate=ntsc_23976) self.assertEqual(t, otio.opentime.from_timecode(timecode, ntsc_23976)) timecode = "01:00:00:00" t = otio.opentime.RationalTime(value=24 * 60 * 60, rate=ntsc_23976) self.assertEqual(t, otio.opentime.from_timecode(timecode, ntsc_23976)) timecode = "24:00:00:00" t = otio.opentime.RationalTime(value=24 * 60 * 60 * 24, rate=ntsc_23976) self.assertEqual(t, otio.opentime.from_timecode(timecode, ntsc_23976)) timecode = "23:59:59:23" t = otio.opentime.RationalTime( value=24 * 60 * 60 * 24 - 1, rate=ntsc_23976 ) self.assertEqual( t, otio.opentime.from_timecode(timecode, ntsc_23976) ) def test_converting_negative_values_to_timecode(self): t = otio.opentime.RationalTime(value=-1, rate=25) with self.assertRaises(ValueError): otio.opentime.to_timecode(t, 25) def test_dropframe_timecode_2997fps(self): """Test drop frame in action. Focused on minute roll overs We nominal_fps 30 for frame calculation For this frame rate we drop 2 frames per minute except every 10th. Compensation is calculated like this when below 10 minutes: (fps * seconds + frames - dropframes * (minutes - 1)) Like this when not a whole 10 minute above 10 minutes: --minutes == minutes - 1 (fps * seconds + frames - dropframes * (--minutes - --minutes / 10)) And like this after that: (fps * seconds + frames - dropframes * (minutes - minutes / 10)) """ test_values = { 'first_four_frames': [ (0, '00:00:00;00'), (1, '00:00:00;01'), (2, '00:00:00;02'), (3, '00:00:00;03') ], 'first_minute_rollover': [ (30 * 59 + 29, '00:00:59;29'), (30 * 59 + 30, '00:01:00;02'), (30 * 59 + 31, '00:01:00;03'), (30 * 59 + 32, '00:01:00;04'), (30 * 59 + 33, '00:01:00;05') ], 'fift_minute': [ (30 * 299 + 29 - 2 * 4, '00:04:59;29'), (30 * 299 + 30 - 2 * 4, '00:05:00;02'), (30 * 299 + 31 - 2 * 4, '00:05:00;03'), (30 * 299 + 32 - 2 * 4, '00:05:00;04'), (30 * 299 + 33 - 2 * 4, '00:05:00;05') ], 'seventh_minute': [ (30 * 419 + 29 - 2 * 6, '00:06:59;29'), (30 * 419 + 30 - 2 * 6, '00:07:00;02'), (30 * 419 + 31 - 2 * 6, '00:07:00;03'), (30 * 419 + 32 - 2 * 6, '00:07:00;04'), (30 * 419 + 33 - 2 * 6, '00:07:00;05') ], 'tenth_minute': [ (30 * 599 + 29 - 2 * (10 - 10 // 10), '00:09:59;29'), (30 * 599 + 30 - 2 * (10 - 10 // 10), '00:10:00;00'), (30 * 599 + 31 - 2 * (10 - 10 // 10), '00:10:00;01'), (30 * 599 + 32 - 2 * (10 - 10 // 10), '00:10:00;02'), (30 * 599 + 33 - 2 * (10 - 10 // 10), '00:10:00;03') ], 'second_hour': [ (30 * 7199 + 29 - 2 * (120 - 120 // 10), '01:59:59;29'), (30 * 7199 + 30 - 2 * (120 - 120 // 10), '02:00:00;00'), (30 * 7199 + 31 - 2 * (120 - 120 // 10), '02:00:00;01'), (30 * 7199 + 32 - 2 * (120 - 120 // 10), '02:00:00;02'), (30 * 7199 + 33 - 2 * (120 - 120 // 10), '02:00:00;03') ], 'second_and_a_half_hour': [ (30 * 8999 + 29 - 2 * (150 - 150 // 10), '02:29:59;29'), (30 * 8999 + 30 - 2 * (150 - 150 // 10), '02:30:00;00'), (30 * 8999 + 31 - 2 * (150 - 150 // 10), '02:30:00;01'), (30 * 8999 + 32 - 2 * (150 - 150 // 10), '02:30:00;02'), (30 * 8999 + 33 - 2 * (150 - 150 // 10), '02:30:00;03') ], 'tenth_hour': [ (30 * 35999 + 29 - 2 * (600 - 600 // 10), '09:59:59;29'), (30 * 35999 + 30 - 2 * (600 - 600 // 10), '10:00:00;00'), (30 * 35999 + 31 - 2 * (600 - 600 // 10), '10:00:00;01'), (30 * 35999 + 32 - 2 * (600 - 600 // 10), '10:00:00;02'), (30 * 35999 + 33 - 2 * (600 - 600 // 10), '10:00:00;03') ], # Since 3 minutes < 10, we subtract 1 from 603 minutes 'tenth_hour_third minute': [ (30 * 36179 + 29 - 2 * (602 - 602 // 10), '10:02:59;29'), (30 * 36179 + 30 - 2 * (602 - 602 // 10), '10:03:00;02'), (30 * 36179 + 31 - 2 * (602 - 602 // 10), '10:03:00;03'), (30 * 36179 + 32 - 2 * (602 - 602 // 10), '10:03:00;04'), (30 * 36179 + 33 - 2 * (602 - 602 // 10), '10:03:00;05') ] } ntsc_2997 = otio.opentime.RationalTime.nearest_smpte_timecode_rate(29.97) self.assertEqual(ntsc_2997, 30000 / 1001.0) for time_key, time_values in test_values.items(): for value, tc in time_values: t = otio.opentime.RationalTime(value, ntsc_2997) self.assertEqual( tc, otio.opentime.to_timecode( t, rate=ntsc_2997, drop_frame=True ) ) t1 = otio.opentime.from_timecode(tc, rate=ntsc_2997) self.assertEqual(t, t1) def test_timecode_ntsc_2997fps(self): frames = 1084319 rate_float = (30000 / 1001.0) t = otio.opentime.RationalTime(frames, rate_float) dftc = otio.opentime.to_timecode(t, rate_float, drop_frame=True) self.assertEqual(dftc, '10:03:00;05') tc = otio.opentime.to_timecode(t, rate_float, drop_frame=False) self.assertEqual(tc, '10:02:23:29') # Detect DFTC from rate for backward compatibility with old versions tc_auto = otio.opentime.to_timecode(t, rate_float) self.assertEqual(tc_auto, '10:03:00;05') invalid_df_rate = otio.opentime.RationalTime(30, (24000 / 1001.0)) with self.assertRaises(ValueError): otio.opentime.to_timecode( invalid_df_rate, (24000 / 1001.0), drop_frame=True ) def test_timecode_infer_drop_frame(self): # These rates are all non-integer SMPTE rates. # When `to_timecode` is called without specifying # a value for `drop_frame`, it will infer that these # should be displayed as drop-frame timecode. frames = 1084319 rates = [ (29.97, '10:03:00;05'), (30000.0 / 1001.0, '10:03:00;05'), (59.94, '05:01:30;03'), (60000.0 / 1001.0, '05:01:30;03') ] for rate, timecode in rates: t = otio.opentime.RationalTime(frames, rate) self.assertEqual(t.to_timecode(rate, drop_frame=None), timecode) self.assertEqual(t.to_timecode(rate), timecode) def test_timecode_2997(self): ref_values = [ (10789, '00:05:59:19', '00:05:59;29'), (10790, '00:05:59:20', '00:06:00;02'), (17981, '00:09:59:11', '00:09:59;29'), (17982, '00:09:59:12', '00:10:00;00'), (17983, '00:09:59:13', '00:10:00;01'), (17984, '00:09:59:14', '00:10:00;02'), ] ntsc_2997 = 30000 / 1001.0 for value, tc, dftc in ref_values: t = otio.opentime.RationalTime(value, ntsc_2997) to_dftc = otio.opentime.to_timecode(t, rate=ntsc_2997, drop_frame=True) to_tc = otio.opentime.to_timecode(t, rate=ntsc_2997, drop_frame=False) to_auto_tc = otio.opentime.to_timecode(t, rate=ntsc_2997) # 29.97 should auto-detect dftc for backward compatibility self.assertEqual(to_dftc, to_auto_tc) # check calculated against reference self.assertEqual(to_dftc, dftc) self.assertEqual(tc, to_tc) # Check they convert back t1 = otio.opentime.from_timecode(to_dftc, rate=ntsc_2997) self.assertEqual(t1, t) t2 = otio.opentime.from_timecode(to_tc, rate=ntsc_2997) self.assertEqual(t2, t) def test_faulty_formatted_timecode_24(self): """ 01:00:13;23 is drop-frame timecode, which only applies for NTSC rates (24000/1001, 30000/1001, etc). Such timecodes drop frames to compensate for the NTSC rates being slightly slower than whole frame rates (eg: 24 fps). It does not make sense to use drop-frame timecodes for non-NTSC rates. This is what we're testing here. When using 24 fps for the drop-frame timecode 01:00:13;23 we should get a ValueError mapping internally to the ErrorStatus 'INVALID_RATE_FOR_DROP_FRAME_TIMECODE'. """ with self.assertRaises(ValueError): otio.opentime.from_timecode('01:00:13;23', 24) def test_faulty_time_string(self): with self.assertRaises(ValueError): otio.opentime.from_time_string("bogus", 24) def test_invalid_rate_to_timecode_functions(self): # Use a bogus rate, expecting `to_timecode` to complain t = otio.opentime.RationalTime(100, 999) with self.assertRaises(ValueError): otio.opentime.to_timecode(t, 777) with self.assertRaises(ValueError): otio.opentime.to_timecode(t) def test_time_string_24(self): time_string = "00:00:00.041667" t = otio.opentime.RationalTime(value=1.0, rate=24) time_obj = otio.opentime.from_time_string(time_string, 24) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) self.assertEqual(time_obj.rate, 24) time_string = "00:00:01" t = otio.opentime.RationalTime(value=24, rate=24) time_obj = otio.opentime.from_time_string(time_string, 24) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) time_string = "00:01:00" t = otio.opentime.RationalTime(value=24 * 60, rate=24) time_obj = otio.opentime.from_time_string(time_string, 24) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) time_string = "01:00:00" t = otio.opentime.RationalTime(value=24 * 60 * 60, rate=24) time_obj = otio.opentime.from_time_string(time_string, 24) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) time_string = "24:00:00" t = otio.opentime.RationalTime(value=24 * 60 * 60 * 24, rate=24) time_obj = otio.opentime.from_time_string(time_string, 24) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) time_string = "23:59:59.958333" t = otio.opentime.RationalTime(value=24 * 60 * 60 * 24 - 1, rate=24) time_obj = otio.opentime.from_time_string(time_string, 24) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) def test_time_string_25(self): time_string = "00:00:01" t = otio.opentime.RationalTime(value=25, rate=25) time_obj = otio.opentime.from_time_string(time_string, 25) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) time_string = "00:01:00" t = otio.opentime.RationalTime(value=25 * 60, rate=25) time_obj = otio.opentime.from_time_string(time_string, 25) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) time_string = "01:00:00" t = otio.opentime.RationalTime(value=25 * 60 * 60, rate=25) time_obj = otio.opentime.from_time_string(time_string, 25) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) time_string = "24:00:00" t = otio.opentime.RationalTime(value=25 * 60 * 60 * 24, rate=25) time_obj = otio.opentime.from_time_string(time_string, 25) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) time_string = "23:59:59.92" t = otio.opentime.RationalTime(value=25 * 60 * 60 * 24 - 2, rate=25) time_obj = otio.opentime.from_time_string(time_string, 25) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) def test_time_time_string_negative_rational_time(self): """ Negative rational time should return a valid time string with a '-' signage. (This is making it ffmpeg compatible) """ baseline_time_string = "-00:00:01.0" rt = otio.opentime.RationalTime(-24, 24) time_string = otio.opentime.to_time_string(rt) self.assertEqual(baseline_time_string, time_string) def test_time_time_string_zero(self): t = otio.opentime.RationalTime() time_string = "00:00:00.0" time_obj = otio.opentime.from_time_string(time_string, 24) self.assertEqual(time_string, otio.opentime.to_time_string(t)) self.assertTrue(t.almost_equal(time_obj, delta=0.001)) def test_to_time_string_microseconds_starts_with_zero(self): # this number has a leading 0 in the fractional part when converted to # time string (ie 27.08333) rt = otio.opentime.RationalTime(2090, 24) self.assertEqual( str(rt), str(otio.opentime.from_time_string(otio.opentime.to_time_string(rt), 24)) ) def test_long_running_time_string_24(self): final_frame_number = 24 * 60 * 60 * 24 - 1 final_time = otio.opentime.from_frames(final_frame_number, 24) self.assertEqual( otio.opentime.to_time_string(final_time), "23:59:59.958333" ) step_time = otio.opentime.RationalTime(value=1, rate=24) cumulative_time = otio._opentime._testing.add_many( step_time, final_frame_number ) self.assertTrue(cumulative_time.almost_equal(final_time, delta=0.001)) # Adding by a non-multiple of 24 for fnum in range(1113, final_frame_number, 1113): rt = otio.opentime.from_frames(fnum, 24) tc = otio.opentime.to_time_string(rt) rt2 = otio.opentime.from_time_string(tc, 24) self.assertEqual(rt, rt2) self.assertEqual(tc, otio.opentime.to_time_string(rt2)) def test_time_string_23976_fps(self): # This list is rewritten from conversion into seconds of # test_timecode_23976_fps ref_values_23976 = [ (1025, '00:00:01.708333'), (179900, '00:04:59.833333'), (180000, '00:05:00.0'), (360000, '00:10:00.0'), (720000, '00:20:00.0'), (1079300, '00:29:58.833333'), (1080000, '00:30:00.0'), (1080150, '00:30:00.25'), (1440000, '00:40:00.0'), (1800000, '00:50:00.0'), (1978750, '00:54:57.916666'), (1980000, '00:55:00.0'), (46700, '00:01:17.833333'), (225950, '00:06:16.583333'), (436400, '00:12:07.333333'), (703350, '00:19:32.25') ] for value, ts in ref_values_23976: t = otio.opentime.RationalTime(value, 600) self.assertEqual(ts, otio.opentime.to_time_string(t)) # t1 = otio.opentime.from_time_string(ts, rate=23.976) # fails due to precision issues # self.assertEqual(t, t1) def test_time_to_string(self): t = otio.opentime.RationalTime(1.0, 2.0) self.assertEqual(str(t), "RationalTime(1, 2)") self.assertEqual( repr(t), "otio.opentime.RationalTime(value=1, rate=2)" ) def test_frames_with_int_fps(self): for fps in (24, 30, 48, 60): t1 = otio.opentime.from_frames(101, fps) t2 = otio.opentime.RationalTime(101, fps) self.assertEqual(t1, t2) def test_frames_with_nonint_fps(self): for fps in (23.98, 29.97, 59.94): t1 = otio.opentime.from_frames(101, fps) t2 = otio.opentime.RationalTime(101, fps) self.assertEqual(t1, t2) def test_seconds(self): s1 = 1834 t1 = otio.opentime.from_seconds(s1) self.assertEqual(t1.value, 1834) self.assertEqual(t1.rate, 1) t1_as_seconds = otio.opentime.to_seconds(t1) self.assertEqual(t1_as_seconds, s1) self.assertAlmostEqual(float(t1.value) / t1.rate, s1) s2 = 248474.345 t2 = otio.opentime.from_seconds(s2) self.assertAlmostEqual(t2.value, s2) self.assertAlmostEqual(t2.rate, 1.0) t2_as_seconds = otio.opentime.to_seconds(t2) self.assertAlmostEqual(s2, t2_as_seconds) self.assertAlmostEqual(float(t2.value) / t2.rate, s2) v3 = 3459 r3 = 24 s3 = float(3459) / 24 t3 = otio.opentime.RationalTime(v3, r3) t4 = otio.opentime.from_seconds(s3) self.assertAlmostEqual(otio.opentime.to_seconds(t3), s3) self.assertAlmostEqual(otio.opentime.to_seconds(t4), s3) t5 = otio.opentime.from_seconds(s3).rescaled_to(r3) t6 = otio.opentime.from_seconds(s3, r3) self.assertEqual(t5, t6) self.assertEqual(t6.rate, r3) def test_duration(self): start_time = otio.opentime.from_frames(100, 24) end = otio.opentime.from_frames(200, 24) duration = otio.opentime.duration_from_start_end_time(start_time, end) self.assertEqual(duration, otio.opentime.from_frames(100, 24)) start_time = otio.opentime.from_frames(0, 1) end = otio.opentime.from_frames(200, 24) duration = otio.opentime.duration_from_start_end_time(start_time, end) self.assertEqual(duration, otio.opentime.from_frames(200, 24)) start_time = otio.opentime.from_frames(100, 24) end = otio.opentime.from_frames(200, 24) duration = otio.opentime.duration_from_start_end_time_inclusive(start_time, end) self.assertEqual(duration, otio.opentime.from_frames(101, 24)) start_time = otio.opentime.from_frames(0, 30) end = otio.opentime.from_frames(200, 24) duration = otio.opentime.duration_from_start_end_time_inclusive(start_time, end) self.assertEqual(duration, otio.opentime.from_frames(251, 30)) def test_math(self): a = otio.opentime.from_frames(100, 24) gap = otio.opentime.from_frames(50, 24) b = otio.opentime.from_frames(150, 24) self.assertEqual(b - a, gap) self.assertEqual(a + gap, b) self.assertEqual(b - gap, a) with self.assertRaises(TypeError): b + "foo" a += gap self.assertEqual(a, b) a = otio.opentime.from_frames(100, 24) step = otio.opentime.from_frames(1, 24) for _ in range(50): a += step self.assertEqual(a, otio.opentime.from_frames(150, 24)) def test_math_with_different_scales(self): a = otio.opentime.from_frames(100, 24) gap = otio.opentime.from_frames(100, 48) b = otio.opentime.from_frames(75, 12) self.assertEqual(b - a, gap.rescaled_to(24)) self.assertEqual(a + gap, b.rescaled_to(48)) gap2 = copy.copy(gap) gap2 += a self.assertEqual(gap2, a + gap) self.assertEqual(b - gap, a.rescaled_to(48)) def test_duration_from_start_end_time(self): tend = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.duration_from_start_end_time( start_time=otio.opentime.RationalTime(0, 25), end_time_exclusive=tend ) self.assertEqual(tend, tdur) def test_subtract_with_different_rates(self): t1 = otio.opentime.RationalTime(12, 10) t2 = otio.opentime.RationalTime(12, 5) self.assertEqual((t1 - t2).value, -12) def test_incomparable_floats(self): t1 = otio.opentime.RationalTime(12, 10) with self.assertRaises(TypeError): t1 < -1 def test_immutable(self): t1 = otio.opentime.RationalTime(12, 10) with self.assertRaises(AttributeError): t1.value = 12 def test_passing_ndf_tc_at_df_rate(self): DF_TC = "01:00:02;05" NDF_TC = "00:59:58:17" frames = 107957 ntsc_2997 = otio.opentime.RationalTime.nearest_smpte_timecode_rate(29.97) self.assertEqual(ntsc_2997, 30000 / 1001.0) tc1 = otio.opentime.to_timecode( otio.opentime.RationalTime(frames, ntsc_2997) ) self.assertEqual(tc1, DF_TC) tc2 = otio.opentime.to_timecode( otio.opentime.RationalTime(frames, ntsc_2997), ntsc_2997, drop_frame=False ) self.assertEqual(tc2, NDF_TC) t1 = otio.opentime.from_timecode(DF_TC, ntsc_2997) self.assertEqual(t1.value, frames) t2 = otio.opentime.from_timecode(NDF_TC, ntsc_2997) self.assertEqual(t2.value, frames) def test_nearest_smpte_timecode_rate(self): rate_pairs = ( (23.97602397602397, 24000.0 / 1001.0), (23.97, 24000.0 / 1001.0), (23.976, 24000.0 / 1001.0), (23.98, 24000.0 / 1001.0), (29.97, 30000.0 / 1001.0), (59.94, 60000.0 / 1001.0), (24.0, 24.0), (23.999999, 24.0), (29.999999, 30.0), (30.01, 30.0), (60.01, 60.0) ) for wonky_rate, smpte_rate in rate_pairs: self.assertTrue( otio.opentime.RationalTime.is_smpte_timecode_rate( smpte_rate ) ) self.assertEqual( otio.opentime.RationalTime.nearest_smpte_timecode_rate( wonky_rate ), smpte_rate, ) class TestTimeTransform(unittest.TestCase): def test_identity_transform(self): tstart = otio.opentime.RationalTime(12, 25) txform = otio.opentime.TimeTransform() self.assertEqual(tstart, txform.applied_to(tstart)) tstart = otio.opentime.RationalTime(12, 25) txform = otio.opentime.TimeTransform(rate=50) self.assertEqual(24, txform.applied_to(tstart).value) def test_offset(self): tstart = otio.opentime.RationalTime(12, 25) toffset = otio.opentime.RationalTime(10, 25) txform = otio.opentime.TimeTransform(offset=toffset) self.assertEqual(tstart + toffset, txform.applied_to(tstart)) tr = otio.opentime.TimeRange(tstart, tstart) self.assertEqual( txform.applied_to(tr), otio.opentime.TimeRange(tstart + toffset, tstart) ) def test_scale(self): tstart = otio.opentime.RationalTime(12, 25) txform = otio.opentime.TimeTransform(scale=2) self.assertEqual( otio.opentime.RationalTime(24, 25), txform.applied_to(tstart) ) tr = otio.opentime.TimeRange(tstart, tstart) tstart_scaled = otio.opentime.RationalTime(24, 25) self.assertEqual( txform.applied_to(tr), otio.opentime.TimeRange(tstart_scaled, tstart_scaled) ) def test_rate(self): txform1 = otio.opentime.TimeTransform() txform2 = otio.opentime.TimeTransform(rate=50) self.assertEqual(txform2.rate, txform1.applied_to(txform2).rate) def test_string(self): tstart = otio.opentime.RationalTime(12.0, 25.0) txform = otio.opentime.TimeTransform(offset=tstart, scale=2.0) self.assertEqual( repr(txform), "otio.opentime.TimeTransform(" "offset=otio.opentime.RationalTime(" "value=12, " "rate=25" "), " "scale=2, " "rate=-1" ")" ) self.assertEqual( str(txform), "TimeTransform(RationalTime(12, 25), 2, -1)" ) def test_comparison(self): tstart = otio.opentime.RationalTime(12, 25) txform = otio.opentime.TimeTransform(offset=tstart, scale=2) tstart = otio.opentime.RationalTime(12, 25) txform2 = otio.opentime.TimeTransform(offset=tstart, scale=2) self.assertEqual(txform, txform2) self.assertFalse(txform != txform2) tstart = otio.opentime.RationalTime(23, 25) txform3 = otio.opentime.TimeTransform(offset=tstart, scale=2) self.assertNotEqual(txform, txform3) self.assertFalse(txform == txform3) def test_copy(self): tstart = otio.opentime.RationalTime(12, 25) t1 = otio.opentime.TimeTransform(tstart) t2 = copy.copy(t1) self.assertEqual(t1, t2) self.assertIsNot(t1, t2) self.assertEqual(t1.offset, t2.offset) # TimeTransform.__copy__ acts as a deep copy self.assertIsNot(t1.offset, t2.offset) def test_deepcopy(self): tstart = otio.opentime.RationalTime(12, 25) t1 = otio.opentime.TimeTransform(tstart) t2 = copy.deepcopy(t1) self.assertEqual(t1, t2) self.assertIsNot(t1, t2) self.assertEqual(t1.offset, t2.offset) # TimeTransform.__copy__ acts as a deep copy self.assertIsNot(t1.offset, t2.offset) class TestTimeRange(unittest.TestCase): def test_create(self): tr = otio.opentime.TimeRange() blank = otio.opentime.RationalTime() self.assertEqual(tr.start_time, blank) self.assertEqual(tr.duration, blank) tr1 = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(10, 48) ) self.assertEqual(tr1.start_time.rate, tr1.duration.rate) tr2 = otio.opentime.TimeRange( duration=otio.opentime.RationalTime(10, 48) ) self.assertEqual(tr2.start_time.rate, tr2.duration.rate) tr3 = otio.opentime.TimeRange(0, 48, 24) self.assertEqual(tr3.start_time, otio.opentime.RationalTime(0, 24)) self.assertEqual(tr3.duration, otio.opentime.RationalTime(48, 24)) def test_valid(self): tr = otio.opentime.TimeRange(0, 0, 0) self.assertTrue(tr.is_invalid_range()) self.assertFalse(tr.is_valid_range()) tr2 = otio.opentime.TimeRange(0, 48, 24) self.assertTrue(tr2.is_valid_range()) self.assertFalse(tr2.is_invalid_range()) tr3 = otio.opentime.TimeRange(0, -48, 24) self.assertFalse(tr3.is_valid_range()) self.assertTrue(tr3.is_invalid_range()) def test_duration_validation(self): tr = otio.opentime.TimeRange() with self.assertRaises(AttributeError): setattr(tr, "duration", "foo") def test_extended_by(self): # base 25 is just for testing # range starts at 0 and has duration 0 tr = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 25) ) with self.assertRaises(TypeError): tr.extended_by("foo") self.assertEqual(tr.duration, otio.opentime.RationalTime()) def test_end_time(self): # test whole number duration rt_start = otio.opentime.RationalTime(1, 24) rt_dur = otio.opentime.RationalTime(5, 24) tr = otio.opentime.TimeRange(rt_start, rt_dur) self.assertEqual(tr.duration, rt_dur) self.assertEqual(tr.end_time_exclusive(), rt_start + rt_dur) self.assertEqual( tr.end_time_inclusive(), rt_start + rt_dur - otio.opentime.RationalTime(1, 24) ) # test non-integer duration value rt_dur = otio.opentime.RationalTime(5.5, 24) tr = otio.opentime.TimeRange(rt_start, rt_dur) self.assertEqual(tr.end_time_exclusive(), rt_start + rt_dur) self.assertEqual( tr.end_time_inclusive(), otio.opentime.RationalTime(6, 24) ) def test_repr(self): tr = otio.opentime.TimeRange( otio.opentime.RationalTime(-1.0, 24.0), otio.opentime.RationalTime(6.0, 24.0) ) self.assertEqual( repr(tr), "otio.opentime.TimeRange(" "start_time=otio.opentime.RationalTime(value=-1, rate=24), " "duration=otio.opentime.RationalTime(value=6, rate=24))" ) def test_compare(self): start_time1 = otio.opentime.RationalTime(18, 24) duration1 = otio.opentime.RationalTime(7, 24) tr1 = otio.opentime.TimeRange(start_time1, duration1) start_time2 = otio.opentime.RationalTime(18, 24) duration2 = otio.opentime.RationalTime(14, 48) tr2 = otio.opentime.TimeRange(start_time2, duration2) self.assertEqual(tr1, tr2) self.assertFalse(tr1 != tr2) start_time3 = otio.opentime.RationalTime(20, 24) duration3 = otio.opentime.RationalTime(3, 24) tr3 = otio.opentime.TimeRange(start_time3, duration3) self.assertNotEqual(tr1, tr3) self.assertFalse(tr1 == tr3) def test_copy(self): start_time1 = otio.opentime.RationalTime(18, 24) duration1 = otio.opentime.RationalTime(7, 24) tr1 = otio.opentime.TimeRange(start_time1, duration1) tr2 = copy.copy(tr1) self.assertEqual( tr2, otio.opentime.TimeRange( otio.opentime.RationalTime(18, 24), otio.opentime.RationalTime(7, 24), ), ) def test_deepcopy(self): start_time1 = otio.opentime.RationalTime(18, 24) duration1 = otio.opentime.RationalTime(7, 24) tr1 = otio.opentime.TimeRange(start_time1, duration1) tr2 = copy.deepcopy(tr1) self.assertEqual( tr2, otio.opentime.TimeRange( otio.opentime.RationalTime(18, 24), otio.opentime.RationalTime(7, 24), ), ) def test_clamped(self): test_point_min = otio.opentime.RationalTime(-2, 24) test_point_max = otio.opentime.RationalTime(6, 24) tr = otio.opentime.TimeRange( otio.opentime.RationalTime(-1, 24), otio.opentime.RationalTime(6, 24), ) other_tr = otio.opentime.TimeRange( otio.opentime.RationalTime(-2, 24), otio.opentime.RationalTime(7, 24), ) self.assertEqual(tr.clamped(test_point_min), tr.start_time) self.assertEqual(tr.clamped(test_point_max), tr.end_time_inclusive()) self.assertEqual(tr.clamped(other_tr), tr) def test_overlaps_garbage(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) with self.assertRaises(TypeError): tr.overlaps("foo") def test_contains(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3.3, 25) tr = otio.opentime.TimeRange(tstart, tdur) with self.assertRaises(TypeError): tr.contains("foo") self.assertTrue(tr.contains(tstart)) self.assertFalse(tr.contains(tstart + tdur)) self.assertFalse(tr.contains(tstart - tdur)) self.assertFalse(tr.contains(tr)) tr_2 = otio.opentime.TimeRange(tstart - tdur, tdur) self.assertFalse(tr.contains(tr_2)) self.assertFalse(tr_2.contains(tr)) def test_overlaps_rationaltime(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.overlaps(otio.opentime.RationalTime(13, 25))) self.assertFalse(tr.overlaps(otio.opentime.RationalTime(1, 25))) def test_overlaps_timerange(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) tstart = otio.opentime.RationalTime(0, 25) tdur = otio.opentime.RationalTime(3, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.overlaps(tr_t)) tstart = otio.opentime.RationalTime(10, 25) tdur = otio.opentime.RationalTime(3, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.overlaps(tr_t)) tstart = otio.opentime.RationalTime(13, 25) tdur = otio.opentime.RationalTime(1, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.overlaps(tr_t)) tstart = otio.opentime.RationalTime(2, 25) tdur = otio.opentime.RationalTime(30, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.overlaps(tr_t)) tstart = otio.opentime.RationalTime(2, 50) tdur = otio.opentime.RationalTime(60, 50) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.overlaps(tr_t)) tstart = otio.opentime.RationalTime(2, 50) tdur = otio.opentime.RationalTime(14, 50) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.overlaps(tr_t)) tstart = otio.opentime.RationalTime(-100, 50) tdur = otio.opentime.RationalTime(400, 50) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.overlaps(tr_t)) tstart = otio.opentime.RationalTime(100, 50) tdur = otio.opentime.RationalTime(400, 50) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.overlaps(tr_t)) def test_intersects_timerange(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) tstart = otio.opentime.RationalTime(0, 25) tdur = otio.opentime.RationalTime(3, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(10, 25) tdur = otio.opentime.RationalTime(3, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(10, 25) tdur = otio.opentime.RationalTime(2, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(14, 25) tdur = otio.opentime.RationalTime(2, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(15, 25) tdur = otio.opentime.RationalTime(2, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(13, 25) tdur = otio.opentime.RationalTime(1, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(2, 25) tdur = otio.opentime.RationalTime(30, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(2, 50) tdur = otio.opentime.RationalTime(60, 50) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(2, 50) tdur = otio.opentime.RationalTime(14, 50) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(-100, 50) tdur = otio.opentime.RationalTime(400, 50) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.intersects(tr_t)) tstart = otio.opentime.RationalTime(100, 50) tdur = otio.opentime.RationalTime(400, 50) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.intersects(tr_t)) def test_before_timerange(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) tstart = otio.opentime.RationalTime(10, 25) tdur = otio.opentime.RationalTime(1.5, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr_t.before(tr)) self.assertFalse(tr.before(tr_t)) tdur = otio.opentime.RationalTime(12, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr_t.before(tr)) self.assertFalse(tr.before(tr)) def test_before_rationaltime(self): tafter = otio.opentime.RationalTime(15, 25) tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.before(tafter)) self.assertFalse(tr.before(tstart)) tdur = otio.opentime.RationalTime(1.99, 25) tr = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.before(tafter)) def test_meets(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) tstart = otio.opentime.RationalTime(15, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.meets(tr_t)) self.assertFalse(tr_t.meets(tr)) tstart = otio.opentime.RationalTime(14.99, 25) tdur = otio.opentime.RationalTime(0, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr_t.meets(tr_t)) def test_begins_timerange(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) tdur = otio.opentime.RationalTime(5, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.begins(tr_t)) self.assertFalse(tr_t.begins(tr)) self.assertFalse(tr.begins(tr)) tdur = otio.opentime.RationalTime(0, 25) tr = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.begins(tr_t)) self.assertFalse(tr.begins(tr)) tstart = otio.opentime.RationalTime(30, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr.begins(tr_t)) tstart = otio.opentime.RationalTime(13, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) tdur = otio.opentime.RationalTime(3, 25) tstart = otio.opentime.RationalTime(12, 25) tr = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr_t.begins(tr)) def test_begins_rationaltime(self): tend = otio.opentime.RationalTime(15, 25) tbefore = otio.opentime.RationalTime(11.9, 25) tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.begins(tstart)) self.assertFalse(tr.begins(tend)) self.assertFalse(tr.begins(tbefore)) def test_finishes_timerange(self): tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) tstart = otio.opentime.RationalTime(13, 25) tdur = otio.opentime.RationalTime(2, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr_t.finishes(tr)) self.assertFalse(tr.finishes(tr_t)) self.assertFalse(tr.finishes(tr)) tdur = otio.opentime.RationalTime(1, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr_t.finishes(tr)) tstart = otio.opentime.RationalTime(30, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertFalse(tr_t.finishes(tr)) tstart = otio.opentime.RationalTime(15, 25) tdur = otio.opentime.RationalTime(0, 25) tr_t = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr_t.finishes(tr)) def test_finishes_rationaltime(self): tafter = otio.opentime.RationalTime(16, 25) tend = otio.opentime.RationalTime(15, 25) tstart = otio.opentime.RationalTime(12, 25) tdur = otio.opentime.RationalTime(3, 25) tr = otio.opentime.TimeRange(tstart, tdur) self.assertTrue(tr.finishes(tend)) self.assertFalse(tr.finishes(tstart)) self.assertFalse(tr.finishes(tafter)) def test_range_from_start_end_time(self): tstart = otio.opentime.RationalTime(0, 25) tend = otio.opentime.RationalTime(12, 25) tr = otio.opentime.range_from_start_end_time( start_time=tstart, end_time_exclusive=tend ) self.assertEqual(tr.start_time, tstart) self.assertEqual(tr.duration, tend) self.assertEqual(tr.end_time_exclusive(), tend) self.assertEqual( tr.end_time_inclusive(), tend - otio.opentime.RationalTime(1, 25) ) self.assertEqual( tr, otio.opentime.range_from_start_end_time( tr.start_time, tr.end_time_exclusive() ) ) def test_range_from_start_end_time_inclusive(self): tstart = otio.opentime.RationalTime(0, 25) tend = otio.opentime.RationalTime(12, 25) tr = otio.opentime.range_from_start_end_time_inclusive( start_time=tstart, end_time_inclusive=tend ) self.assertEqual(tr.start_time, tstart) self.assertEqual(tr.duration, otio.opentime.RationalTime(13, 25)) self.assertEqual(tr.end_time_inclusive(), tend) self.assertEqual( tr.end_time_inclusive(), otio.opentime.RationalTime(12, 25), ) self.assertEqual( tr, otio.opentime.range_from_start_end_time_inclusive( tr.start_time, tr.end_time_inclusive() ) ) def test_adjacent_timeranges(self): d1 = 0.3 d2 = 0.4 r1 = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 1), otio.opentime.RationalTime(d1, 1) ) r2 = otio.opentime.TimeRange( r1.end_time_exclusive(), otio.opentime.RationalTime(d2, 1) ) full = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 1), otio.opentime.RationalTime(d1 + d2, 1) ) self.assertFalse(r1.overlaps(r2)) self.assertEqual(r1.extended_by(r2), full) def test_distant_timeranges(self): start = 0.1 d1 = 0.3 gap = 1.7 d2 = 0.4 r1 = otio.opentime.TimeRange( otio.opentime.RationalTime(start, 1), otio.opentime.RationalTime(d1, 1) ) r2 = otio.opentime.TimeRange( otio.opentime.RationalTime(start + gap + d1, 1), otio.opentime.RationalTime(d2, 1) ) full = otio.opentime.TimeRange( otio.opentime.RationalTime(start, 1), otio.opentime.RationalTime(d1 + gap + d2, 1) ) self.assertFalse(r1.overlaps(r2)) self.assertEqual(full, r1.extended_by(r2)) self.assertEqual(full, r2.extended_by(r1)) def test_to_timecode_mixed_rates(self): timecode = "00:06:56:17" t = otio.opentime.from_timecode(timecode, 24) self.assertEqual(timecode, otio.opentime.to_timecode(t)) self.assertEqual(timecode, otio.opentime.to_timecode(t, 24)) self.assertNotEqual(timecode, otio.opentime.to_timecode(t, 48)) time1 = otio.opentime.RationalTime(24.0, 24.0) time2 = otio.opentime.RationalTime(1.0, 1.0) self.assertEqual( otio.opentime.to_timecode(time1, 24.0), otio.opentime.to_timecode(time2, 24.0) ) def test_to_frames_mixed_rates(self): frame = 100 t = otio.opentime.from_frames(frame, 24) self.assertEqual(frame, otio.opentime.to_frames(t)) self.assertEqual(frame, otio.opentime.to_frames(t, 24)) self.assertNotEqual(frame, otio.opentime.to_frames(t, 12)) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_documentation.py0000664000175000017500000001056415110656141020041 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test cases to verify examples used in the OTIO documentation.""" import os import unittest import opentimelineio as otio SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") CLIP_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "clip_example.otio") class DocTester(unittest.TestCase): def test_clip(self): timeline = otio.adapters.read_from_file(CLIP_EXAMPLE_PATH) track = timeline.tracks[0] gapA, clip, transition, gapB = track[:] self.assertEqual( otio.opentime.RationalTime(19, 24), track.duration() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(19, 24) ), track.trimmed_range() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(19, 24) ), track.available_range() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(19, 24) ), track.visible_range() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(8, 24), duration=otio.opentime.RationalTime(3, 24) ), track.trimmed_range_of_child(clip) ) self.assertEqual( ( None, otio.opentime.RationalTime(1, 24) ), track.handles_of_child(clip) ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(8, 24), duration=otio.opentime.RationalTime(3, 24) ), track.trimmed_range_of_child(clip) ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(8, 24), duration=otio.opentime.RationalTime(3, 24) ), track.range_of_child(clip) ) self.assertEqual( otio.opentime.RationalTime(8, 24), gapA.duration() ) self.assertEqual( otio.opentime.RationalTime(3, 24), clip.duration() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(8, 24), duration=otio.opentime.RationalTime(3, 24) ), clip.trimmed_range_in_parent() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(8, 24), duration=otio.opentime.RationalTime(3, 24) ), clip.range_in_parent() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(3, 24), duration=otio.opentime.RationalTime(3, 24) ), clip.trimmed_range() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(3, 24), duration=otio.opentime.RationalTime(4, 24) ), clip.visible_range() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(8, 24) ), clip.available_range() ) self.assertEqual( otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(8, 24) ), clip.media_reference.available_range ) self.assertEqual( otio.opentime.RationalTime(2, 24), transition.in_offset ) self.assertEqual( otio.opentime.RationalTime(1, 24), transition.out_offset ) self.assertEqual( otio.opentime.RationalTime(8, 24), gapB.duration() ) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_otiod.py0000664000175000017500000001421315110656141016301 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Tests for the OTIOD adapter.""" import unittest import os import tempfile import opentimelineio as otio from opentimelineio import test_utils as otio_test_utils from opentimelineio.adapters import file_bundle_utils SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") SCREENING_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "screening_example.otio") MEDIA_EXAMPLE_PATH_REL = os.path.relpath( os.path.join( SAMPLE_DATA_DIR, "OpenTimelineIO@3xDark.png" ) ) MEDIA_EXAMPLE_PATH_URL_REL = otio.url_utils.url_from_filepath( MEDIA_EXAMPLE_PATH_REL ) MEDIA_EXAMPLE_PATH_ABS = os.path.abspath( MEDIA_EXAMPLE_PATH_REL.replace( "3xDark", "3xLight" ) ) MEDIA_EXAMPLE_PATH_URL_ABS = otio.url_utils.url_from_filepath( MEDIA_EXAMPLE_PATH_ABS ) class OTIODTester(unittest.TestCase, otio_test_utils.OTIOAssertions): def setUp(self): tl = otio.adapters.read_from_file(SCREENING_EXAMPLE_PATH) # convert to contrived local reference last_rel = False for cl in tl.find_clips(): # vary the relative and absolute paths, make sure that both work next_rel = ( MEDIA_EXAMPLE_PATH_URL_REL if last_rel else MEDIA_EXAMPLE_PATH_URL_ABS ) last_rel = not last_rel cl.media_reference = otio.schema.ExternalReference( target_url=next_rel ) self.tl = tl def test_file_bundle_manifest_missing_reference(self): # all missing should be empty result_otio, manifest = ( file_bundle_utils._prepped_otio_for_bundle_and_manifest( input_otio=self.tl, media_policy=file_bundle_utils.MediaReferencePolicy.AllMissing, adapter_name="TEST_NAME", ) ) self.assertEqual(manifest, {}) for cl in result_otio.find_clips(): self.assertIsInstance( cl.media_reference, otio.schema.MissingReference, "{} is of type {}, not an instance of {}.".format( cl.media_reference, type(cl.media_reference), type(otio.schema.MissingReference) ) ) def test_file_bundle_manifest(self): result_otio, manifest = ( file_bundle_utils._prepped_otio_for_bundle_and_manifest( input_otio=self.tl, media_policy=( file_bundle_utils.MediaReferencePolicy.ErrorIfNotFile ), adapter_name="TEST_NAME", ) ) self.assertEqual(len(manifest.keys()), 2) files_in_manifest = set(manifest.keys()) known_files = { MEDIA_EXAMPLE_PATH_ABS: 5, os.path.abspath(MEDIA_EXAMPLE_PATH_REL): 4 } # should only contain absolute paths self.assertEqual(files_in_manifest, set(known_files.keys())) for fname, count in known_files.items(): self.assertEqual(len(manifest[fname]), count) def test_round_trip(self): with tempfile.NamedTemporaryFile(suffix=".otiod") as bogusfile: tmp_path = bogusfile.name otio.adapters.write_to_file(self.tl, tmp_path) self.assertTrue(os.path.exists(tmp_path)) # by default will provide relative paths result = otio.adapters.read_from_file( tmp_path, ) for cl in result.find_clips(): self.assertNotEqual( cl.media_reference.target_url, MEDIA_EXAMPLE_PATH_URL_REL ) # conform media references in input to what they should be in the output for cl in self.tl.find_clips(): # construct an absolute file path to the result cl.media_reference.target_url = ( otio.url_utils.url_from_filepath( os.path.join( otio.adapters.file_bundle_utils.BUNDLE_DIR_NAME, os.path.basename(cl.media_reference.target_url) ) ) ) self.assertJsonEqual(result, self.tl) def test_round_trip_all_missing_references(self): with tempfile.NamedTemporaryFile(suffix=".otiod") as bogusfile: tmp_path = bogusfile.name otio.adapters.write_to_file( self.tl, tmp_path, media_policy=( otio.adapters.file_bundle_utils.MediaReferencePolicy.AllMissing ) ) # ...but can be optionally told to generate absolute paths result = otio.adapters.read_from_file( tmp_path, absolute_media_reference_paths=True ) for cl in result.find_clips(): self.assertIsInstance( cl.media_reference, otio.schema.MissingReference ) def test_round_trip_absolute_paths(self): with tempfile.NamedTemporaryFile(suffix=".otiod") as bogusfile: tmp_path = bogusfile.name otio.adapters.write_to_file(self.tl, tmp_path) # ...but can be optionally told to generate absolute paths result = otio.adapters.read_from_file( tmp_path, absolute_media_reference_paths=True ) for cl in result.find_clips(): self.assertNotEqual( cl.media_reference.target_url, MEDIA_EXAMPLE_PATH_URL_REL ) # conform media references in input to what they should be in the output for cl in self.tl.find_clips(): # should be only field that changed cl.media_reference.target_url = ( otio.url_utils.url_from_filepath( os.path.join( tmp_path, otio.adapters.file_bundle_utils.BUNDLE_DIR_NAME, os.path.basename(cl.media_reference.target_url) ) ) ) self.assertJsonEqual(result, self.tl) if __name__ == "__main__": unittest.main() opentimelineio-0.18.1/tests/test_console.py0000775000175000017500000010252315110656141016632 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Unit tests for the 'console' module.""" import unittest import sys import os import subprocess import sysconfig import pathlib import platform import io from tempfile import TemporaryDirectory # noqa: F401 import tempfile import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils import opentimelineio.console as otio_console SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") MULTITRACK_PATH = os.path.join(SAMPLE_DATA_DIR, "multitrack.otio") PREMIERE_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "premiere_example.otio") SCREENING_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "screening_example.otio") SIMPLE_CUT_PATH = os.path.join(SAMPLE_DATA_DIR, "simple_cut.otio") TRANSITION_PATH = os.path.join(SAMPLE_DATA_DIR, "transition.otio") EFFECTS_PATH = os.path.join(SAMPLE_DATA_DIR, "effects.otio") def CreateShelloutTest(cl): if os.environ.get("OTIO_DISABLE_SHELLOUT_TESTS"): newSuite = None else: class newSuite(cl): SHELL_OUT = True newSuite.__name__ = cl.__name__ + "_on_shell" return newSuite class ConsoleTester(otio_test_utils.OTIOAssertions): """ Base class for running console tests both by directly calling main() and by shelling out. """ SHELL_OUT = False def setUp(self): self.saved_args = sys.argv self.old_stdout = sys.stdout self.old_stderr = sys.stderr sys.stdout = io.StringIO() sys.stderr = io.StringIO() def run_test(self): if self.SHELL_OUT: # make sure its on the path console_script = os.path.join(sysconfig.get_path('scripts'), sys.argv[0]) if platform.system() == 'Windows': console_script += '.exe' if not os.path.exists(console_script): self.fail( "Could not find '{}'. Tests that explicitly shell" " out can be disabled by setting the environment variable " "OTIO_DISABLE_SHELLOUT_TESTS.".format(console_script) ) # actually run the test (sys.argv is already populated correctly) proc = subprocess.Popen( sys.argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout, stderr = proc.communicate() # XXX 2.7 vs 3.xx bug if type(stdout) is not str: stdout = stdout.decode("utf-8") stderr = stderr.decode("utf-8") sys.stdout.write(stdout) sys.stderr.write(stderr) if proc.returncode != 0: raise SystemExit() else: self.test_module.main() # pre-fetch these strings for easy access stdout = sys.stdout.getvalue() stderr = sys.stderr.getvalue() if platform.system() == 'Windows': # Normalize line-endings for assertEqual(expected, actual) stdout = stdout.replace('\r\n', '\n') stderr = stderr.replace('\r\n', '\n') return stdout, stderr def tearDown(self): sys.stdout = self.old_stdout sys.stderr = self.old_stderr sys.argv = self.saved_args class OTIOStatTest(ConsoleTester, unittest.TestCase): test_module = otio_console.otiostat def test_basic(self): sys.argv = ['otiostat', SCREENING_EXAMPLE_PATH] self.run_test() self.assertIn("top level object: Timeline.1", sys.stdout.getvalue()) OTIOStatTest_ShellOut = CreateShelloutTest(OTIOStatTest) class OTIOCatTests(ConsoleTester, unittest.TestCase): test_module = otio_console.otiocat def test_basic(self): sys.argv = ['otiocat', SCREENING_EXAMPLE_PATH] self.run_test() self.assertIn('"name": "Example_Screening.01",', sys.stdout.getvalue()) def test_no_media_linker(self): sys.argv = ['otiocat', SCREENING_EXAMPLE_PATH, "-m", "none"] self.run_test() self.assertIn('"name": "Example_Screening.01",', sys.stdout.getvalue()) def test_input_argument_error(self): sys.argv = [ 'otiocat', SCREENING_EXAMPLE_PATH, "-a", "foobar", ] with self.assertRaises(SystemExit): self.run_test() # read results back in self.assertIn('error: adapter', sys.stderr.getvalue()) def test_media_linker_argument_error(self): sys.argv = [ 'otiocat', SCREENING_EXAMPLE_PATH, "-M", "foobar", ] with self.assertRaises(SystemExit): self.run_test() # read results back in self.assertIn('error: media linker', sys.stderr.getvalue()) OTIOCatTests_OnShell = CreateShelloutTest(OTIOCatTests) class OTIOConvertTests(ConsoleTester, unittest.TestCase): test_module = otio_console.otioconvert def test_basic(self): with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, "test_basic.otio") sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, '--tracks', '0' ] self.run_test() # read results back in with open(temp_file) as fi: self.assertIn('"name": "Example_Screening.01",', fi.read()) def test_begin_end(self): with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, "test_begin_end.otio") # begin needs to be a,b sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, "--begin", "foobar" ] with self.assertRaises(SystemExit): self.run_test() # end requires begin sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, "--end", "foobar" ] with self.assertRaises(SystemExit): self.run_test() # prune everything sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, "--begin", "0,24", "--end", "0,24", ] otio_console.otioconvert.main() # check that begin/end "," parsing is checked sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, "--begin", "0", "--end", "0,24", ] with self.assertRaises(SystemExit): self.run_test() sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, "--begin", "0,24", "--end", "0", ] with self.assertRaises(SystemExit): self.run_test() result = otio.adapters.read_from_file(temp_file) self.assertEqual(len(result.tracks[0]), 0) self.assertEqual(result.name, "Example_Screening.01") def test_input_argument_error(self): with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, "test_input_argument_error.otio") sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, "-a", "foobar", ] with self.assertRaises(SystemExit): self.run_test() # read results back in self.assertIn('error: input adapter', sys.stderr.getvalue()) def test_output_argument_error(self): with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, "test_output_argument_error.otio") sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, "-A", "foobar", ] with self.assertRaises(SystemExit): self.run_test() # read results back in self.assertIn('error: output adapter', sys.stderr.getvalue()) def test_media_linker_argument_error(self): with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, "test_media_linker_argument_error.otio") sys.argv = [ 'otioconvert', '-i', SCREENING_EXAMPLE_PATH, '-o', temp_file, "-m", "fake_linker", "-M", "somestring=foobar", "-M", "foobar", ] with self.assertRaises(SystemExit): self.run_test() # read results back in self.assertIn('error: media linker', sys.stderr.getvalue()) OTIOConvertTests_OnShell = CreateShelloutTest(OTIOConvertTests) class OTIOPlugInfoTest(ConsoleTester, unittest.TestCase): test_module = otio_console.otiopluginfo def test_basic(self): sys.argv = ['otiopluginfo'] self.run_test() self.assertIn("Manifests loaded:", sys.stdout.getvalue()) OTIOPlugInfoTest_ShellOut = CreateShelloutTest(OTIOStatTest) class OTIOToolTest(ConsoleTester, unittest.TestCase): test_module = otio_console.otiotool def test_list_tracks(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--list-tracks' ] out, err = self.run_test() self.assertEqual("""TIMELINE: OTIO TEST - multitrack.Exported.01 TRACK: Sequence (Video) TRACK: Sequence 2 (Video) TRACK: Sequence 3 (Video) """, out) def test_list_clips(self): sys.argv = [ 'otiotool', '-i', SCREENING_EXAMPLE_PATH, '--list-clips' ] out, err = self.run_test() self.assertEqual("""TIMELINE: Example_Screening.01 CLIP: ZZ100_501 (LAY3) CLIP: ZZ100_502A (LAY3) CLIP: ZZ100_503A (LAY1) CLIP: ZZ100_504C (LAY1) CLIP: ZZ100_504B (LAY1) CLIP: ZZ100_507C (LAY2) CLIP: ZZ100_508 (LAY2) CLIP: ZZ100_510 (LAY1) CLIP: ZZ100_510B (LAY1) """, out) def test_list_markers(self): sys.argv = [ 'otiotool', '-i', PREMIERE_EXAMPLE_PATH, '--list-markers' ] out, err = self.run_test() self.assertEqual( ("TIMELINE: sc01_sh010_layerA\n" " MARKER: global: 00:00:03:23 local: 00:00:03:23 duration: 0.0 color: RED name: My MArker 1\n" # noqa: E501 line too long " MARKER: global: 00:00:16:12 local: 00:00:16:12 duration: 0.0 color: RED name: dsf\n" # noqa: E501 line too long " MARKER: global: 00:00:09:28 local: 00:00:09:28 duration: 0.0 color: RED name: \n" # noqa: E501 line too long " MARKER: global: 00:00:13:05 local: 00:00:02:13 duration: 0.0 color: RED name: \n"), # noqa: E501 line too long out) def test_list_tracks_and_clips(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--list-tracks', '--list-clips' ] out, err = self.run_test() self.assertEqual("""TIMELINE: OTIO TEST - multitrack.Exported.01 TRACK: Sequence (Video) CLIP: tech.fux (loop)-HD.mp4 CLIP: out-b (loop)-HD.mp4 CLIP: brokchrd (loop)-HD.mp4 TRACK: Sequence 2 (Video) CLIP: t-hawk (loop)-HD.mp4 TRACK: Sequence 3 (Video) CLIP: KOLL-HD.mp4 """, out) def test_list_tracks_and_clips_and_media(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--list-tracks', '--list-clips', '--list-media' ] out, err = self.run_test() self.assertEqual("""TIMELINE: OTIO TEST - multitrack.Exported.01 TRACK: Sequence (Video) CLIP: tech.fux (loop)-HD.mp4 MEDIA: None CLIP: out-b (loop)-HD.mp4 MEDIA: None CLIP: brokchrd (loop)-HD.mp4 MEDIA: None TRACK: Sequence 2 (Video) CLIP: t-hawk (loop)-HD.mp4 MEDIA: None TRACK: Sequence 3 (Video) CLIP: KOLL-HD.mp4 MEDIA: None """, out) def test_list_tracks_and_clips_and_media_and_markers(self): sys.argv = [ 'otiotool', '-i', PREMIERE_EXAMPLE_PATH, '--list-tracks', '--list-clips', '--list-media', '--list-markers' ] out, err = self.run_test() self.assertEqual( ("TIMELINE: sc01_sh010_layerA\n" " MARKER: global: 00:00:03:23 local: 00:00:03:23 duration: 0.0 color: RED name: My MArker 1\n" # noqa E501 line too long " MARKER: global: 00:00:16:12 local: 00:00:16:12 duration: 0.0 color: RED name: dsf\n" # noqa E501 line too long " MARKER: global: 00:00:09:28 local: 00:00:09:28 duration: 0.0 color: RED name: \n" # noqa E501 line too long "TRACK: (Video)\n" " CLIP: sc01_sh010_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh010_anim.mov\n" "TRACK: (Video)\n" " CLIP: sc01_sh010_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh010_anim.mov\n" " CLIP: sc01_sh020_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh020_anim.mov\n" " CLIP: sc01_sh030_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh030_anim.mov\n" " MARKER: global: 00:00:13:05 local: 00:00:02:13 duration: 0.0 color: RED name: \n" # noqa E501 line too long "TRACK: (Video)\n" " CLIP: test_title\n" " MEDIA: None\n" "TRACK: (Video)\n" " CLIP: sc01_master_layerA_sh030_temp.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov\n" # noqa E501 line too long " CLIP: sc01_sh010_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh010_anim.mov\n" "TRACK: (Audio)\n" " CLIP: sc01_sh010_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh010_anim.mov\n" " CLIP: sc01_sh010_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh010_anim.mov\n" "TRACK: (Audio)\n" " CLIP: sc01_placeholder.wav\n" " MEDIA: file://localhost/D%3a/media/sc01_placeholder.wav\n" "TRACK: (Audio)\n" " CLIP: track_08.wav\n" " MEDIA: file://localhost/D%3a/media/track_08.wav\n" "TRACK: (Audio)\n" " CLIP: sc01_master_layerA_sh030_temp.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov\n" # noqa E501 line too long " CLIP: sc01_sh010_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh010_anim.mov\n"), out) def test_verify_media(self): sys.argv = [ 'otiotool', '-i', PREMIERE_EXAMPLE_PATH, '--list-tracks', '--list-clips', '--list-media', '--verify-media' ] out, err = self.run_test() self.assertEqual("""TIMELINE: sc01_sh010_layerA TRACK: (Video) CLIP: sc01_sh010_anim.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_sh010_anim.mov TRACK: (Video) CLIP: sc01_sh010_anim.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_sh010_anim.mov CLIP: sc01_sh020_anim.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_sh020_anim.mov CLIP: sc01_sh030_anim.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_sh030_anim.mov TRACK: (Video) CLIP: test_title MEDIA: None TRACK: (Video) CLIP: sc01_master_layerA_sh030_temp.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov CLIP: sc01_sh010_anim.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_sh010_anim.mov TRACK: (Audio) CLIP: sc01_sh010_anim.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_sh010_anim.mov CLIP: sc01_sh010_anim.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_sh010_anim.mov TRACK: (Audio) CLIP: sc01_placeholder.wav MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_placeholder.wav TRACK: (Audio) CLIP: track_08.wav MEDIA NOT FOUND: file://localhost/D%3a/media/track_08.wav TRACK: (Audio) CLIP: sc01_master_layerA_sh030_temp.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov CLIP: sc01_sh010_anim.mov MEDIA NOT FOUND: file://localhost/D%3a/media/sc01_sh010_anim.mov """, out) def test_video_only(self): sys.argv = [ 'otiotool', '-i', PREMIERE_EXAMPLE_PATH, '--video-only', '--list-clips' ] out, err = self.run_test() self.assertEqual("""TIMELINE: sc01_sh010_layerA CLIP: sc01_sh010_anim.mov CLIP: sc01_sh010_anim.mov CLIP: sc01_sh020_anim.mov CLIP: sc01_sh030_anim.mov CLIP: test_title CLIP: sc01_master_layerA_sh030_temp.mov CLIP: sc01_sh010_anim.mov """, out) def test_audio_only(self): sys.argv = [ 'otiotool', '-i', PREMIERE_EXAMPLE_PATH, '--audio-only', '--list-clips' ] out, err = self.run_test() self.assertEqual("""TIMELINE: sc01_sh010_layerA CLIP: sc01_sh010_anim.mov CLIP: sc01_sh010_anim.mov CLIP: sc01_placeholder.wav CLIP: track_08.wav CLIP: sc01_master_layerA_sh030_temp.mov CLIP: sc01_sh010_anim.mov """, out) def test_only_tracks_with_name(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--only-tracks-with-name', 'Sequence 3', '--list-clips' ] out, err = self.run_test() self.assertEqual("""TIMELINE: OTIO TEST - multitrack.Exported.01 CLIP: KOLL-HD.mp4 """, out) def test_only_tracks_with_index(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--only-tracks-with-index', '3', '--list-clips' ] out, err = self.run_test() self.assertEqual("""TIMELINE: OTIO TEST - multitrack.Exported.01 CLIP: KOLL-HD.mp4 """, out) def test_only_tracks_with_index2(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--only-tracks-with-index', '2', '3', '--list-clips' ] out, err = self.run_test() self.assertEqual("""TIMELINE: OTIO TEST - multitrack.Exported.01 CLIP: t-hawk (loop)-HD.mp4 CLIP: KOLL-HD.mp4 """, out) def test_only_clips_with_name(self): sys.argv = [ 'otiotool', '-i', PREMIERE_EXAMPLE_PATH, '--list-clips', '--list-tracks', '--only-clips-with-name', 'sc01_sh010_anim.mov' ] out, err = self.run_test() self.assertEqual("""TIMELINE: sc01_sh010_layerA TRACK: (Video) CLIP: sc01_sh010_anim.mov TRACK: (Video) CLIP: sc01_sh010_anim.mov TRACK: (Video) TRACK: (Video) CLIP: sc01_sh010_anim.mov TRACK: (Audio) CLIP: sc01_sh010_anim.mov CLIP: sc01_sh010_anim.mov TRACK: (Audio) TRACK: (Audio) TRACK: (Audio) CLIP: sc01_sh010_anim.mov """, out) def test_only_clips_with_regex(self): sys.argv = [ 'otiotool', '-i', PREMIERE_EXAMPLE_PATH, '--list-clips', '--list-tracks', '--only-clips-with-name-regex', 'anim' ] out, err = self.run_test() self.assertEqual("""TIMELINE: sc01_sh010_layerA TRACK: (Video) CLIP: sc01_sh010_anim.mov TRACK: (Video) CLIP: sc01_sh010_anim.mov CLIP: sc01_sh020_anim.mov CLIP: sc01_sh030_anim.mov TRACK: (Video) TRACK: (Video) CLIP: sc01_sh010_anim.mov TRACK: (Audio) CLIP: sc01_sh010_anim.mov CLIP: sc01_sh010_anim.mov TRACK: (Audio) TRACK: (Audio) TRACK: (Audio) CLIP: sc01_sh010_anim.mov """, out) def test_remove_transition(self): sys.argv = [ 'otiotool', '-i', TRANSITION_PATH, '-o', '-', '--remove-transitions' ] out, err = self.run_test() self.assertNotIn('"OTIO_SCHEMA": "Transition.', out) # make sure the timeline has the same clips in it in_timeline = otio.adapters.read_from_file(TRANSITION_PATH) out_timeline = otio.adapters.read_from_string(out, "otio_json") self.assertEqual(len(in_timeline.find_clips()), len(out_timeline.find_clips())) def test_remove_effects(self): sys.argv = [ 'otiotool', '-i', EFFECTS_PATH, '-o', '-', '--remove-effects' ] out, err = self.run_test() self.assertNotIn('"OTIO_SCHEMA": "Effect.', out) # make sure the timeline has the same clips in it in_timeline = otio.adapters.read_from_file(EFFECTS_PATH) out_timeline = otio.adapters.read_from_string(out, "otio_json") self.assertEqual(len(in_timeline.find_clips()), len(out_timeline.find_clips())) def test_trim(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--trim', '20', '40', '--list-clips', '--inspect', 't-hawk' ] out, err = self.run_test() self.assertEqual( ("TIMELINE: OTIO TEST - multitrack.Exported.01\n" " ITEM: t-hawk (loop)-HD.mp4 ()\n" # noqa E501 line too long " source_range: TimeRange(RationalTime(0, 24), RationalTime(478, 24))\n" # noqa E501 line too long " trimmed_range: TimeRange(RationalTime(0, 24), RationalTime(478, 24))\n" # noqa E501 line too long " visible_range: TimeRange(RationalTime(0, 24), RationalTime(478, 24))\n" # noqa E501 line too long " range_in_parent: TimeRange(RationalTime(2, 24), RationalTime(478, 24))\n" # noqa E501 line too long " trimmed range in timeline: TimeRange(RationalTime(2, 24), RationalTime(478, 24))\n" # noqa E501 line too long " visible range in timeline: TimeRange(RationalTime(2, 24), RationalTime(478, 24))\n" # noqa E501 line too long " range in Sequence 2 (): TimeRange(RationalTime(2, 24), RationalTime(478, 24))\n" # noqa E501 line too long " range in NestedScope (): TimeRange(RationalTime(2, 24), RationalTime(478, 24))\n" # noqa E501 line too long "TIMELINE: OTIO TEST - multitrack.Exported.01\n" " CLIP: tech.fux (loop)-HD.mp4\n" " CLIP: out-b (loop)-HD.mp4\n" " CLIP: t-hawk (loop)-HD.mp4\n"), out) def test_flatten(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--flatten', 'video', '--list-clips', '--list-tracks', '--inspect', 'out-b' ] out, err = self.run_test() self.assertEqual( ("TIMELINE: OTIO TEST - multitrack.Exported.01\n" " ITEM: out-b (loop)-HD.mp4 ()\n" # noqa E501 line too long " source_range: TimeRange(RationalTime(159, 24), RationalTime(236, 24))\n" # noqa E501 line too long " trimmed_range: TimeRange(RationalTime(159, 24), RationalTime(236, 24))\n" # noqa E501 line too long " visible_range: TimeRange(RationalTime(159, 24), RationalTime(236, 24))\n" # noqa E501 line too long " range_in_parent: TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long " trimmed range in timeline: TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long " visible range in timeline: TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long " range in Flattened (): TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long " range in NestedScope (): TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long "TIMELINE: OTIO TEST - multitrack.Exported.01\n" "TRACK: Flattened (Video)\n" " CLIP: tech.fux (loop)-HD.mp4\n" " CLIP: t-hawk (loop)-HD.mp4\n" " CLIP: out-b (loop)-HD.mp4\n" " CLIP: KOLL-HD.mp4\n" " CLIP: brokchrd (loop)-HD.mp4\n"), out) def test_keep_flattened_tracks(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--flatten', 'video', '--keep-flattened-tracks', '--list-clips', '--list-tracks', '--inspect', 'out-b' ] out, err = self.run_test() self.assertEqual( ("TIMELINE: OTIO TEST - multitrack.Exported.01\n" " ITEM: out-b (loop)-HD.mp4 ()\n" # noqa E501 line too long " source_range: TimeRange(RationalTime(0, 24), RationalTime(722, 24))\n" # noqa E501 line too long " trimmed_range: TimeRange(RationalTime(0, 24), RationalTime(722, 24))\n" # noqa E501 line too long " visible_range: TimeRange(RationalTime(0, 24), RationalTime(722, 24))\n" # noqa E501 line too long " range_in_parent: TimeRange(RationalTime(803, 24), RationalTime(722, 24))\n" # noqa E501 line too long " trimmed range in timeline: TimeRange(RationalTime(803, 24), RationalTime(722, 24))\n" # noqa E501 line too long " visible range in timeline: TimeRange(RationalTime(803, 24), RationalTime(722, 24))\n" # noqa E501 line too long " range in Sequence (): TimeRange(RationalTime(803, 24), RationalTime(722, 24))\n" # noqa E501 line too long " range in NestedScope (): TimeRange(RationalTime(803, 24), RationalTime(722, 24))\n" # noqa E501 line too long " ITEM: out-b (loop)-HD.mp4 ()\n" # noqa E501 line too long " source_range: TimeRange(RationalTime(159, 24), RationalTime(236, 24))\n" # noqa E501 line too long " trimmed_range: TimeRange(RationalTime(159, 24), RationalTime(236, 24))\n" # noqa E501 line too long " visible_range: TimeRange(RationalTime(159, 24), RationalTime(236, 24))\n" # noqa E501 line too long " range_in_parent: TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long " trimmed range in timeline: TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long " visible range in timeline: TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long " range in Flattened (): TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long " range in NestedScope (): TimeRange(RationalTime(962, 24), RationalTime(236, 24))\n" # noqa E501 line too long "TIMELINE: OTIO TEST - multitrack.Exported.01\n" "TRACK: Sequence (Video)\n" " CLIP: tech.fux (loop)-HD.mp4\n" " CLIP: out-b (loop)-HD.mp4\n" " CLIP: brokchrd (loop)-HD.mp4\n" "TRACK: Sequence 2 (Video)\n" " CLIP: t-hawk (loop)-HD.mp4\n" "TRACK: Sequence 3 (Video)\n" " CLIP: KOLL-HD.mp4\n" "TRACK: Flattened (Video)\n" " CLIP: tech.fux (loop)-HD.mp4\n" " CLIP: t-hawk (loop)-HD.mp4\n" " CLIP: out-b (loop)-HD.mp4\n" " CLIP: KOLL-HD.mp4\n" " CLIP: brokchrd (loop)-HD.mp4\n"), out) def test_stack(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, PREMIERE_EXAMPLE_PATH, '--stack', '--list-clips', '--list-tracks', '--stats' ] out, err = self.run_test() self.maxDiff = None self.assertEqual("""Name: Stacked 2 Timelines Start: 00:00:00:00 End: 00:02:16:18 Duration: 00:02:16:18 TIMELINE: Stacked 2 Timelines TRACK: Sequence (Video) CLIP: tech.fux (loop)-HD.mp4 CLIP: out-b (loop)-HD.mp4 CLIP: brokchrd (loop)-HD.mp4 TRACK: Sequence 2 (Video) CLIP: t-hawk (loop)-HD.mp4 TRACK: Sequence 3 (Video) CLIP: KOLL-HD.mp4 TRACK: (Video) CLIP: sc01_sh010_anim.mov TRACK: (Video) CLIP: sc01_sh010_anim.mov CLIP: sc01_sh020_anim.mov CLIP: sc01_sh030_anim.mov TRACK: (Video) CLIP: test_title TRACK: (Video) CLIP: sc01_master_layerA_sh030_temp.mov CLIP: sc01_sh010_anim.mov TRACK: (Audio) CLIP: sc01_sh010_anim.mov CLIP: sc01_sh010_anim.mov TRACK: (Audio) CLIP: sc01_placeholder.wav TRACK: (Audio) CLIP: track_08.wav TRACK: (Audio) CLIP: sc01_master_layerA_sh030_temp.mov CLIP: sc01_sh010_anim.mov """, out) def test_concat(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, PREMIERE_EXAMPLE_PATH, '--concat', '--list-clips', '--list-tracks', '--stats' ] out, err = self.run_test() self.maxDiff = None self.assertEqual("""Name: Concatenated 2 Timelines Start: 00:00:00:00 End: 00:02:59:03 Duration: 00:02:59:03 TIMELINE: Concatenated 2 Timelines TRACK: (Video) TRACK: Sequence (Video) CLIP: tech.fux (loop)-HD.mp4 CLIP: out-b (loop)-HD.mp4 CLIP: brokchrd (loop)-HD.mp4 TRACK: Sequence 2 (Video) CLIP: t-hawk (loop)-HD.mp4 TRACK: Sequence 3 (Video) CLIP: KOLL-HD.mp4 TRACK: (Video) CLIP: sc01_sh010_anim.mov TRACK: (Video) CLIP: sc01_sh010_anim.mov CLIP: sc01_sh020_anim.mov CLIP: sc01_sh030_anim.mov TRACK: (Video) CLIP: test_title TRACK: (Video) CLIP: sc01_master_layerA_sh030_temp.mov CLIP: sc01_sh010_anim.mov TRACK: (Audio) CLIP: sc01_sh010_anim.mov CLIP: sc01_sh010_anim.mov TRACK: (Audio) CLIP: sc01_placeholder.wav TRACK: (Audio) CLIP: track_08.wav TRACK: (Audio) CLIP: sc01_master_layerA_sh030_temp.mov CLIP: sc01_sh010_anim.mov """, out) def test_redact(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--redact', '--list-clips', '--list-tracks' ] out, err = self.run_test() self.assertEqual("""TIMELINE: Timeline #1 TRACK: Track #1 (Video) CLIP: Clip #1 CLIP: Clip #2 CLIP: Clip #3 TRACK: Track #2 (Video) CLIP: Clip #4 TRACK: Track #3 (Video) CLIP: Clip #5 """, out) def test_stats(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--stats' ] out, err = self.run_test() self.assertEqual("""Name: OTIO TEST - multitrack.Exported.01 Start: 00:00:00:00 End: 00:02:16:18 Duration: 00:02:16:18 """, out) def test_inspect(self): sys.argv = [ 'otiotool', '-i', MULTITRACK_PATH, '--inspect', 'KOLL' ] out, err = self.run_test() self.assertEqual( ("TIMELINE: OTIO TEST - multitrack.Exported.01\n" " ITEM: KOLL-HD.mp4 ()\n" " source_range: TimeRange(RationalTime(0, 24), RationalTime(640, 24))\n" # noqa E501 line too long " trimmed_range: TimeRange(RationalTime(0, 24), RationalTime(640, 24))\n" # noqa E501 line too long " visible_range: TimeRange(RationalTime(0, 24), RationalTime(640, 24))\n" # noqa E501 line too long " range_in_parent: TimeRange(RationalTime(1198, 24), RationalTime(640, 24))\n" # noqa E501 line too long " trimmed range in timeline: TimeRange(RationalTime(1198, 24), RationalTime(640, 24))\n" # noqa E501 line too long " visible range in timeline: TimeRange(RationalTime(1198, 24), RationalTime(640, 24))\n" # noqa E501 line too long " range in Sequence 3 (): TimeRange(RationalTime(1198, 24), RationalTime(640, 24))\n" # noqa E501 line too long " range in NestedScope (): TimeRange(RationalTime(1198, 24), RationalTime(640, 24))\n"), # noqa E501 line too long out) def test_relink(self): with tempfile.TemporaryDirectory() as temp_dir: temp_file1 = os.path.join(temp_dir, "Clip-001.empty") temp_file2 = os.path.join(temp_dir, "Clip-003.empty") open(temp_file1, "w").write("A") open(temp_file2, "w").write("B") temp_url = pathlib.Path(temp_dir).as_uri() sys.argv = [ 'otiotool', '-i', SIMPLE_CUT_PATH, '--relink-by-name', temp_dir, '--list-media' ] out, err = self.run_test() self.assertIn( ("TIMELINE: Figure 1 - Simple Cut List\n" f" MEDIA: {temp_url}/Clip-001.empty\n" " MEDIA: file:///folder/wind-up.mov\n" f" MEDIA: {temp_url}/Clip-003.empty\n" " MEDIA: file:///folder/credits.mov\n"), out) OTIOToolTest_ShellOut = CreateShelloutTest(OTIOToolTest) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_timeline.cpp0000664000175000017500000000555415110656141017133 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include #include #include namespace otime = opentime::OPENTIME_VERSION; namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; int main(int argc, char** argv) { Tests tests; tests.add_test( "test_find_children", [] { using namespace otio; otio::SerializableObject::Retainer cl = new otio::Clip(); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl); otio::SerializableObject::Retainer tl = new otio::Timeline(); tl->tracks()->append_child(tr); OTIO_NS::ErrorStatus err; auto result = tl->find_children(&err); assertEqual(result.size(), 1); assertEqual(result[0].value, cl.value); }); tests.add_test( "test_find_children_search_range", [] { using namespace otio; const TimeRange range(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)); otio::SerializableObject::Retainer cl0 = new otio::Clip(); cl0->set_source_range(range); otio::SerializableObject::Retainer cl1 = new otio::Clip(); cl1->set_source_range(range); otio::SerializableObject::Retainer cl2 = new otio::Clip(); cl2->set_source_range(range); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl0); tr->append_child(cl1); tr->append_child(cl2); otio::SerializableObject::Retainer tl = new otio::Timeline(); tl->tracks()->append_child(tr); OTIO_NS::ErrorStatus err; auto result = tl->find_children(&err, range); assertEqual(result.size(), 1); assertEqual(result[0].value, cl0.value); }); tests.add_test( "test_find_children_shallow_search", [] { using namespace otio; otio::SerializableObject::Retainer cl = new otio::Clip(); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl); otio::SerializableObject::Retainer tl = new otio::Timeline(); tl->tracks()->append_child(tr); OTIO_NS::ErrorStatus err; auto result = tl->find_children(&err, std::nullopt, true); assertEqual(result.size(), 0); result = tl->find_children(&err, std::nullopt, false); assertEqual(result.size(), 1); assertEqual(result[0].value, cl.value); }); tests.run(argc, argv); return 0; } opentimelineio-0.18.1/tests/utils.cpp0000664000175000017500000000201515110656141015413 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include void assertTrue(bool value) { assert(value); } void assertFalse(bool value) { assert(!value); } void Tests::add_test(std::string const& name, std::function const& test) { _tests.push_back(std::make_pair(name, test)); } void Tests::run(int argc, char** argv) { std::vector filter; for (int arg = 1; arg < argc; ++arg) { filter.push_back(argv[arg]); } for (auto const& test: _tests) { bool run_test = true; if (!filter.empty()) { const auto filter_it = std::find(filter.begin(), filter.end(), test.first); run_test = filter_it != filter.end(); } std::cout << (run_test ? "Running" : "Skipping") << " test " << test.first << std::endl; if (run_test) { test.second(); } } } opentimelineio-0.18.1/tests/test_otioz.py0000664000175000017500000002025115110656141016326 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Tests for the OTIOZ adapter.""" import unittest import os import tempfile import shutil import urllib.parse as urlparse import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") SCREENING_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "screening_example.otio") MEDIA_EXAMPLE_PATH_REL = os.path.relpath( os.path.join( SAMPLE_DATA_DIR, "OpenTimelineIO@3xDark.png" ) ) MEDIA_EXAMPLE_PATH_URL_REL = otio.url_utils.url_from_filepath( MEDIA_EXAMPLE_PATH_REL ) MEDIA_EXAMPLE_PATH_ABS = os.path.abspath( MEDIA_EXAMPLE_PATH_REL.replace( "3xDark", "3xLight" ) ) MEDIA_EXAMPLE_PATH_URL_ABS = otio.url_utils.url_from_filepath( MEDIA_EXAMPLE_PATH_ABS ) class OTIOZTester(unittest.TestCase, otio_test_utils.OTIOAssertions): def setUp(self): tl = otio.adapters.read_from_file(SCREENING_EXAMPLE_PATH) # convert to contrived local reference last_rel = False for cl in tl.find_clips(): # vary the relative and absolute paths, make sure that both work next_rel = ( MEDIA_EXAMPLE_PATH_URL_REL if last_rel else MEDIA_EXAMPLE_PATH_URL_ABS ) last_rel = not last_rel cl.media_reference = otio.schema.ExternalReference( target_url=next_rel ) self.tl = tl def test_dryrun(self): # generate a fake name with tempfile.NamedTemporaryFile(suffix=".otioz") as bogusfile: fname = bogusfile.name # dryrun should compute what the total size of the zipfile will be. size = otio.adapters.write_to_file(self.tl, fname, dryrun=True) self.assertEqual( size, os.path.getsize(MEDIA_EXAMPLE_PATH_ABS) + os.path.getsize(MEDIA_EXAMPLE_PATH_REL) ) def test_not_a_file_error(self): # dryrun should compute what the total size of the zipfile will be. tmp_path = tempfile.mkstemp(suffix=".otioz", text=False)[1] with tempfile.NamedTemporaryFile() as bogusfile: fname = bogusfile.name for cl in self.tl.find_clips(): # write with a non-file schema cl.media_reference = otio.schema.ExternalReference( target_url=f"http://{fname}" ) with self.assertRaises(otio.exceptions.OTIOError): otio.adapters.write_to_file(self.tl, tmp_path, dryrun=True) for cl in self.tl.find_clips(): cl.media_reference = otio.schema.ExternalReference( target_url=otio.url_utils.url_from_filepath(fname) ) with self.assertRaises(otio.exceptions.OTIOError): otio.adapters.write_to_file(self.tl, tmp_path, dryrun=True) tempdir = tempfile.mkdtemp() fname = tempdir shutil.rmtree(tempdir) for cl in self.tl.find_clips(): cl.media_reference = otio.schema.ExternalReference(target_url=fname) def test_colliding_basename(self): tempdir = tempfile.mkdtemp() new_path = os.path.join( tempdir, os.path.basename(MEDIA_EXAMPLE_PATH_ABS) ) shutil.copyfile( MEDIA_EXAMPLE_PATH_ABS, new_path ) list(self.tl.find_clips())[0].media_reference.target_url = ( otio.url_utils.url_from_filepath(new_path) ) tmp_path = tempfile.mkstemp(suffix=".otioz", text=False)[1] with self.assertRaises(otio.exceptions.OTIOError): otio.adapters.write_to_file(self.tl, tmp_path) with self.assertRaises(otio.exceptions.OTIOError): otio.adapters.write_to_file(self.tl, tmp_path, dryrun=True) shutil.rmtree(tempdir) def test_round_trip(self): with tempfile.NamedTemporaryFile(suffix=".otioz") as bogusfile: tmp_path = bogusfile.name otio.adapters.write_to_file(self.tl, tmp_path) self.assertTrue(os.path.exists(tmp_path)) result = otio.adapters.read_from_file(tmp_path) for cl in result.find_clips(): self.assertNotIn( cl.media_reference.target_url, [MEDIA_EXAMPLE_PATH_URL_ABS, MEDIA_EXAMPLE_PATH_URL_REL] ) # ensure that unix style paths are used, so that bundles created on # windows are compatible with ones created on unix self.assertFalse( urlparse.urlparse( cl.media_reference.target_url ).path.startswith( "media\\" ) ) # conform media references in input to what they should be in the output for cl in self.tl.find_clips(): # should be only field that changed cl.media_reference.target_url = "media/{}".format( os.path.basename(cl.media_reference.target_url) ) self.assertJsonEqual(result, self.tl) def test_round_trip_with_extraction(self): with tempfile.NamedTemporaryFile(suffix=".otioz") as bogusfile: tmp_path = bogusfile.name otio.adapters.write_to_file(self.tl, tmp_path) self.assertTrue(os.path.exists(tmp_path)) tempdir = tempfile.mkdtemp() result = otio.adapters.read_from_file( tmp_path, extract_to_directory=tempdir ) # make sure that all the references are ExternalReference for cl in result.find_clips(): self.assertIsInstance( cl.media_reference, otio.schema.ExternalReference ) # conform media references in input to what they should be in the output for cl in self.tl.find_clips(): # should be only field that changed cl.media_reference.target_url = "media/{}".format( os.path.basename(cl.media_reference.target_url) ) self.assertJsonEqual(result, self.tl) # content file self.assertTrue( os.path.exists( os.path.join( tempdir, otio.adapters.file_bundle_utils.BUNDLE_PLAYLIST_PATH ) ) ) # media directory overall self.assertTrue( os.path.exists( os.path.join( tempdir, otio.adapters.file_bundle_utils.BUNDLE_DIR_NAME ) ) ) # actual media file self.assertTrue( os.path.exists( os.path.join( tempdir, otio.adapters.file_bundle_utils.BUNDLE_DIR_NAME, os.path.basename(MEDIA_EXAMPLE_PATH_URL_REL) ) ) ) def test_round_trip_with_extraction_no_media(self): with tempfile.NamedTemporaryFile(suffix=".otioz") as bogusfile: tmp_path = bogusfile.name otio.adapters.write_to_file( self.tl, tmp_path, media_policy=( otio.adapters.file_bundle_utils.MediaReferencePolicy.AllMissing ), ) tempdir = tempfile.mkdtemp() result = otio.adapters.read_from_file( tmp_path, extract_to_directory=tempdir, ) version_file_path = os.path.join( tempdir, otio.adapters.file_bundle_utils.BUNDLE_VERSION_FILE ) self.assertTrue(os.path.exists(version_file_path)) with open(version_file_path) as fi: self.assertEqual( fi.read(), otio.adapters.file_bundle_utils.BUNDLE_VERSION ) # conform media references in input to what they should be in the output for cl in result.find_clips(): # should be all MissingReferences self.assertIsInstance( cl.media_reference, otio.schema.MissingReference ) self.assertIn("original_target_url", cl.media_reference.metadata) if __name__ == "__main__": unittest.main() opentimelineio-0.18.1/tests/test_adapter_plugin.py0000775000175000017500000002772615110656141020201 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import os import opentimelineio as otio from tests import baseline_reader, utils import tempfile """Unit tests for the adapter plugin system.""" MANIFEST_PATH = "adapter_plugin_manifest.plugin_manifest" ADAPTER_PATH = "adapter_example" MEDIA_LINKER_EXAMPLE = "media_linker_example" class TestAdapterSuffixes(unittest.TestCase): def test_supported_suffixes_is_not_none(self): result = otio.adapters.suffixes_with_defined_adapters() self.assertIsNotNone(result) self.assertNotEqual(result, []) self.assertNotEqual(result, set()) class TestPluginAdapters(unittest.TestCase): def setUp(self): self.jsn = baseline_reader.json_baseline_as_string(ADAPTER_PATH) self.adp = otio.adapters.read_from_string(self.jsn, 'otio_json') self.adp._json_path = os.path.join( baseline_reader.MODPATH, "baselines", ADAPTER_PATH ) def test_plugin_adapter(self): self.assertEqual(self.adp.name, "example") self.assertEqual(self.adp.filepath, "example.py") self.assertEqual(self.adp.suffixes[0], "example") self.assertEqual(list(self.adp.suffixes), ['example']) self.assertMultiLineEqual( str(self.adp), "Adapter(" "{}, " "{}, " "{}" ")".format( repr(self.adp.name), repr(self.adp.filepath), repr(self.adp.suffixes), ) ) self.assertMultiLineEqual( repr(self.adp), "otio.adapter.Adapter(" "name={}, " "filepath={}, " "suffixes={}" ")".format( repr(self.adp.name), repr(self.adp.filepath), repr(self.adp.suffixes), ) ) self.assertNotEqual(self.adp._json_path, None) def test_load_adapter_module(self): target = os.path.join( baseline_reader.MODPATH, "baselines", "example.py" ) self.assertEqual(self.adp.module_abs_path(), target) self.assertTrue(hasattr(self.adp.module(), "read_from_file")) # call through the module accessor self.assertEqual(self.adp.module().read_from_file("foo").name, "foo") # call through the convenience wrapper self.assertEqual(self.adp.read_from_file("foo").name, "foo") def test_has_feature(self): self.assertTrue(self.adp.has_feature("read")) self.assertTrue(self.adp.has_feature("read_from_file")) self.assertFalse(self.adp.has_feature("write")) self.assertTrue(self.adp.has_feature("hooks")) def test_pass_arguments_to_adapter(self): self.assertEqual(self.adp.read_from_file("foo", suffix=3).name, "foo3") def test_run_media_linker_during_adapter(self): mfest = otio.plugins.ActiveManifest() manifest = utils.create_manifest() # this wires up the media linkers into the active manifest mfest.media_linkers.extend(manifest.media_linkers) fake_tl = self.adp.read_from_file("foo", media_linker_name="example") self.assertTrue( fake_tl.tracks[0][0].media_reference.metadata.get( 'from_test_linker' ) ) fake_tl = self.adp.read_from_string( "foo", media_linker_name="example" ) self.assertTrue( fake_tl.tracks[0][0].media_reference.metadata.get( 'from_test_linker' ) ) # explicitly turn the media_linker off fake_tl = self.adp.read_from_file("foo", media_linker_name=None) self.assertIsNone( fake_tl.tracks[0][0].media_reference.metadata.get( 'from_test_linker' ) ) # Delete the temporary manifest utils.remove_manifest(manifest) class TestPluginManifest(unittest.TestCase): def setUp(self): self.man = utils.create_manifest() def tearDown(self): utils.remove_manifest(self.man) def test_plugin_manifest(self): self.assertNotEqual(self.man.adapters, []) def test_find_adapter_by_suffix(self): self.assertEqual(self.man.from_filepath("example").name, "example") with self.assertRaises(Exception): self.man.from_filepath("BLARG") adp = self.man.from_filepath("example") self.assertEqual(adp.module().read_from_file("path").name, "path") self.assertEqual( self.man.adapter_module_from_suffix( "example" ).read_from_file("path").name, "path" ) def test_find_adapter_by_name(self): self.assertEqual(self.man.from_name("example").name, "example") with self.assertRaises(Exception): self.man.from_name("BLARG") adp = self.man.from_name("example") self.assertEqual(adp.module().read_from_file("path").name, "path") self.assertEqual( self.man.adapter_module_from_name("example").read_from_file( "path" ).name, "path" ) def test_deduplicate_env_variable_paths(self): "Ensure that duplicate entries in the environment variable are ignored" basename = "unittest.plugin_manifest.json" # back up existing manifest bak = otio.plugins.manifest._MANIFEST bak_env = os.environ.get('OTIO_PLUGIN_MANIFEST_PATH') try: # Generate a fake manifest in a temp file, and point at it with # the environment variable with tempfile.TemporaryDirectory( prefix='test_find_manifest_by_environment_variable' ) as temp_dir: temp_file = os.path.join(temp_dir, basename) otio.adapters.write_to_file(self.man, temp_file, 'otio_json') # clear out existing manifest otio.plugins.manifest._MANIFEST = None # set where to find the new manifest os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = ( temp_file # add it twice + os.pathsep + temp_file ) result = otio.plugins.manifest.load_manifest() # ... should only appear once in the result self.assertEqual(result.source_files.count(temp_file), 1) # Rather than try and remove any other setuptools based plugins # that might be installed, this check is made more permissive to # see if the known unit test linker is being loaded by the manifest self.assertTrue(len(result.media_linkers) > 0) self.assertIn("example", (ml.name for ml in result.media_linkers)) finally: otio.plugins.manifest._MANIFEST = bak if bak_env is not None: os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = bak_env else: if "OTIO_PLUGIN_MANIFEST_PATH" in os.environ: del os.environ['OTIO_PLUGIN_MANIFEST_PATH'] def test_find_manifest_by_environment_variable(self): basename = "unittest.plugin_manifest.json" # back up existing manifest bak = otio.plugins.manifest._MANIFEST bak_env = os.environ.get('OTIO_PLUGIN_MANIFEST_PATH') try: # Generate a fake manifest in a temp file, and point at it with # the environment variable with tempfile.TemporaryDirectory( prefix='test_find_manifest_by_environment_variable' ) as temp_dir: temp_file = os.path.join(temp_dir, basename) otio.adapters.write_to_file(self.man, temp_file, 'otio_json') # clear out existing manifest otio.plugins.manifest._MANIFEST = None # set where to find the new manifest os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = ( temp_file + os.pathsep + 'foo' ) result = otio.plugins.manifest.load_manifest() # Rather than try and remove any other setuptools based plugins # that might be installed, this check is made more permissive to # see if the known unit test linker is being loaded by the manifest self.assertTrue(len(result.media_linkers) > 0) self.assertIn("example", (ml.name for ml in result.media_linkers)) finally: otio.plugins.manifest._MANIFEST = bak if bak_env is not None: os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = bak_env else: del os.environ['OTIO_PLUGIN_MANIFEST_PATH'] def test_load_manifest_empty_environment_variable(self): # back up existing manifest bak = otio.plugins.manifest._MANIFEST bak_env = os.environ.get('OTIO_PLUGIN_MANIFEST_PATH') try: # clear out existing manifest otio.plugins.manifest._MANIFEST = None # set manifest to an empty path os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = '' result = otio.plugins.manifest.load_manifest() # Make sure adapters and linkers landed in the proper place for adapter in result.adapters: self.assertIsInstance(adapter, otio.adapters.Adapter) for linker in result.media_linkers: self.assertIsInstance(linker, otio.media_linker.MediaLinker) finally: otio.plugins.manifest._MANIFEST = bak if bak_env is not None: os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = bak_env else: del os.environ['OTIO_PLUGIN_MANIFEST_PATH'] def test_plugin_manifest_order(self): basename = "test.plugin_manifest.json" # back up existing manifest bak = otio.plugins.manifest._MANIFEST bak_env = os.environ.get('OTIO_PLUGIN_MANIFEST_PATH') try: local_manifest = { "OTIO_SCHEMA": "PluginManifest.1", "adapters": [ { "OTIO_SCHEMA": "Adapter.1", "name": "local_json", "filepath": "example.py", "suffixes": ["example"] } ], } with tempfile.TemporaryDirectory() as temp_dir: filename = os.path.join(temp_dir, basename) otio.adapters.write_to_file(local_manifest, filename, 'otio_json') result = otio.plugins.manifest.load_manifest() self.assertTrue(len(result.adapters) > 0) self.assertIn("otio_json", (ml.name for ml in result.adapters)) self.assertNotIn("local_otio", (ml.name for ml in result.adapters)) # set where to find the new manifest os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = filename result = otio.plugins.manifest.load_manifest() # Rather than try and remove any other setuptools based plugins # that might be installed, this check is made more permissive to # see if the known unit test linker is being loaded by the manifest self.assertTrue(len(result.adapters) > 0) self.assertIn("otio_json", (ml.name for ml in result.adapters)) self.assertIn("local_json", (ml.name for ml in result.adapters)) self.assertLess( [ml.name for ml in result.adapters].index("local_json"), [ml.name for ml in result.adapters].index("otio_json") ) finally: otio.plugins.manifest._MANIFEST = bak if bak_env is not None: os.environ['OTIO_PLUGIN_MANIFEST_PATH'] = bak_env else: del os.environ['OTIO_PLUGIN_MANIFEST_PATH'] if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_generator_reference.py0000664000175000017500000000647515110656141021202 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """ Generator Reference class test harness. """ import unittest import os import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") GEN_REF_TEST = os.path.join(SAMPLE_DATA_DIR, "generator_reference_test.otio") class GeneratorRefTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def setUp(self): self.gen = otio.schema.GeneratorReference( name="SMPTEBars", generator_kind="SMPTEBars", available_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(100, 24), ), parameters={ "test_param": 5.0, }, metadata={ "foo": "bar" }, available_image_bounds=otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(16.0, 9.0) ) ) def test_constructor(self): self.assertEqual(self.gen.generator_kind, "SMPTEBars") self.assertEqual(self.gen.name, "SMPTEBars") self.assertEqual(self.gen.parameters, {"test_param": 5.0}) self.assertEqual(self.gen.metadata, {"foo": "bar"}) self.assertEqual( self.gen.available_range, otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(100, 24), ) ) self.assertEqual( self.gen.available_image_bounds, otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(16.0, 9.0) ) ) def test_serialize(self): encoded = otio.adapters.otio_json.write_to_string(self.gen) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(self.gen, decoded) def test_read_file(self): self.assertTrue(os.path.exists(GEN_REF_TEST)) decoded = otio.adapters.otio_json.read_from_file(GEN_REF_TEST) self.assertEqual( decoded.tracks[0][0].media_reference.generator_kind, "SMPTEBars" ) def test_stringify(self): self.assertMultiLineEqual( str(self.gen), "GeneratorReference(" '"{}", ' '"{}", ' '{}, ' '{}, ' "{}" ")".format( str(self.gen.name), str(self.gen.generator_kind), str(self.gen.parameters), str(self.gen.available_image_bounds), str(self.gen.metadata), ) ) self.assertMultiLineEqual( repr(self.gen), "otio.schema.GeneratorReference(" "name={}, " "generator_kind={}, " "parameters={}, " "available_image_bounds={}, " "metadata={}" ")".format( repr(self.gen.name), repr(self.gen.generator_kind), repr(self.gen.parameters), repr(self.gen.available_image_bounds), repr(self.gen.metadata), ) ) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_core.py0000664000175000017500000000275715110656141016125 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import sys import shutil import tempfile import unittest import opentimelineio as otio class TestCoreFunctions(unittest.TestCase): def setUp(self): self.tmpDir = tempfile.mkdtemp() def tearDown(self): shutil.rmtree(self.tmpDir) def test_deserialize_json_from_file_errors(self): """Verify the bindings return the correct errors based on the errno""" with self.assertRaises(FileNotFoundError) as exc: otio.core.deserialize_json_from_file('non-existent-file-here') self.assertIsInstance(exc.exception, FileNotFoundError) @unittest.skipUnless( not sys.platform.startswith("win"), "requires non Windows system" ) def test_serialize_json_to_file_errors_non_windows(self): """Verify the bindings return the correct errors based on the errno""" with self.assertRaises(IsADirectoryError) as exc: otio.core.serialize_json_to_file({}, self.tmpDir) self.assertIsInstance(exc.exception, IsADirectoryError) @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") def test_serialize_json_to_file_errors_windows(self): """Verify the bindings return the correct errors based on the errno""" with self.assertRaises(PermissionError) as exc: otio.core.serialize_json_to_file({}, self.tmpDir) self.assertIsInstance(exc.exception, PermissionError) opentimelineio-0.18.1/tests/test_editAlgorithm.cpp0000664000175000017500000034552215110656141020123 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include #include #include #include #include #include #include // Uncomment this for debugging output #define DEBUG namespace otime = opentime::OPENTIME_VERSION; namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; using otime::RationalTime; using otime::TimeRange; using otio::algo::ReferencePoint; #ifdef DEBUG #include std::ostream& operator << (std::ostream& os, const RationalTime& value) { os << std::fixed << value.value() << "/" << value.rate(); return os; } std::ostream& operator << (std::ostream& os, const TimeRange& value) { os << std::fixed << value.start_time().value() << "/" << value.duration().value() << "/" << value.duration().rate(); return os; } #endif namespace { void assert_duration(const RationalTime& new_duration, const RationalTime& duration) { #ifdef DEBUG std::cout << "\tnew duration=" << new_duration << " old duration=" << duration << std::endl; #endif assertEqual(new_duration, duration); } void debug_track_ranges(const std::string& title, otio::Track* track) { #ifdef DEBUG std::cout << "\t" << title << " TRACK RANGES" << std::endl; for (const auto& child: track->children()) { auto clip = otio::dynamic_retainer_cast(child); if (clip) { auto range = track->trimmed_range_of_child(child).value(); std::cout << "\t\t" << clip->name() << " " << range << std::endl; } auto gap = otio::dynamic_retainer_cast(child); if (gap) { auto range = track->trimmed_range_of_child(child).value(); std::string name = gap->name(); if (name.empty()) name = "gap"; std::cout << "\t\t" << name << " " << range << std::endl; } auto transition = otio::dynamic_retainer_cast(child); if (transition) { auto range = track->trimmed_range_of_child(child).value(); std::string name = transition->name(); if (name.empty()) name = "transition"; std::cout << "\t\t" << name << " " << range << std::endl; } } std::cout << "\t" << title << " TRACK RANGES END" << std::endl; #endif } void debug_clip_ranges(const std::string& title, otio::Track* track) { #ifdef DEBUG std::cout << "\t" << title << " CLIP TRIMMED RANGES" << std::endl; for (const auto& child: track->children()) { auto clip = otio::dynamic_retainer_cast(child); if (clip) { auto range = clip->trimmed_range(); std::cout << "\t\t" << clip->name() << " " << range << std::endl; } auto gap = otio::dynamic_retainer_cast(child); if (gap) { auto range = gap->trimmed_range(); std::string name = gap->name(); if (name.empty()) name = "gap"; std::cout << "\t\t" << name << " " << range << std::endl; } } std::cout << "\t" << title << " CLIP TRIMMED RANGES END" << std::endl; #endif } void assert_clip_ranges( otio::Track* track, const std::vector& expected_ranges) { std::vector ranges; size_t children = 0; for (const auto& child: track->children()) { auto item = otio::dynamic_retainer_cast(child); if (item) { ranges.push_back(item->trimmed_range()); ++children; } } debug_clip_ranges("TEST", track); assertEqual(children, expected_ranges.size()); assertEqual(expected_ranges, ranges); } void assert_track_ranges( otio::Track* track, const std::vector& expected_ranges) { std::vector ranges; size_t children = 0; for (const auto& child: track->children()) { auto item = otio::dynamic_retainer_cast(child); if (item) { ranges.push_back(track->trimmed_range_of_child(child).value()); ++children; } } debug_track_ranges("TEST", track); assertEqual(children, ranges.size()); assertEqual(expected_ranges, ranges); } void test_edit_slice( const TimeRange& clip_range, const RationalTime& slice_time, const std::vector& slice_ranges) { // Create a track with one clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip("clip_0", nullptr, clip_range); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); debug_track_ranges("START", track); // Slice. otio::algo::slice(track, slice_time); // Asserts. assert_track_ranges(track, slice_ranges); } void test_edit_slice_transitions( const RationalTime& slice_time, const std::vector& slice_ranges) { // Create a track with two clips and one transition. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, TimeRange(RationalTime(0.0, 24.0), RationalTime(50.0, 24.0))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, TimeRange(RationalTime(0.0, 24.0), RationalTime(30.0, 24.0))); otio::SerializableObject::Retainer clip_3 = new otio::Clip( "clip_3", nullptr, TimeRange(RationalTime(0.0, 24.0), RationalTime(25.0, 24.0))); otio::SerializableObject::Retainer transition_0 = new otio::Transition( "transition_0", otio::Transition::Type::SMPTE_Dissolve, RationalTime(5.0, 24.0), RationalTime(3.0, 24.0)); otio::SerializableObject::Retainer transition_1 = new otio::Transition( "transition_1", otio::Transition::Type::SMPTE_Dissolve, RationalTime(5.0, 24.0), RationalTime(3.0, 24.0)); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->insert_child(1, transition_0); track->append_child(clip_2); track->append_child(clip_3); track->append_child(transition_1); debug_track_ranges("START", track); // Slice. otio::algo::slice(track, slice_time); // Asserts. assert_track_ranges(track, slice_ranges); } void test_edit_slip( const TimeRange& media_range, const TimeRange& clip_range, const RationalTime& slip_time, const TimeRange slip_range) { // Create one clip with one media. otio::SerializableObject::Retainer media_0 = new otio::MediaReference( "media_0", media_range); otio::SerializableObject::Retainer clip_0 = new otio::Clip("clip_0", media_0, clip_range); // Slip. otio::algo::slip(clip_0, slip_time); // Asserts. const otio::TimeRange& range = clip_0->trimmed_range(); assertEqual(slip_range, range); } void test_edit_slide( const TimeRange& media_range, const RationalTime& slide_time, const std::vector& slide_ranges) { // Create a track with three clips. otio::SerializableObject::Retainer media_0 = new otio::MediaReference( "media_0", media_range); otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", media_0, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(30.0, 24.0))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(40.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); // Slide. otio::algo::slide(clip_1, slide_time); // Asserts. assert_track_ranges(track, slide_ranges); } void test_edit_ripple( const RationalTime& delta_in, const RationalTime& delta_out, const std::vector& track_ranges, const std::vector& item_ranges ) { // Create a track with one gap and two clips. otio::SerializableObject::Retainer clip_0 = new otio::Gap( otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(20.0, 24.0)), "gap_0"); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(25.0, 24.0))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(20.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); otio::ErrorStatus error_status; otio::algo::ripple( clip_1, delta_in, delta_out, &error_status); // Asserts. assert(!otio::is_error(error_status)); assert_track_ranges(track, track_ranges); assert_clip_ranges(track, item_ranges); } void test_edit_roll( const RationalTime& delta_in, const RationalTime& delta_out, const std::vector& track_ranges, const std::vector& item_ranges ) { // Create a track with one gap and two clips. otio::SerializableObject::Retainer clip_0 = new otio::Gap( otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(20.0, 24.0)), "gap_0"); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(30.0, 24.0))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(20.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); otio::ErrorStatus error_status; otio::algo::roll( clip_1, delta_in, delta_out, &error_status); // Asserts. assert(!otio::is_error(error_status)); assert_track_ranges(track, track_ranges); assert_clip_ranges(track, item_ranges); } void test_edit_fill( const TimeRange& clip_range, const RationalTime& track_time, const ReferencePoint& reference_point, const std::vector& track_ranges, const std::vector& item_ranges ) { // Create a track with one gap and two clips. We leave one clip for fill. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(20.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Gap( otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(30.0, 24.0)), "gap_0"); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(20.0, 24.0))); otio::SerializableObject::Retainer clip_3 = new otio::Clip( "fill_0", nullptr, clip_range); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); auto duration = track->duration(); otio::ErrorStatus error_status; otio::algo::fill( clip_3, track, track_time, reference_point, &error_status); auto new_duration = track->duration(); // Asserts. if (reference_point == ReferencePoint::Sequence) { assert_duration(new_duration, duration); } assert(!otio::is_error(error_status)); assert_track_ranges(track, track_ranges); assert_clip_ranges(track, item_ranges); } } // namespace int main(int argc, char** argv) { Tests tests; tests.add_test("test_edit_slice_1", [] { // Slice in the middle. test_edit_slice( TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), RationalTime(12.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(12.0, 24.0)), TimeRange(RationalTime(12.0, 24.0), RationalTime(12.0, 24.0)) }); // Slice at the beginning. test_edit_slice( TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), RationalTime(0.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)) }); // Slice near the beginning. test_edit_slice( TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), RationalTime(1.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(1.0, 24.0)), TimeRange(RationalTime(1.0, 24.0), RationalTime(23.0, 24.0)) }); // Slice near the end. test_edit_slice( TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), RationalTime(23.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(23.0, 24.0)), TimeRange(RationalTime(23.0, 24.0), RationalTime(1.0, 24.0)) }); // Slice at the end. test_edit_slice( TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), RationalTime(24.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)) }); }); tests.add_test("test_edit_slice_2", [] { // Create a track with three clips of different rates // Slice the clips several times at different points. // Delete an item and slice again at same point. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, otio::TimeRange( otio::RationalTime(90.0, 30.0), otio::RationalTime(90.0, 30.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); // Slice. otio::ErrorStatus error_status; otio::algo::slice( track, RationalTime(121.0, 30.0), true, &error_status); // Asserts. assert(!otio::is_error(error_status)); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(0.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(31.0, 30.0), RationalTime(59, 30.0)), TimeRange( RationalTime(90.0, 30.0), RationalTime(90.0, 30.0)) }); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(90.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(121.0, 30.0), RationalTime(59, 30.0)), TimeRange( RationalTime(180.0, 30.0), RationalTime(90.0, 30.0)) }); otio::algo::slice( track, RationalTime(122.0, 30.0), true, &error_status); // Asserts. assert(!otio::is_error(error_status)); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(0.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(31.0, 30.0), RationalTime(1, 30.0)), TimeRange( RationalTime(32.0, 30.0), RationalTime(58, 30.0)), TimeRange( RationalTime(90.0, 30.0), RationalTime(90.0, 30.0)) }); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(90.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(121.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(122.0, 30.0), RationalTime(58, 30.0)), TimeRange( RationalTime(180.0, 30.0), RationalTime(90.0, 30.0)) }); track->remove_child(2); // Delete the 1 frame item // Asserts. assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(0.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(32.0, 30.0), RationalTime(58, 30.0)), TimeRange( RationalTime(90.0, 30.0), RationalTime(90.0, 30.0)) }); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(90.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(121.0, 30.0), RationalTime(58, 30.0)), TimeRange( RationalTime(179.0, 30.0), RationalTime(90.0, 30.0)) }); // Slice again at the same points (this slice does nothing at it is at // start point). otio::algo::slice( track, RationalTime(121.0, 30.0), true, &error_status); // Asserts. assert(!otio::is_error(error_status)); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(0.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(32.0, 30.0), RationalTime(58, 30.0)), TimeRange( RationalTime(90.0, 30.0), RationalTime(90.0, 30.0)) }); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(90.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(121.0, 30.0), RationalTime(58, 30.0)), TimeRange( RationalTime(179.0, 30.0), RationalTime(90.0, 30.0)) }); // Slice again for one frame otio::algo::slice( track, RationalTime(122.0, 30.0), true, &error_status); // Asserts. assert(!otio::is_error(error_status)); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(0.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(32.0, 30.0), RationalTime(1, 30.0)), TimeRange( RationalTime(33.0, 30.0), RationalTime(57, 30.0)), TimeRange( RationalTime(90.0, 30.0), RationalTime(90.0, 30.0)) }); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(90.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(121.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(122.0, 30.0), RationalTime(57, 30.0)), TimeRange( RationalTime(179.0, 30.0), RationalTime(90.0, 30.0)) }); // Slice again for one frame otio::algo::slice( track, RationalTime(179.0, 30.0), true, &error_status); // Asserts. assert(!otio::is_error(error_status)); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(0.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(32.0, 30.0), RationalTime(1, 30.0)), TimeRange( RationalTime(33.0, 30.0), RationalTime(57, 30.0)), TimeRange( RationalTime(90.0, 30.0), RationalTime(90.0, 30.0)) }); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(90.0, 30.0), RationalTime(31.0, 30.0)), TimeRange( RationalTime(121.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(122.0, 30.0), RationalTime(57, 30.0)), TimeRange( RationalTime(179.0, 30.0), RationalTime(90.0, 30.0)) }); }); tests.add_test("test_edit_slice_transitions_1", [] { // Four clips with two transitions. test_edit_slice_transitions( RationalTime(24.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), TimeRange(RationalTime(24.0, 24.0), RationalTime(50.0, 24.0)), TimeRange(RationalTime(74.0, 24.0), RationalTime(30.0, 24.0)), TimeRange(RationalTime(104.0, 24.0), RationalTime(25.0, 24.0)) }); test_edit_slice_transitions( RationalTime(23.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(23.0, 24.0)), TimeRange(RationalTime(23.0, 24.0), RationalTime(1.0, 24.0)), TimeRange(RationalTime(24.0, 24.0), RationalTime(50.0, 24.0)), TimeRange(RationalTime(74.0, 24.0), RationalTime(30.0, 24.0)), TimeRange(RationalTime(104.0, 24.0), RationalTime(25.0, 24.0)) }); }); tests.add_test("test_edit_overwrite_0", [] { // Create a track with one clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite past the clip. otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::ErrorStatus error_status; otio::algo::overwrite( clip_1, track, TimeRange(RationalTime(48.0, 24.0), RationalTime(24.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); assertEqual(track->children().size(), 3); assert(otio::dynamic_retainer_cast(track->children()[1])); const RationalTime duration = track->duration(); assert(duration == otio::RationalTime(72.0, 24.0)); auto range = clip_1->trimmed_range_in_parent().value(); assertEqual( range, otio::TimeRange( otio::RationalTime(48.0, 24.0), otio::RationalTime(24.0, 24.0))); }); tests.add_test("test_edit_overwrite_1", [] { // Create a track with one clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite past the clip. otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::ErrorStatus error_status; otio::algo::overwrite( clip_1, track, TimeRange(RationalTime(48.0, 24.0), RationalTime(24.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); assertEqual(track->children().size(), 3); assert(otio::dynamic_retainer_cast(track->children()[1])); const RationalTime duration = track->duration(); assert(duration == otio::RationalTime(72.0, 24.0)); auto range = clip_1->trimmed_range_in_parent().value(); assertEqual( range, otio::TimeRange( otio::RationalTime(48.0, 24.0), otio::RationalTime(24.0, 24.0))); }); tests.add_test("test_edit_overwrite_2", [] { // Create a track with one clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(1.0, 24.0), otio::RationalTime(100.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite a single frame inside the clip. otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(1.0, 24.0), otio::RationalTime(1.0, 24.0))); otio::ErrorStatus error_status; otio::algo::overwrite( clip_1, track, TimeRange(RationalTime(42.0, 24.0), RationalTime(1.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime duration = track->duration(); assert(duration == otio::RationalTime(100.0, 24.0)); assert_clip_ranges(track, { TimeRange( RationalTime(1.0, 24.0), RationalTime(42.0, 24.0)), TimeRange( RationalTime(1.0, 24.0), RationalTime(1.0, 24.0)), TimeRange( RationalTime(44.0, 24.0), RationalTime(57.0, 24.0)) }); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(42.0, 24.0)), TimeRange( RationalTime(42.0, 24.0), RationalTime(1.0, 24.0)), TimeRange( RationalTime(43.0, 24.0), RationalTime(57.0, 24.0)) }); }); tests.add_test("test_edit_overwrite_3", [] { // Create a track with two clips and overwrite a portion of both. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); // Overwrite both clips. otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::overwrite( clip_2, track, TimeRange(RationalTime(12.0, 24.0), RationalTime(24.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(12.0, 24.0)), TimeRange( RationalTime(12.0, 24.0), RationalTime(24.0, 24.0)), TimeRange( RationalTime(36.0, 24.0), RationalTime(12.0, 24.0)) }); }); tests.add_test("test_edit_overwrite_4", [] { // Create a track with one long clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(704.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite one portion of the clip. otio::SerializableObject::Retainer over_1 = new otio::Clip( "over_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(1.0, 24.0))); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::overwrite( over_1, track, TimeRange(RationalTime(272.0, 24.0), RationalTime(1.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(272.0, 24.0)), TimeRange( RationalTime(272.0, 24.0), RationalTime(1.0, 24.0)), TimeRange( RationalTime(273.0, 24.0), RationalTime(431.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(272.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(1.0, 24.0)), TimeRange( RationalTime(273.0, 24.0), RationalTime(431.0, 24.0)) }); }); tests.add_test("test_edit_overwrite_5", [] { // Create a track with one long clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 30.0), otio::RationalTime(704.0, 30.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite one portion of the clip. otio::SerializableObject::Retainer over_1 = new otio::Clip( "over_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 30.0), otio::RationalTime(1.0, 30.0))); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::overwrite( over_1, track, TimeRange(RationalTime(272.0, 30.0), RationalTime(1.0, 30.0)), true, nullptr, &error_status); assert(!otio::is_error(error_status)); { const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); } assert_track_ranges(track, { TimeRange( RationalTime(0.0, 30.0), RationalTime(272.0, 30.0)), TimeRange( RationalTime(272.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(273.0, 30.0), RationalTime(431.0, 30.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 30.0), RationalTime(272.0, 30.0)), TimeRange( RationalTime(0.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(273.0, 30.0), RationalTime(431.0, 30.0)) }); // Overwrite another portion of the clip. otio::SerializableObject::Retainer over_2 = new otio::Clip( "over_2", nullptr, otio::TimeRange( otio::RationalTime(0.0, 30.0), otio::RationalTime(1.0, 30.0))); otio::algo::overwrite( over_2, track, TimeRange(RationalTime(360.0, 30.0), RationalTime(1.0, 30.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); { const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); } assert_track_ranges(track, { TimeRange( RationalTime(0.0, 30.0), RationalTime(272.0, 30.0)), TimeRange( RationalTime(272.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(273.0, 30.0), RationalTime(87.0, 30.0)), TimeRange( RationalTime(360.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(361.0, 30.0), RationalTime(343.0, 30.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 30.0), RationalTime(272.0, 30.0)), TimeRange( RationalTime(0.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(273.0, 30.0), RationalTime(87.0, 30.0)), TimeRange( RationalTime(0.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(361.0, 30.0), RationalTime(343.0, 30.0)) }); // Overwrite the same portion of the clip. otio::SerializableObject::Retainer over_3 = new otio::Clip( "over_3", nullptr, otio::TimeRange( otio::RationalTime(0.0, 30.0), otio::RationalTime(1.0, 30.0))); otio::algo::overwrite( over_3, track, TimeRange(RationalTime(360.0, 30.0), RationalTime(1.0, 30.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); { const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); } assert_track_ranges(track, { TimeRange( RationalTime(0.0, 30.0), RationalTime(272.0, 30.0)), TimeRange( RationalTime(272.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(273.0, 30.0), RationalTime(87.0, 30.0)), TimeRange( RationalTime(360.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(361.0, 30.0), RationalTime(343.0, 30.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 30.0), RationalTime(272.0, 30.0)), TimeRange( RationalTime(0.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(273.0, 30.0), RationalTime(87.0, 30.0)), TimeRange( RationalTime(0.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(361.0, 30.0), RationalTime(343.0, 30.0)) }); }); tests.add_test("test_edit_overwrite_6", [] { // Create a track with three clips of different rates. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, otio::TimeRange( otio::RationalTime(90.0, 30), otio::RationalTime(90.0, 30))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); // Overwrite one portion of the clip. otio::SerializableObject::Retainer over_1 = new otio::Clip( "over_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 30.0), otio::RationalTime(1.0, 30.0))); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::overwrite( over_1, track, TimeRange(RationalTime(137.0, 30.0), RationalTime(1.0, 30.0)), true, nullptr, &error_status); assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(90, 30.0), RationalTime(47.0, 30.0)), TimeRange( RationalTime(137.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(138.0, 30.0), RationalTime(42.0, 30.0)), TimeRange( RationalTime(180.0, 30.0), RationalTime(90.0, 30.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 23.98), RationalTime(71.94, 23.98)), TimeRange( RationalTime(0.0, 30.0), RationalTime(47.0, 30.0)), TimeRange( RationalTime(0.0, 30.0), RationalTime(1.0, 30.0)), TimeRange( RationalTime(48.0, 30.0), RationalTime(42.0, 30.0)), TimeRange( RationalTime(90.0, 30.0), RationalTime(90.0, 30.0)) }); }); tests.add_test("test_edit_overwrite_7", [] { // Create a track with one long clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(704.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite past the clip, creating a gap. otio::SerializableObject::Retainer over_1 = new otio::Clip( "over_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(1.0, 24.0))); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::overwrite( over_1, track, TimeRange(RationalTime(800.0, 24.0), RationalTime(1.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, RationalTime(801.0, 24.0)); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(704.0, 24.0)), TimeRange( RationalTime(704.0, 24.0), RationalTime(96.0, 24.0)), TimeRange( RationalTime(800.0, 24.0), RationalTime(1.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(704.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(96.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(1.0, 24.0)) }); }); tests.add_test("test_edit_overwrite_8", [] { // Create a track with one long clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(704.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite before the clip, creating a gap. otio::SerializableObject::Retainer over_1 = new otio::Clip( "over_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(1.0, 24.0))); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::overwrite( over_1, track, TimeRange(RationalTime(-30.0, 24.0), RationalTime(1.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, RationalTime(734.0, 24.0)); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(1.0, 24.0)), TimeRange( RationalTime(1.0, 24.0), RationalTime(29.0, 24.0)), TimeRange( RationalTime(30.0, 24.0), RationalTime(704.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(1.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(29.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(704.0, 24.0)) }); }); tests.add_test("test_edit_overwrite_9", [] { // Create a track with one long clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(704.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite before the clip, creating a gap. otio::SerializableObject::Retainer over_1 = new otio::Clip( "over_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(100.0, 24.0))); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::overwrite( over_1, track, TimeRange(RationalTime(-30.0, 24.0), RationalTime(70.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(70.0, 24.0)), TimeRange( RationalTime(70.0, 24.0), RationalTime(634.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(70.0, 24.0)), TimeRange( RationalTime(70.0, 24.0), RationalTime(634.0, 24.0)) }); }); tests.add_test("test_edit_overwrite_10", [] { // Create a track with one long clip. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(704.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); // Overwrite before the clip, creating a gap. otio::SerializableObject::Retainer over_1 = new otio::Clip( "over_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(100.0, 24.0))); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::overwrite( over_1, track, TimeRange(RationalTime(20.0, 24.0), RationalTime(70.0, 24.0)), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); //assert_duration(new_duration, duration); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(70.0, 24.0)), TimeRange( RationalTime(90.0, 24.0), RationalTime(614.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(70.0, 24.0)), TimeRange( RationalTime(90.0, 24.0), RationalTime(614.0, 24.0)) }); }); // Insert at middle of clip_0 tests.add_test("test_edit_insert_1", [] { // Create a track with two clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); // Insert in clip 0. otio::SerializableObject::Retainer insert_1 = new otio::Clip( "insert_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(12.0, 24.0))); otio::ErrorStatus error_status; otio::algo::insert( insert_1, track, RationalTime(12.0, 24.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); assertEqual(track->children().size(), 4); const RationalTime duration = track->duration(); assert_duration(duration, otime::RationalTime(60.0,24.0)); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(12.0, 24.0)), TimeRange( RationalTime(12.0, 24.0), RationalTime(12.0, 24.0)), TimeRange( RationalTime(24.0, 24.0), RationalTime(12.0, 24.0)), TimeRange( RationalTime(36.0, 24.0), RationalTime(24.0, 24.0)) }); }); // Insert at start of clip_0. tests.add_test("test_edit_insert_2", [] { // Create a track with two clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); // Insert in clip 0. otio::SerializableObject::Retainer insert_1 = new otio::Clip( "insert_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(12.0, 24.0))); otio::ErrorStatus error_status; otio::algo::insert( insert_1, track, RationalTime(0.0, 24.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); assertEqual(track->children().size(), 3); const RationalTime duration = track->duration(); assert_duration(duration, otime::RationalTime(60.0,24.0)); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(12.0, 24.0)), TimeRange( RationalTime(12.0, 24.0), RationalTime(24.0, 24.0)), TimeRange( RationalTime(36.0, 24.0), RationalTime(24.0, 24.0)) }); }); // Insert at start of clip_1 (insert at 0 index). tests.add_test("test_edit_insert_3", [] { // Create a track with two clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); // Insert in clip 0. otio::SerializableObject::Retainer insert_1 = new otio::Clip( "insert_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(12.0, 24.0))); otio::ErrorStatus error_status; otio::algo::insert( insert_1, track, RationalTime(-1.0, 24.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); assertEqual(track->children().size(), 3); const RationalTime duration = track->duration(); assertEqual(duration, otime::RationalTime(60.0,24.0)); auto range = clip_0->trimmed_range_in_parent().value(); assertEqual( range, otio::TimeRange( otio::RationalTime(12.0, 24.0), otio::RationalTime(24.0, 24.0))); range = clip_1->trimmed_range_in_parent().value(); assertEqual( range, otio::TimeRange( otio::RationalTime(36.0, 24.0), otio::RationalTime(24.0, 24.0))); range = insert_1->trimmed_range_in_parent().value(); assertEqual( range, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(12.0, 24.0))); }); // Insert at start of clip_1. tests.add_test("test_edit_insert_4", [] { // Create a track with two clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); // Insert in clip 0. otio::SerializableObject::Retainer insert_1 = new otio::Clip( "insert_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(12.0, 24.0))); otio::ErrorStatus error_status; otio::algo::insert( insert_1, track, RationalTime(24.0, 24.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime duration = track->duration(); assert_duration(duration, otime::RationalTime(60.0,24.0)); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), TimeRange( RationalTime(24.0, 24.0), RationalTime(12.0, 24.0)), TimeRange( RationalTime(36.0, 24.0), RationalTime(24.0, 24.0)) }); }); // Insert at end of clip_1 (append at end). tests.add_test("test_edit_insert_4", [] { // Create a track with two clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); // Insert in clip 0. otio::SerializableObject::Retainer insert_1 = new otio::Clip( "insert_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(12.0, 24.0))); otio::ErrorStatus error_status; otio::algo::insert( insert_1, track, RationalTime(48.0, 24.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime duration = track->duration(); assert_duration(duration, otime::RationalTime(60.0,24.0)); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), TimeRange( RationalTime(24.0, 24.0), RationalTime(24.0, 24.0)), TimeRange( RationalTime(48.0, 24.0), RationalTime(12.0, 24.0)) }); }); // Insert near the beginning of clip_0. tests.add_test("test_edit_insert_5", [] { // Create a track with two clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); // Insert in clip 0. otio::SerializableObject::Retainer insert_1 = new otio::Clip( "insert_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(12.0, 24.0))); otio::ErrorStatus error_status; otio::algo::insert( insert_1, track, RationalTime(1.0, 24.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime duration = track->duration(); assert_duration(duration, otime::RationalTime(60.0,24.0)); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(1.0, 24.0)), TimeRange( RationalTime(1.0, 24.0), RationalTime(12.0, 24.0)), TimeRange( RationalTime(13.0, 24.0), RationalTime(23.0, 24.0)), TimeRange( RationalTime(36.0, 24.0), RationalTime(24.0, 24.0)) }); }); // Insert near the end of clip_1. tests.add_test("test_edit_insert_6", [] { // Create a track with two clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); // Insert in clip 0. otio::SerializableObject::Retainer insert_1 = new otio::Clip( "insert_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(12.0, 24.0))); otio::ErrorStatus error_status; otio::algo::insert( insert_1, track, RationalTime(47.0, 24.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime duration = track->duration(); assert_duration(duration, otime::RationalTime(60.0,24.0)); assert_track_ranges(track, { otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(24.0, 24.0)), otio::TimeRange( otio::RationalTime(24.0, 24.0), otio::RationalTime(23.0, 24.0)), otio::TimeRange( otio::RationalTime(47.0, 24.0), otio::RationalTime(12.0, 24.0)), otio::TimeRange( otio::RationalTime(59.0, 24.0), otio::RationalTime(1.0, 24.0)), }); }); // Insert at the end of clip_1. tests.add_test("test_edit_insert_7", [] { otio::ErrorStatus error_status; // Create a track with three clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "PSR63_2012-06-02", nullptr, otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "Dinky_2015-06-11", nullptr, otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "BART_2021-02-07", nullptr, otio::TimeRange( otio::RationalTime(90.0, 30.0), otio::RationalTime(90.0, 30.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); const RationalTime duration = track->duration(); track->remove_child(1); const RationalTime new_duration = track->duration(); assert_duration(new_duration, RationalTime(180.0, 30.0)); assert_clip_ranges(track, { otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98)), otio::TimeRange( otio::RationalTime(90.0, 30.0), otio::RationalTime(90.0, 30.0)), }); // Insert at end of clip 2. otio::algo::insert( clip_1, track, RationalTime(180.0, 30.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); { const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); } assert_clip_ranges(track, { otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98)), otio::TimeRange( otio::RationalTime(90.0, 30.0), otio::RationalTime(90.0, 30.0)), otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98)), }); track->remove_child(2); { const RationalTime new_duration = track->duration(); assert_duration(new_duration, RationalTime(180.0, 30.0)); } assert_clip_ranges(track, { otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98)), otio::TimeRange( otio::RationalTime(90.0, 30.0), otio::RationalTime(90.0, 30.0)), }); // Insert at end of clip 1. otio::algo::insert( clip_1, track, RationalTime(90.0, 30.0), true, nullptr, &error_status); { const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); } assert_clip_ranges(track, { otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98)), otio::TimeRange( otio::RationalTime(0.0, 23.98), otio::RationalTime(71.94, 23.98)), otio::TimeRange( otio::RationalTime(90.0, 30.0), otio::RationalTime(90.0, 30.0)), }); }); // Insert at the middle of clip 0 tests.add_test("test_edit_insert_8", [] { otio::ErrorStatus error_status; // Create a track with three clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "spiderman", nullptr, otio::TimeRange( otio::RationalTime(1575360, 23.976024), otio::RationalTime(3809.0, 23.976024))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "spider insert", nullptr, otio::TimeRange( otio::RationalTime(1575360, 23.976024), otio::RationalTime(1.0, 23.976024))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); debug_track_ranges("START", track); // Insert at end of clip 2. otio::algo::insert( clip_1, track, RationalTime(141.0, 23.976024), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); // { // const RationalTime new_duration = track->duration(); // assert_duration(new_duration, duration); // } assert_clip_ranges(track, { otio::TimeRange( otio::RationalTime(1575360.0, 23.976024), otio::RationalTime(141.0, 23.976024)), otio::TimeRange( otio::RationalTime(1575360, 23.976024), otio::RationalTime(1.0, 23.976024)), otio::TimeRange( otio::RationalTime(1575502.0, 23.976024), otio::RationalTime(3668.0, 23.976024)), }); assert_track_ranges(track, { otio::TimeRange( otio::RationalTime(0.0, 23.976024), otio::RationalTime(141.0, 23.976024)), otio::TimeRange( otio::RationalTime(141.0, 23.976024), otio::RationalTime(1, 23.976024)), otio::TimeRange( otio::RationalTime(142.0, 23.976024), otio::RationalTime(3668.0, 23.976024)), }); }); tests.add_test("test_edit_slip", [] { const TimeRange media_range( RationalTime(-15.0, 24.0), RationalTime(63.0, 24.0)); const TimeRange clip_range( RationalTime(0.0, 24.0), RationalTime(36.0, 24.0)); // Slip +5 frames. test_edit_slip( media_range, clip_range, RationalTime(5.0, 24.0), TimeRange(RationalTime(5.0, 24.0), RationalTime(36.0, 24.0))); // Slip +12 frames. test_edit_slip( media_range, clip_range, RationalTime(12.0, 24.0), TimeRange(RationalTime(12.0, 24.0), RationalTime(36.0, 24.0))); // Slip +20 frames. test_edit_slip( media_range, clip_range, RationalTime(20.0, 24.0), TimeRange(RationalTime(12.0, 24.0), RationalTime(36.0, 24.0))); // Slip -5 frames test_edit_slip( media_range, clip_range, RationalTime(-5.0, 24.0), TimeRange(RationalTime(-5.0, 24.0), RationalTime(36.0, 24.0))); // Slip -15 frames test_edit_slip( media_range, clip_range, RationalTime(-15.0, 24.0), TimeRange(RationalTime(-15.0, 24.0), RationalTime(36.0, 24.0))); // Slip -30 frames test_edit_slip( media_range, clip_range, RationalTime(-30.0, 24.0), TimeRange(RationalTime(-15.0, 24.0), RationalTime(36.0, 24.0))); }); tests.add_test("test_edit_slide", [] { TimeRange media_range( RationalTime(0.0, 24.0), RationalTime(48.0, 24.0)); // Slide 0. No change. test_edit_slide( media_range, RationalTime(0.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), TimeRange(RationalTime(24.0, 24.0), RationalTime(30.0, 24.0)), TimeRange(RationalTime(54.0, 24.0), RationalTime(40.0, 24.0)) }); // Slide right +12. test_edit_slide( media_range, RationalTime(12.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(36.0, 24.0)), TimeRange(RationalTime(36.0, 24.0), RationalTime(30.0, 24.0)), TimeRange(RationalTime(66.0, 24.0), RationalTime(40.0, 24.0)) }); // Slide right +48, which will clamp. test_edit_slide( media_range, RationalTime(48.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(48.0, 24.0)), TimeRange(RationalTime(48.0, 24.0), RationalTime(30.0, 24.0)), TimeRange(RationalTime(78.0, 24.0), RationalTime(40.0, 24.0)) }); // Slide left -10. test_edit_slide( media_range, RationalTime(-10.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(14.0, 24.0)), TimeRange(RationalTime(14.0, 24.0), RationalTime(30.0, 24.0)), TimeRange(RationalTime(44.0, 24.0), RationalTime(40.0, 24.0)) }); // Slide left -24, which is invalid. No change. test_edit_slide( media_range, RationalTime(-24.0, 24.0), { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), TimeRange(RationalTime(24.0, 24.0), RationalTime(30.0, 24.0)), TimeRange(RationalTime(54.0, 24.0), RationalTime(40.0, 24.0)) }); }); tests.add_test("test_edit_trim_1", [] { // Create a track with one gap and two clips. otio::SerializableObject::Retainer clip_0 = new otio::Gap( otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(20.0, 24.0)), "gap_0"); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(50.0, 24.0))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(10.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::trim( clip_1, RationalTime(5.0, 24.0), RationalTime(0.0, 24.0), nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(25.0, 24.0), RationalTime(45.0, 24.0)), TimeRange( RationalTime(70.0, 24.0), RationalTime(10.0, 24.0)) }); }); // Test trim delta_out right (no change due to clip). tests.add_test("test_edit_trim_2", [] { // Create a track with one gap and two clips. otio::SerializableObject::Retainer clip_0 = new otio::Gap( otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(20.0, 24.0)), "gap_0"); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(50.0, 24.0))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(10.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::trim( clip_1, RationalTime(0.0, 24.0), RationalTime(5.0, 24.0), nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(50.0, 24.0)), TimeRange( RationalTime(70.0, 24.0), RationalTime(10.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(50.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(10.0, 24.0)) }); }); // Test trim delta_out left (create a gap) tests.add_test("test_edit_trim_3", [] { // Create a track with one gap and two clips. otio::SerializableObject::Retainer clip_0 = new otio::Gap( otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(20.0, 24.0)), "gap_0"); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(50.0, 24.0))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(10.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; otio::algo::trim( clip_1, RationalTime(0.0, 24.0), RationalTime(-5.0, 24.0), nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(45.0, 24.0)), TimeRange( RationalTime(65.0, 24.0), RationalTime(5.0, 24.0)), TimeRange( RationalTime(70.0, 24.0), RationalTime(10.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(45.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(5.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(10.0, 24.0)) }); }); // Test ripple tests.add_test("test_edit_ripple_1", [] { test_edit_ripple( RationalTime(10.0, 24.0), RationalTime(0.0, 24.0), // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(35.0, 24.0), RationalTime(20.0, 24.0)) }, { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(15.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); tests.add_test("test_edit_ripple_2", [] { test_edit_ripple( RationalTime(-10.0, 24.0), RationalTime(0.0, 24.0), // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(30.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(30.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); tests.add_test("test_edit_ripple_3", [] { test_edit_ripple( RationalTime(0.0, 24.0), RationalTime(10.0, 24.0), // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(35.0, 24.0)), TimeRange( RationalTime(55.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(35.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); tests.add_test("test_edit_ripple_4", [] { test_edit_ripple( RationalTime(0.0, 24.0), RationalTime(-10.0, 24.0), // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(35.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); tests.add_test("test_edit_roll_1", [] { test_edit_roll( RationalTime(10.0, 24.0), RationalTime(0.0, 24.0), // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(30.0, 24.0)), TimeRange( RationalTime(30.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(30.0, 24.0)), TimeRange( RationalTime(15.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); tests.add_test("test_edit_roll_2", [] { test_edit_roll( RationalTime(-10.0, 24.0), RationalTime(0.0, 24.0), // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(15.0, 24.0), RationalTime(35.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(35.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); tests.add_test("test_edit_roll_3", [] { test_edit_roll( RationalTime(0.0, 24.0), RationalTime(10.0, 24.0), // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(40.0, 24.0)), TimeRange( RationalTime(60.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(40.0, 24.0)), TimeRange( RationalTime(15.0, 24.0), RationalTime(20.0, 24.0)) }); }); tests.add_test("test_edit_roll_4", [] { test_edit_roll( RationalTime(0.0, 24.0), RationalTime(-10.0, 24.0), // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(45.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)) }); }); // Add longer clip in gap as Fit reference point // (creates linearTimeWarp effect). tests.add_test("test_edit_fill_1", [] { test_edit_fill( TimeRange( RationalTime(0.0, 24.0), RationalTime(35.0, 24.0)), RationalTime(20.0, 24.0), ReferencePoint::Fit, // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(35.0, 24.0)), TimeRange( RationalTime(55.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(35.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); // Add longer clip at gap as Source reference point. // Stretches timeline. tests.add_test("test_edit_fill_2", [] { test_edit_fill( TimeRange( RationalTime(0.0, 24.0), RationalTime(35.0, 24.0)), RationalTime(20.0, 24.0), ReferencePoint::Source, // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(35.0, 24.0)), TimeRange( RationalTime(55.0, 24.0), RationalTime(5.0, 24.0)), }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(35.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(5.0, 24.0)), }); }); // Add equal clip in gap as Source reference point tests.add_test("test_edit_fill_3", [] { test_edit_fill( TimeRange( RationalTime(0.0, 24.0), RationalTime(30.0, 24.0)), RationalTime(20.0, 24.0), ReferencePoint::Source, // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(30.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(30.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); // Add shorter clip in gap as Source reference point tests.add_test("test_edit_fill_4", [] { test_edit_fill( TimeRange( RationalTime(0.0, 24.0), RationalTime(5.0, 24.0)), RationalTime(20.0, 24.0), ReferencePoint::Source, // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(5.0, 24.0)), TimeRange( RationalTime(25.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(5.0, 24.0)), TimeRange( RationalTime(10.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); // Add an equal clip (after trim) in gap as // Sequence reference point. tests.add_test("test_edit_fill_5", [] { test_edit_fill( TimeRange( RationalTime(0.0, 24.0), RationalTime(35.0, 24.0)), RationalTime(20.0, 24.0), ReferencePoint::Sequence, // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(30.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(30.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); // Add a longer clip in gap as Sequence reference point tests.add_test("test_edit_fill_6", [] { test_edit_fill( TimeRange( RationalTime(-10.0, 24.0), RationalTime(30.0, 24.0)), RationalTime(20.0, 24.0), ReferencePoint::Sequence, // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(35.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(15.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); // Add a shorter clip in gap as Sequence reference point tests.add_test("test_edit_fill_7", [] { test_edit_fill( TimeRange( RationalTime(10.0, 24.0), RationalTime(5.0, 24.0)), RationalTime(20.0, 24.0), ReferencePoint::Sequence, // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(5.0, 24.0)), TimeRange( RationalTime(25.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(10.0, 24.0), RationalTime(5.0, 24.0)), TimeRange( RationalTime(10.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); // Add a shorter clip in gap as Sequence reference point tests.add_test("test_edit_fill_8", [] { test_edit_fill( TimeRange( RationalTime(10.0, 24.0), RationalTime(5.0, 24.0)), RationalTime(20.0, 24.0), ReferencePoint::Sequence, // Clip in Track Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(20.0, 24.0), RationalTime(5.0, 24.0)), TimeRange( RationalTime(25.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(20.0, 24.0)) }, // Clip Ranges { TimeRange( RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), TimeRange( RationalTime(10.0, 24.0), RationalTime(5.0, 24.0)), TimeRange( RationalTime(10.0, 24.0), RationalTime(25.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(20.0, 24.0)) }); }); // Test remove middle clip tests.add_test("test_edit_remove_0", [] { // Create a track with three clips. otio::SerializableObject::Retainer clip_0 = new otio::Clip( "clip_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(50.0, 24.0))); otio::SerializableObject::Retainer clip_1 = new otio::Clip( "clip_1", nullptr, otio::TimeRange( otio::RationalTime(5.0, 24.0), otio::RationalTime(50.0, 24.0))); otio::SerializableObject::Retainer clip_2 = new otio::Clip( "clip_2", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(10.0, 24.0))); otio::SerializableObject::Retainer fill_0 = new otio::Clip( "fill_0", nullptr, otio::TimeRange( otio::RationalTime(0.0, 24.0), otio::RationalTime(10.0, 24.0))); otio::SerializableObject::Retainer track = new otio::Track(); track->append_child(clip_0); track->append_child(clip_1); track->append_child(clip_2); const RationalTime duration = track->duration(); otio::ErrorStatus error_status; // Remove second clip (creates a gap) otio::algo::remove( track, RationalTime(55.0, 24.0), true, nullptr, &error_status); // Asserts. assert(!otio::is_error(error_status)); { const RationalTime new_duration = track->duration(); assert_duration(new_duration, duration); } assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(50.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(50.0, 24.0)), TimeRange( RationalTime(100.0, 24.0), RationalTime(10.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(50.0, 24.0)), TimeRange( RationalTime(5.0, 24.0), RationalTime(50.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(10.0, 24.0)) }); // Remove second clip (which is now a gap, replacing it with fill_0) otio::algo::remove( track, RationalTime(55.0, 24.0), true, fill_0, &error_status); // Asserts. assert(!otio::is_error(error_status)); { const RationalTime new_duration = track->duration(); assert_duration(new_duration, RationalTime(70.0, 24.0)); } assert_track_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(50.0, 24.0)), TimeRange( RationalTime(50.0, 24.0), RationalTime(10.0, 24.0)), TimeRange( RationalTime(60.0, 24.0), RationalTime(10.0, 24.0)) }); assert_clip_ranges(track, { TimeRange( RationalTime(0.0, 24.0), RationalTime(50.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(10.0, 24.0)), TimeRange( RationalTime(0.0, 24.0), RationalTime(10.0, 24.0)) }); }); tests.run(argc, argv); return 0; } opentimelineio-0.18.1/tests/baselines/0000775000175000017500000000000015110656141015516 5ustar memeopentimelineio-0.18.1/tests/baselines/empty_timeline.json0000664000175000017500000000044415110656141021437 0ustar meme{ "OTIO_SCHEMA" : "Timeline.1", "global_start_time" : null, "metadata" : { "comments" : "adding some stuff to metadata to try out", "a number" : 1.0 }, "name" : "Example Timeline", "tracks" : { "FROM_TEST_FILE" : "empty_stack.json" } } opentimelineio-0.18.1/tests/baselines/empty_timerange.json0000664000175000017500000000030615110656141021601 0ustar meme{ "OTIO_SCHEMA" : "TimeRange.1", "start_time" : { "FROM_TEST_FILE" : "empty_rationaltime.json" }, "duration" : { "FROM_TEST_FILE" : "empty_rationaltime.json" } } opentimelineio-0.18.1/tests/baselines/empty_generator_reference.json0000664000175000017500000000031215110656141023627 0ustar meme{ "OTIO_SCHEMA" : "GeneratorReference.1", "available_range" : null, "available_image_bounds" : null, "generator_kind" : "", "metadata" : {}, "parameters" : {}, "name" : "" } opentimelineio-0.18.1/tests/baselines/empty_external_reference.json0000664000175000017500000000026515110656141023472 0ustar meme{ "OTIO_SCHEMA" : "ExternalReference.1", "available_range" : null, "available_image_bounds" : null, "metadata" : {}, "name" : "", "target_url" : "foo.bar" } opentimelineio-0.18.1/tests/baselines/empty_color.json0000664000175000017500000000015015110656141020741 0ustar meme{ "OTIO_SCHEMA": "Color.1", "r": 1.0, "g": 1.0, "b": 1.0, "a": 1.0, "name": "" }opentimelineio-0.18.1/tests/baselines/post_write_example.py0000664000175000017500000000106015110656141021777 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import os """This file is here to support the test_adapter_plugin unittest. If you want to learn how to write your own adapter plugin, please read: https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html """ def hook_function(in_timeline, argument_map=None): filepath = argument_map.get('_filepath') argument_map.update({'filesize': os.path.getsize(filepath)}) in_timeline.metadata.update(argument_map) return in_timeline opentimelineio-0.18.1/tests/baselines/empty_clip.json0000664000175000017500000000056515110656141020564 0ustar meme{ "OTIO_SCHEMA" : "Clip.2", "metadata" : {}, "name" : "test_clip", "source_range" : null, "markers" : [], "enabled" : true, "effects" : [], "active_media_reference_key": "DEFAULT_MEDIA", "color": null, "media_references" : { "DEFAULT_MEDIA" : { "FROM_TEST_FILE" : "empty_missingreference.json" } } } opentimelineio-0.18.1/tests/baselines/empty_missingreference.json0000664000175000017500000000022615110656141023157 0ustar meme{ "OTIO_SCHEMA" : "MissingReference.1", "available_range" : null, "available_image_bounds" : null, "metadata" : {}, "name" : "" } opentimelineio-0.18.1/tests/baselines/empty_serializable_collection.json0000664000175000017500000000021315110656141024504 0ustar meme{ "OTIO_SCHEMA" : "SerializableCollection.1", "metadata" : { "foo":"bar" }, "name" : "test", "children" : [] } opentimelineio-0.18.1/tests/baselines/media_linker_example.json0000664000175000017500000000013715110656141022550 0ustar meme{ "OTIO_SCHEMA" : "MediaLinker.1", "name" : "example", "filepath" : "example.py" } opentimelineio-0.18.1/tests/baselines/adapter_example.json0000664000175000017500000000017115110656141021543 0ustar meme{ "OTIO_SCHEMA" : "Adapter.1", "name" : "example", "filepath" : "example.py", "suffixes" : ["example"] } opentimelineio-0.18.1/tests/baselines/empty_rationaltime.json0000664000175000017500000000011615110656141022315 0ustar meme{ "OTIO_SCHEMA" : "RationalTime.1", "rate" : 1.0, "value" : 0.0 } opentimelineio-0.18.1/tests/baselines/plugin_module/0000775000175000017500000000000015110656141020361 5ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_jsonplugin/0000775000175000017500000000000015110656141023603 5ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_jsonplugin/__init__.py0000664000175000017500000000000015110656141025702 0ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_jsonplugin/plugin_manifest.json0000664000175000017500000000064415110656141027666 0ustar meme{ "OTIO_SCHEMA" : "PluginManifest.1", "adapters": [ { "OTIO_SCHEMA": "Adapter.1", "name": "mock_adapter_json", "filepath": "adapter.py", "suffixes": ["mockadapter"] } ], "media_linkers": [ { "OTIO_SCHEMA" : "MediaLinker.1", "name" : "mock_linker_json", "filepath" : "linker.py" } ] } opentimelineio-0.18.1/tests/baselines/plugin_module/otio_mockplugin/0000775000175000017500000000000015110656141023563 5ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_mockplugin/__init__.py0000664000175000017500000000166315110656141025702 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project from importlib import resources from opentimelineio.plugins import manifest """An example plugin package that generates its package manifest on demand. If you create a plugin that doesn't have a plugin_manifest.json, OTIO will attempt to call the plugin_manifest() function from the __init__.py directory. This would allow you to programmatically generate a manifest rather than have it be static on disk, allowing you to switch features on or off or do some template substitution or any other kind of procedural processing. This unit test uses a very simple example that just reads the manifest from a non-standard json file path. """ def plugin_manifest(): filepath = ( resources.files(__package__) / "unusually_named_plugin_manifest.json" ) return manifest.manifest_from_string( filepath.read_text() ) ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootopentimelineio-0.18.1/tests/baselines/plugin_module/otio_mockplugin/unusually_named_plugin_manifest.jsonopentimelineio-0.18.1/tests/baselines/plugin_module/otio_mockplugin/unusually_named_plugin_manifest.0000664000175000017500000000063215110656141032236 0ustar meme{ "OTIO_SCHEMA" : "PluginManifest.1", "adapters": [ { "OTIO_SCHEMA": "Adapter.1", "name": "mock_adapter", "filepath": "adapter.py", "suffixes": ["mockadapter"] } ], "media_linkers": [ { "OTIO_SCHEMA" : "MediaLinker.1", "name" : "mock_linker", "filepath" : "linker.py" } ] } opentimelineio-0.18.1/tests/baselines/plugin_module/otio_override_adapter/0000775000175000017500000000000015110656141024732 5ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_override_adapter/__init__.py0000664000175000017500000000000015110656141027031 0ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_override_adapter/plugin_manifest.json0000664000175000017500000000035115110656141031010 0ustar meme{ "OTIO_SCHEMA" : "PluginManifest.1", "adapters": [ { "OTIO_SCHEMA" : "Adapter.1", "name" : "otiod", "filepath" : "adapter.py", "suffixes" : ["otiod"] } ] } opentimelineio-0.18.1/tests/baselines/plugin_module/otio_override_adapter/adapter.py0000664000175000017500000000000015110656141026712 0ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_jsonplugin.egg-info/0000775000175000017500000000000015110656141025275 5ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_jsonplugin.egg-info/PKG-INFO0000664000175000017500000000051615110656141026374 0ustar memeMetadata-Version: 1.0 Name: otio-jsonplugin Version: 1.0.0 Summary: Dummy plugin used for testing. Home-page: http://opentimeline.io Author: Contributors to the OpenTimelineIO project Author-email: otio-discussion@lists.aswf.io License: Modified Apache 2.0 License Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: any opentimelineio-0.18.1/tests/baselines/plugin_module/otio_jsonplugin.egg-info/entry_points.txt0000664000175000017500000000007015110656141030570 0ustar meme[opentimelineio.plugins] mock_plugin = otio_jsonplugin opentimelineio-0.18.1/tests/baselines/plugin_module/otio_override_adapter.egg-info/0000775000175000017500000000000015110656141026424 5ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_override_adapter.egg-info/PKG-INFO0000664000175000017500000000052515110656141027523 0ustar memeMetadata-Version: 1.0 Name: otio-override-adapter Version: 1.0.0 Summary: Dummy Adapter used for testing. Home-page: http://opentimeline.io Author: Contributors to the OpenTimelineIO project Author-email: otio-discussion@lists.aswf.io License: Modified Apache 2.0 License Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: any opentimelineio-0.18.1/tests/baselines/plugin_module/otio_override_adapter.egg-info/entry_points.txt0000664000175000017500000000007615110656141031725 0ustar meme[opentimelineio.plugins] mock_plugin = otio_override_adapter opentimelineio-0.18.1/tests/baselines/plugin_module/otio_mockplugin.egg-info/0000775000175000017500000000000015110656141025255 5ustar memeopentimelineio-0.18.1/tests/baselines/plugin_module/otio_mockplugin.egg-info/PKG-INFO0000664000175000017500000000051615110656141026354 0ustar memeMetadata-Version: 1.0 Name: otio-mockplugin Version: 1.0.0 Summary: Dummy plugin used for testing. Home-page: http://opentimeline.io Author: Contributors to the OpenTimelineIO project Author-email: otio-discussion@lists.aswf.io License: Modified Apache 2.0 License Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: any opentimelineio-0.18.1/tests/baselines/plugin_module/otio_mockplugin.egg-info/entry_points.txt0000664000175000017500000000007015110656141030550 0ustar meme[opentimelineio.plugins] mock_plugin = otio_mockplugin opentimelineio-0.18.1/tests/baselines/hookscript_example.json0000664000175000017500000000014315110656141022307 0ustar meme{ "OTIO_SCHEMA" : "HookScript.1", "name" : "example hook", "filepath" : "example.py" } opentimelineio-0.18.1/tests/baselines/empty_timetransform.json0000664000175000017500000000023515110656141022521 0ustar meme{ "OTIO_SCHEMA" : "TimeTransform.1", "offset" : { "FROM_TEST_FILE" : "empty_rationaltime.json" }, "scale" : 1.0, "rate" : -1.0 } opentimelineio-0.18.1/tests/baselines/adapter_plugin_manifest.plugin_manifest.json0000664000175000017500000000202215110656141026454 0ustar meme{ "OTIO_SCHEMA" : "PluginManifest.1", "adapters" : [ { "FROM_TEST_FILE" : "adapter_example.json" } ], "media_linkers" : [ { "FROM_TEST_FILE" : "media_linker_example.json" } ], "hook_scripts" : [ { "FROM_TEST_FILE" : "hookscript_example.json" }, { "FROM_TEST_FILE" : "post_write_hookscript_example.json" }, { "FROM_TEST_FILE" : "custom_adapter_hookscript_example.json" } ], "hooks" : { "pre_adapter_write" : ["example hook", "example hook"], "post_adapter_read" : [], "post_adapter_write" : ["post write example hook"], "post_media_linker" : ["example hook"], "custom_adapter_hook": ["custom adapter hook"] }, "version_manifests" : { "TEST_FAMILY_NAME": { "TEST_LABEL": { "ExampleSchema":2, "EnvVarTestSchema":1, "Clip": 1 } } } } opentimelineio-0.18.1/tests/baselines/example.py0000664000175000017500000000372415110656141017531 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """This file is here to support the test_adapter_plugin unittest. If you want to learn how to write your own adapter plugin, please read: https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html """ # `hook_function_argument_map` is only a required argument for adapters that implement # custom hooks. def read_from_file(filepath, suffix="", hook_function_argument_map=None): import opentimelineio as otio fake_tl = otio.schema.Timeline(name=filepath + str(suffix)) fake_tl.tracks.append(otio.schema.Track()) fake_tl.tracks[0].append(otio.schema.Clip(name=filepath + "_clip")) if (hook_function_argument_map and hook_function_argument_map.get("run_custom_hook", False)): return otio.hooks.run(hook="custom_adapter_hook", tl=fake_tl, extra_args=hook_function_argument_map) return fake_tl # `hook_function_argument_map` is only a required argument for adapters that implement # custom hooks. def read_from_string(input_str, suffix="", hook_function_argument_map=None): tl = read_from_file(input_str, suffix, hook_function_argument_map) return tl # this is only required for adapters that implement custom hooks def adapter_hook_names(): return ["custom_adapter_hook"] # in practice, these will be in separate plugins, but for simplicity in the # unit tests, we put this in the same file as the example adapter. def link_media_reference(in_clip, media_linker_argument_map): import opentimelineio as otio d = {'from_test_linker': True} d.update(media_linker_argument_map) return otio.schema.MissingReference( name=in_clip.name + "_tweaked", metadata=d ) # same thing for this hookscript def hook_function(in_timeline, argument_map=None): in_timeline.name = "hook ran and did stuff" in_timeline.metadata.update(argument_map) return in_timeline opentimelineio-0.18.1/tests/baselines/custom_adapter_hookscript_example.json0000664000175000017500000000020415110656141025377 0ustar meme{ "OTIO_SCHEMA" : "HookScript.1", "name" : "custom adapter hook", "filepath" : "custom_adapter_hookscript_example.py" } opentimelineio-0.18.1/tests/baselines/empty_stack.json0000664000175000017500000000046215110656141020736 0ustar meme{ "OTIO_SCHEMA" : "Stack.1", "metadata" : { "comments" : "adding some stuff to metadata to try out", "a number" : 1.0 }, "name" : "tracks", "source_range": null, "children" : [], "markers" : [], "enabled" : true, "color": null, "effects" : [] } opentimelineio-0.18.1/tests/baselines/empty_track.json0000664000175000017500000000051515110656141020734 0ustar meme{ "OTIO_SCHEMA" : "Track.1", "metadata" : { "comments" : "adding some stuff to metadata to try out", "a number" : 1.0 }, "name" : "test_track", "source_range": null, "children" : [], "markers" : [], "enabled" : true, "color": null, "effects" : [], "kind" : "Video" } opentimelineio-0.18.1/tests/baselines/empty_marker.json0000664000175000017500000000030215110656141021103 0ustar meme{ "OTIO_SCHEMA" : "Marker.2", "metadata" : {}, "name" : "", "color" : "RED", "comment" : "", "marked_range" : { "FROM_TEST_FILE" : "empty_timerange.json" } } opentimelineio-0.18.1/tests/baselines/empty_transition.json0000664000175000017500000000040415110656141022017 0ustar meme{ "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "", "transition_type": "", "in_offset": { "FROM_TEST_FILE" : "empty_rationaltime.json" }, "out_offset": { "FROM_TEST_FILE" : "empty_rationaltime.json" } } opentimelineio-0.18.1/tests/baselines/schemadef_example.json0000664000175000017500000000033715110656141022046 0ustar meme{ "OTIO_SCHEMA" : "PluginManifest.1", "schemadefs": [ { "OTIO_SCHEMA" : "SchemaDef.1", "name" : "example_schemadef", "filepath" : "example_schemadef.py" } ] } opentimelineio-0.18.1/tests/baselines/custom_adapter_hookscript_example.py0000664000175000017500000000077315110656141025071 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """This file is here to support the test_adapter_plugin unittest, specifically adapters that implement their own hooks. If you want to learn how to write your own adapter plugin, please read: https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html """ def hook_function(in_timeline, argument_map=None): in_timeline.metadata["custom_hook"] = dict(argument_map) return in_timeline opentimelineio-0.18.1/tests/baselines/post_write_hookscript_example.json0000664000175000017500000000017115110656141024567 0ustar meme{ "OTIO_SCHEMA" : "HookScript.1", "name" : "post write example hook", "filepath" : "post_write_example.py" } opentimelineio-0.18.1/tests/baselines/empty_effect.json0000664000175000017500000000016715110656141021067 0ustar meme{ "OTIO_SCHEMA" : "Effect.1", "name" : "", "effect_name" : "", "metadata" : {}, "enabled" : true } opentimelineio-0.18.1/tests/baselines/example_schemadef.py0000664000175000017500000000223715110656141021526 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """This file is here to support the test_schemadef_plugin unittest. If you want to learn how to write your own SchemaDef plugin, please read: https://opentimelineio.readthedocs.io/en/latest/tutorials/write-a-schemadef.html """ import opentimelineio as otio @otio.core.register_type class exampleSchemaDef(otio.core.SerializableObject): """Example of a SchemaDef plugin class for testing.""" _serializable_label = "exampleSchemaDef.1" _name = "exampleSchemaDef" def __init__( self, exampleArg=None, ): otio.core.SerializableObject.__init__(self) self.exampleArg = exampleArg exampleArg = otio.core.serializable_field( "exampleArg", doc=( "example of an arg passed to the exampleSchemaDef" ) ) def __str__(self): return 'exampleSchemaDef({})'.format( repr(self.exampleArg) ) def __repr__(self): return \ 'otio.schemadef.example_schemadef.exampleSchemaDef(exampleArg={})'.format( repr(self.exampleArg) ) opentimelineio-0.18.1/tests/baselines/empty_gap.json0000664000175000017500000000035115110656141020375 0ustar meme{ "OTIO_SCHEMA" : "Gap.1", "metadata" : {}, "name" : "", "markers" : [], "enabled" : true, "color": null, "effects" : [], "source_range" : { "FROM_TEST_FILE" : "empty_timerange.json" } } opentimelineio-0.18.1/tests/test_track.py0000664000175000017500000000572215110656141016274 0ustar meme#!/usr/bin/env python # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class TrackTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_append_none(self): tr = otio.schema.Track() self.assertRaises(TypeError, lambda x: tr.append(None)) def test_find_children(self): cl = otio.schema.Clip() tr = otio.schema.Track() tr.append(cl) result = tr.find_children(otio.schema.Clip) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl) def test_find_children_search_range(self): range = otio.opentime.TimeRange( otio.opentime.RationalTime(0.0, 24.0), otio.opentime.RationalTime(24.0, 24.0)) cl0 = otio.schema.Clip() cl0.source_range = range cl1 = otio.schema.Clip() cl1.source_range = range cl2 = otio.schema.Clip() cl2.source_range = range tr = otio.schema.Track() tr.append(cl0) tr.append(cl1) tr.append(cl2) result = tr.find_children(otio.schema.Clip, range) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl0) def test_find_children_shallow_search(self): cl0 = otio.schema.Clip() cl1 = otio.schema.Clip() st = otio.schema.Stack() st.append(cl1) tr = otio.schema.Track() tr.append(cl0) tr.append(st) result = tr.find_children(otio.schema.Clip, shallow_search=True) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl0) result = tr.find_children(otio.schema.Clip, shallow_search=False) self.assertEqual(len(result), 2) self.assertEqual(result[0], cl0) self.assertEqual(result[1], cl1) def test_find_children_stack(self): video_clip = otio.schema.Clip() video_clip.source_range = otio.opentime.TimeRange( otio.opentime.RationalTime(0.0, 30.0), otio.opentime.RationalTime(700.0, 30.0)) audio_clip = otio.schema.Clip() audio_clip.source_range = otio.opentime.TimeRange( otio.opentime.RationalTime(0.0, 30.0), otio.opentime.RationalTime(704.0, 30.0)) video_track = otio.schema.Track() audio_track = otio.schema.Track() stack = otio.schema.Stack() video_track.append(video_clip) audio_track.append(audio_clip) stack.append(video_track) stack.append(audio_track) time = otio.opentime.RationalTime(703.0, 30.0) one_frame = otio.opentime.RationalTime(1.0, 30.0) range = otio.opentime.TimeRange(time, one_frame) items = stack.find_children(search_range=range) self.assertEqual(len(items), 2) self.assertTrue(audio_clip in items) self.assertTrue(audio_track in items) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_v2d.py0000664000175000017500000000715515110656141015665 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import json import sys import unittest import opentimelineio.test_utils as otio_test_utils import opentimelineio as otio class V2dTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): v = otio.schema.V2d() self.assertEqual(v.x, 0.0) self.assertEqual(v.y, 0.0) v = otio.schema.V2d(1.0, 2.0) self.assertEqual(v.x, 1.0) self.assertEqual(v.y, 2.0) self.assertEqual(v[0], 1.0) self.assertEqual(v[1], 2.0) def test_str(self): v = otio.schema.V2d(1.0, 2.0) self.assertMultiLineEqual(str(v), "V2d(1.0, 2.0)") self.assertMultiLineEqual(repr(v), "otio.schema.V2d(x=1.0, y=2.0)") def test_equality(self): v1 = otio.schema.V2d(1.0, 2.0) v2 = otio.schema.V2d(3.0, 4.0) self.assertFalse(v1 == v2) self.assertTrue(v1 != v2) v3 = otio.schema.V2d(1.0, 2.0) self.assertTrue(v1 == v3) self.assertFalse(v1 != v3) self.assertTrue(v1.equalWithAbsError(v3, 0.0)) self.assertTrue(v1.equalWithRelError(v3, 0.0)) def test_math_ops(self): v1 = otio.schema.V2d(1.0, 2.0) v2 = otio.schema.V2d(3.0, 4.0) self.assertEqual(v1 ^ v2, 11.0) self.assertEqual(v1.dot(v2), 11.0) self.assertEqual(v1 % v2, -2.0) self.assertEqual(v1.cross(v2), -2.0) self.assertEqual(v1 + v2, otio.schema.V2d(4.0, 6.0)) self.assertEqual(v1 - v2, otio.schema.V2d(-2.0, -2.0)) self.assertEqual(v1 * v2, otio.schema.V2d(3.0, 8.0)) self.assertEqual(v1 / v2, otio.schema.V2d(1.0 / 3.0, 0.5)) v1 += v2 self.assertEqual(v1, otio.schema.V2d(4.0, 6.0)) v1 -= v2 self.assertEqual(v1, otio.schema.V2d(1.0, 2.0)) v1 *= v2 self.assertEqual(v1, otio.schema.V2d(3.0, 8.0)) v1 /= v2 self.assertEqual(v1, otio.schema.V2d(1.0, 2.0)) def test_geometry(self): v = otio.schema.V2d(3.0, 4.0) self.assertEqual(v.length(), 5.0) self.assertEqual(v.length2(), 25.0) def test_normalize(self): v = otio.schema.V2d(3.0, 4.0) self.assertEqual(v.normalized(), otio.schema.V2d(0.6, 0.8)) self.assertEqual(v.normalizedNonNull(), otio.schema.V2d(0.6, 0.8)) v2 = v v.normalize() v2.normalizeNonNull() self.assertEqual(v, otio.schema.V2d(0.6, 0.8)) self.assertEqual(v2, otio.schema.V2d(0.6, 0.8)) nv = otio.schema.V2d(0.0, 0.0) with self.assertRaises((ValueError, RuntimeError)): nv.normalizeExc() with self.assertRaises((ValueError, RuntimeError)): nv.normalizedExc() def test_limits(self): self.assertEqual(otio.schema.V2d.baseTypeLowest(), -1 * sys.float_info.max) self.assertEqual(otio.schema.V2d.baseTypeMax(), sys.float_info.max) self.assertEqual(otio.schema.V2d.baseTypeSmallest(), sys.float_info.min) self.assertEqual(otio.schema.V2d.baseTypeEpsilon(), sys.float_info.epsilon) def test_json_serialization(self): serialized = otio.adapters.otio_json.write_to_string(otio.schema.V2d(0.1, 0.2)) json_v2d = json.loads(serialized) self.assertEqual(json_v2d["OTIO_SCHEMA"], "V2d.1") def test_serialization_round_trip(self): v2d = otio.schema.V2d(0.3, 0.4) serialized = otio.adapters.otio_json.write_to_string(v2d) deserialized = otio.adapters.otio_json.read_from_string(serialized) self.assertEqual(v2d, deserialized) if __name__ == "__main__": unittest.main() opentimelineio-0.18.1/tests/test_json_backend.py0000775000175000017500000000766515110656141017623 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Unit tests for the JSON format OTIO Serializes to.""" import unittest import json import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils # local to test dir from tests import baseline_reader class TestJsonFormat(unittest.TestCase, otio_test_utils.OTIOAssertions): def setUp(self): self.maxDiff = None def check_against_baseline(self, obj, testname): baseline = baseline_reader.json_baseline(testname) self.assertDictEqual( baseline_reader.json_from_string( otio.adapters.otio_json.write_to_string(obj) ), baseline ) baseline_data = otio.adapters.otio_json.read_from_string( json.dumps(baseline) ) if isinstance(baseline_data, dict): raise TypeError("did not deserialize correctly") self.assertJsonEqual(obj, baseline_data) def test_rationaltime(self): rt = otio.opentime.RationalTime() self.check_against_baseline(rt, "empty_rationaltime") def test_timerange(self): tr = otio.opentime.TimeRange() self.check_against_baseline(tr, "empty_timerange") def test_timetransform(self): tt = otio.opentime.TimeTransform() self.check_against_baseline(tt, "empty_timetransform") def test_color(self): tt = otio.core.Color() self.check_against_baseline(tt, "empty_color") def test_track(self): st = otio.schema.Track( name="test_track", metadata={ "comments": "adding some stuff to metadata to try out", "a number": 1.0 } ) self.check_against_baseline(st, "empty_track") def test_stack(self): st = otio.schema.Stack( name="tracks", metadata={ "comments": "adding some stuff to metadata to try out", "a number": 1.0 } ) self.check_against_baseline(st, "empty_stack") def test_timeline(self): tl = otio.schema.Timeline( name="Example Timeline", metadata={ "comments": "adding some stuff to metadata to try out", "a number": 1.0 } ) tl.tracks.metadata.update({ "comments": "adding some stuff to metadata to try out", "a number": 1.0 }) self.check_against_baseline(tl, "empty_timeline") def test_clip(self): cl = otio.schema.Clip( name="test_clip", media_reference=otio.schema.MissingReference() ) self.check_against_baseline(cl, "empty_clip") def test_gap(self): fl = otio.schema.Gap() self.check_against_baseline(fl, "empty_gap") def test_missing_reference(self): mr = otio.schema.MissingReference() self.check_against_baseline(mr, "empty_missingreference") def test_external_reference(self): mr = otio.schema.ExternalReference(target_url="foo.bar") self.check_against_baseline(mr, "empty_external_reference") def test_marker(self): mr = otio.schema.Marker() self.check_against_baseline(mr, "empty_marker") def test_effect(self): mr = otio.schema.Effect() self.check_against_baseline(mr, "empty_effect") def test_transition(self): trx = otio.schema.Transition() self.check_against_baseline(trx, "empty_transition") def test_serializable_collection(self): tr = otio.schema.SerializableCollection( name="test", metadata={"foo": "bar"} ) self.check_against_baseline(tr, "empty_serializable_collection") def test_generator_reference(self): trx = otio.schema.GeneratorReference() self.check_against_baseline(trx, "empty_generator_reference") if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_stack_algo.cpp0000664000175000017500000001631315110656141017427 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentime/timeRange.h" #include "utils.h" #include #include #include #include #include namespace otime = opentime::OPENTIME_VERSION; namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; int main(int argc, char** argv) { Tests tests; tests.add_test( "test_flatten_stack_01", [] { using namespace otio; otio::RationalTime rt_0_24{0, 24}; otio::RationalTime rt_150_24{150, 24}; otio::TimeRange tr_0_150_24{rt_0_24, rt_150_24}; // all three clips are identical, but placed such that A is over B and // has no gap or end over C // 0 150 300 // [ A ] // [ B | C ] // // should flatten to: // [ A | C ] otio::SerializableObject::Retainer cl_A = new otio::Clip("track1_A", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_B = new otio::Clip("track1_B", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_C = new otio::Clip("track1_C", nullptr, tr_0_150_24); otio::SerializableObject::Retainer tr_over = new otio::Track(); tr_over->append_child(cl_A); otio::SerializableObject::Retainer tr_under = new otio::Track(); tr_under->append_child(cl_B); tr_under->append_child(cl_C); otio::SerializableObject::Retainer st = new otio::Stack(); st->append_child(tr_under); st->append_child(tr_over); auto result = flatten_stack(st); assertEqual(result->children()[0]->name(), std::string("track1_A")); assertEqual(result->children().size(), 2); assertEqual(result->duration().value(), 300); }); tests.add_test( "test_flatten_stack_02", [] { using namespace otio; otio::RationalTime rt_0_24{0, 24}; otio::RationalTime rt_150_24{150, 24}; otio::TimeRange tr_0_150_24{rt_0_24, rt_150_24}; // all four clips are identical, but placed such that A is over B and // has no gap or end over C. The bottom track is also shorter. // 0 150 300 // [ A ] // [ B | C ] // [ D ] // // should flatten to: // [ A | C ] otio::SerializableObject::Retainer cl_A = new otio::Clip("track1_A", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_B = new otio::Clip("track1_B", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_C = new otio::Clip("track1_C", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_D = new otio::Clip("track1_D", nullptr, tr_0_150_24); otio::SerializableObject::Retainer tr_top = new otio::Track(); tr_top->append_child(cl_A); otio::SerializableObject::Retainer tr_middle = new otio::Track(); tr_middle->append_child(cl_B); tr_middle->append_child(cl_C); otio::SerializableObject::Retainer tr_bottom = new otio::Track(); tr_bottom->append_child(cl_D); otio::SerializableObject::Retainer st = new otio::Stack(); st->append_child(tr_bottom); st->append_child(tr_middle); st->append_child(tr_top); auto result = flatten_stack(st); assertEqual(result->children()[0]->name(), std::string("track1_A")); assertEqual(result->children().size(), 2); assertEqual(result->duration().value(), 300); }); tests.add_test( "test_flatten_stack_03", [] { using namespace otio; otio::RationalTime rt_0_24{0, 24}; otio::RationalTime rt_150_24{150, 24}; otio::TimeRange tr_0_150_24{rt_0_24, rt_150_24}; // all three clips are identical but the middle track is empty // 0 150 300 // [ A ] // [] // [ B | C ] // // should flatten to: // [ A | C ] otio::SerializableObject::Retainer cl_A = new otio::Clip("track1_A", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_B = new otio::Clip("track1_B", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_C = new otio::Clip("track1_C", nullptr, tr_0_150_24); otio::SerializableObject::Retainer tr_top = new otio::Track(); tr_top->append_child(cl_A); otio::SerializableObject::Retainer tr_middle = new otio::Track(); otio::SerializableObject::Retainer tr_bottom = new otio::Track(); tr_bottom->append_child(cl_B); tr_bottom->append_child(cl_C); otio::SerializableObject::Retainer st = new otio::Stack(); st->append_child(tr_bottom); st->append_child(tr_middle); st->append_child(tr_top); auto result = flatten_stack(st); assertEqual(result->children()[0]->name(), std::string("track1_A")); assertEqual(result->children().size(), 2); assertEqual(result->duration().value(), 300); }); tests.add_test( "test_flatten_vector_01", [] { using namespace otio; otio::RationalTime rt_0_24{0, 24}; otio::RationalTime rt_150_24{150, 24}; otio::TimeRange tr_0_150_24{rt_0_24, rt_150_24}; // all three clips are identical, but placed such that A is over B and // has no gap or end over C, tests vector version // 0 150 300 // [ A ] // [ B | C ] // // should flatten to: // [ A | C ] otio::SerializableObject::Retainer cl_A = new otio::Clip("track1_A", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_B = new otio::Clip("track1_B", nullptr, tr_0_150_24); otio::SerializableObject::Retainer cl_C = new otio::Clip("track1_C", nullptr, tr_0_150_24); otio::SerializableObject::Retainer tr_over = new otio::Track(); tr_over->append_child(cl_A); otio::SerializableObject::Retainer tr_under = new otio::Track(); tr_under->append_child(cl_B); tr_under->append_child(cl_C); std::vector st; st.push_back(tr_under); st.push_back(tr_over); auto result = flatten_stack(st); assertEqual(result->children()[0]->name(), std::string("track1_A")); assertEqual(result->children().size(), 2); assertEqual(result->duration().value(), 300); }); tests.run(argc, argv); return 0; }opentimelineio-0.18.1/tests/test_opentime.cpp0000664000175000017500000001504115110656141017135 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include namespace otime = opentime::OPENTIME_VERSION; int main(int argc, char** argv) { Tests tests; tests.add_test("test_create", [] { double t_val = 30.2; otime::RationalTime t(t_val); assertEqual(t.value(), t_val); t = otime::RationalTime(); assertEqual(t.value(), 0.0); assertEqual(t.rate(), 1.0); }); tests.add_test("test_valid", [] { otime::RationalTime t1(0.0, 0.0); assertTrue(t1.is_invalid_time()); assertFalse(t1.is_valid_time()); otime::RationalTime t2(0.0, 24.0); assertTrue(t2.is_valid_time()); assertFalse(t2.is_invalid_time()); }); tests.add_test("test_equality", [] { otime::RationalTime t1(30.2); assertEqual(t1, t1); otime::RationalTime t2(30.2); assertEqual(t1, t2); otime::RationalTime t3(60.4, 2.0); assertEqual(t1, t3); }); tests.add_test("test_inequality", [] { otime::RationalTime t1(30.2); assertEqual(t1, t1); otime::RationalTime t2(33.2); assertNotEqual(t1, t2); otime::RationalTime t3(30.2); assertFalse(t1 != t3); }); tests.add_test("test_strict_equality", [] { otime::RationalTime t1(30.2); assertTrue(t1.strictly_equal(t1)); otime::RationalTime t2(30.2); assertTrue(t1.strictly_equal(t2)); otime::RationalTime t3(60.4, 2.0); assertFalse(t1.strictly_equal(t3)); }); tests.add_test("test_rounding", [] { otime::RationalTime t1(30.2); assertEqual(t1.floor(), otime::RationalTime(30.0)); assertEqual(t1.ceil(), otime::RationalTime(31.0)); assertEqual(t1.round(), otime::RationalTime(30.0)); otime::RationalTime t2(30.8); assertEqual(t2.floor(), otime::RationalTime(30.0)); assertEqual(t2.ceil(), otime::RationalTime(31.0)); assertEqual(t2.round(), otime::RationalTime(31.0)); }); tests.add_test("test_from_time_string", [] { std::string time_string = "0:12:04"; auto t = otime::RationalTime(24 * (12 * 60 + 4), 24); auto time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); }); tests.add_test("test_from_time_string24", [] { std::string time_string = "00:00:00.041667"; auto t = otime::RationalTime(1, 24); auto time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "00:00:01"; t = otime::RationalTime(24, 24); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "00:01:00"; t = otime::RationalTime(60 * 24, 24); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "01:00:00"; t = otime::RationalTime(60 * 60 * 24, 24); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "24:00:00"; t = otime::RationalTime(24 * 60 * 60 * 24, 24); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "23:59:59.92"; t = otime::RationalTime((23 * 60 * 60 + 59 * 60 + 59.92) * 24, 24); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); }); tests.add_test("test_from_time_string25", [] { std::string time_string = "0:12:04.929792"; auto t = otime::RationalTime((12 * 60 + 4.929792) * 25, 25); auto time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "00:00:01"; t = otime::RationalTime(25, 25); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "0:1"; t = otime::RationalTime(25, 25); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "1"; t = otime::RationalTime(25, 25); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "00:01:00"; t = otime::RationalTime(60 * 25, 25); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "01:00:00"; t = otime::RationalTime(60 * 60 * 25, 25); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "24:00:00"; t = otime::RationalTime(24 * 60 * 60 * 25, 25); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); time_string = "23:59:59.92"; t = otime::RationalTime((23 * 60 * 60 + 59 * 60 + 59.92) * 25, 25); time_obj = otime::RationalTime::from_time_string(time_string, 24); assertTrue(t.almost_equal(time_obj, 0.001)); }); tests.add_test("test_create_range", [] { otime::RationalTime start(0.0, 24.0); otime::RationalTime duration(24.0, 24.0); otime::TimeRange r(start, duration); assertEqual(r.start_time(), start); assertEqual(r.duration(), duration); r = otime::TimeRange(0.0, 24.0, 24.0); assertEqual(r.start_time(), start); assertEqual(r.duration(), duration); r = otime::TimeRange(); assertEqual(r.start_time(), otime::RationalTime()); assertEqual(r.duration(), otime::RationalTime()); }); tests.add_test("test_valid_range", [] { otime::TimeRange r1(0.0, 0.0, 0.0); assertTrue(r1.is_invalid_range()); assertFalse(r1.is_valid_range()); otime::TimeRange r2(0.0, 24.0, 24.0); assertTrue(r2.is_valid_range()); assertFalse(r2.is_invalid_range()); otime::TimeRange r3(0.0, -24.0, 24.0); assertFalse(r3.is_valid_range()); assertTrue(r3.is_invalid_range()); }); tests.run(argc, argv); return 0; } opentimelineio-0.18.1/tests/sample_data/0000775000175000017500000000000015110656141016023 5ustar memeopentimelineio-0.18.1/tests/sample_data/multitrack.otio0000664000175000017500000003232515110656141021103 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "OTIO TEST - multitrack.Exported.01", "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "tech.fux (loop)-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 720 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Filler", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 83 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "out-b (loop)-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 722 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Filler 2", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 432 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "brokchrd (loop)-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 720 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Filler 3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 605 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Sequence", "source_range": null }, { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "ScopeReference", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 482 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "t-hawk (loop)-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 480 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "ScopeReference 2", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 2320 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Sequence 2", "source_range": null }, { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "ScopeReference 3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 1198 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "KOLL-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 640 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "ScopeReference 4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 1444 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Sequence 3", "source_range": null } ], "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "NestedScope", "source_range": null } }opentimelineio-0.18.1/tests/sample_data/nested_example.otio0000664000175000017500000003167215110656141021725 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "My Timeline", "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "Normal Clip 1", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 238 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 135 } } }, { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "Clip Inside A Stack 1", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 37 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 373 } } } ], "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Nested Stack 1", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 31 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "Normal Clip 2", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 33 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 101 } } }, { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "Clip Inside A Stack 2", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 24 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 429 } } } ], "effects": [ { "OTIO_SCHEMA": "LinearTimeWarp.1", "effect_name": "LinearTimeWarp", "metadata": {}, "name": null, "time_scalar": 0.8545454551724138 } ], "markers": [], "enabled": true, "metadata": {}, "name": "Nested Stack 2 (with effects)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 29 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "Normal Clip 3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 63 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 179 } } }, { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "Clip Inside A Track", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 1 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 471 } } } ], "effects": [ { "OTIO_SCHEMA": "FreezeFrame.1", "effect_name": "FreezeFrame", "metadata": {}, "name": null, "time_scalar": 0 } ], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Nested Track", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 4 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "Normal Clip 4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 238 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 135 } } } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Top Level Track", "source_range": null } ], "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Top Level Stack", "source_range": null } } opentimelineio-0.18.1/tests/sample_data/generator_reference_test.otio0000664000175000017500000000477515110656141023777 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "transition_test", "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Sequence.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA" : "GeneratorReference.1", "available_range" : { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } }, "generator_kind" : "SMPTEBars", "metadata" : {}, "parameters" : {}, "name" : "bars" }, "metadata": {}, "name": "C", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Sequence1", "source_range": null } ], "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "tracks", "source_range": null } } opentimelineio-0.18.1/tests/sample_data/clip_example.otio0000664000175000017500000000724615110656141021372 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "transition_test", "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Gap A", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "metadata": {}, "available_image_bounds": { "OTIO_SCHEMA": "Box2d.1", "min": { "OTIO_SCHEMA":"V2d.1", "x": 0.0, "y": 0.0 }, "max": { "OTIO_SCHEMA":"V2d.1", "x": 16.0, "y": 9.0 } }, "name": null }, "metadata": {}, "name": "Clip-001", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 } } }, { "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "Dissolve", "transition_type": "SMPTE_Dissolve", "parameters": {}, "in_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 2 }, "out_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 1 } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Gap B", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Track-001", "source_range": null } ], "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "tracks", "source_range": null } } opentimelineio-0.18.1/tests/sample_data/premiere_example.otio0000664000175000017500000033774215110656141022262 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": { "fcp_xml": { "@MZ.EditLine": "0", "@MZ.Sequence.AudioTimeDisplayFormat": "200", "@MZ.Sequence.EditingModeGUID": "9678af98-a7b7-4bdb-b477-7ac9c8df4a4e", "@MZ.Sequence.PreviewFrameSizeHeight": "720", "@MZ.Sequence.PreviewFrameSizeWidth": "1280", "@MZ.Sequence.PreviewRenderingClassID": "1297106761", "@MZ.Sequence.PreviewRenderingPresetCodec": "1297107278", "@MZ.Sequence.PreviewRenderingPresetPath": "EncoderPresets\\SequencePreview\\9678af98-a7b7-4bdb-b477-7ac9c8df4a4e\\I-Frame Only MPEG.epr", "@MZ.Sequence.PreviewUseMaxBitDepth": "false", "@MZ.Sequence.PreviewUseMaxRenderQuality": "false", "@MZ.Sequence.VideoTimeDisplayFormat": "104", "@MZ.WorkInPoint": "0", "@MZ.WorkOutPoint": "10550131200000", "@Monitor.ProgramZoomIn": "0", "@Monitor.ProgramZoomOut": "10550131200000", "@TL.SQAVDividerPosition": "0.5", "@TL.SQAudioVisibleBase": "0", "@TL.SQHeaderWidth": "184", "@TL.SQHideShyTracks": "0", "@TL.SQTimePerPixel": "0.033806825568230996", "@TL.SQVideoVisibleBase": "0", "@TL.SQVisibleBaseTime": "0", "@explodedTracks": "true", "@id": "sequence-1", "labels": { "label2": "Forest" }, "media": { "audio": { "format": { "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "numOutputChannels": "2", "outputs": { "group": [ { "channel": { "index": "1" }, "downmix": "0", "index": "1", "numchannels": "1" }, { "channel": { "index": "2" }, "downmix": "0", "index": "2", "numchannels": "1" } ] } }, "video": { "format": { "samplecharacteristics": { "anamorphic": "FALSE", "codec": { "appspecificdata": { "appmanufacturer": "Apple Inc.", "appname": "Final Cut Pro", "appversion": "7.0", "data": { "qtcodec": { "codecname": "Apple ProRes 422", "codectypecode": "apcn", "codectypename": "Apple ProRes 422", "codecvendorcode": "appl", "datarate": "0", "keyframerate": "0", "spatialquality": "1024", "temporalquality": "0" } } }, "name": "Apple ProRes 422" }, "colordepth": "24", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" } }, "uuid": "5ea30a6b-552f-4722-be92-6dfdb66c97e6" } }, "name": "sc01_sh010_layerA", "global_start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 }, "tracks": { "OTIO_SCHEMA": "Stack.1", "metadata": {}, "name": "sc01_sh010_layerA", "source_range": null, "effects": [], "markers": [ { "OTIO_SCHEMA": "Marker.2", "metadata": { "fcp_xml": { "comment": "so, this happened" } }, "name": "My MArker 1", "color": "RED", "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 113.0 } } }, { "OTIO_SCHEMA": "Marker.2", "metadata": { "fcp_xml": { "comment": "fsfsfs" } }, "name": "dsf", "color": "RED", "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 492.0 } } }, { "OTIO_SCHEMA": "Marker.2", "metadata": { "fcp_xml": { "comment": null } }, "name": "", "color": "RED", "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 298.0 } } } ], "enabled": true, "children": [ { "OTIO_SCHEMA": "Track.1", "metadata": { "fcp_xml": { "@MZ.TrackTargeted": "1", "@TL.SQTrackExpanded": "0", "@TL.SQTrackExpandedHeight": "25", "@TL.SQTrackShy": "0", "enabled": "TRUE", "locked": "FALSE" } }, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 536.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-1", "alphatype": "none", "anamorphic": "FALSE", "enabled": "TRUE", "labels": { "label2": "Iris" }, "link": [ { "clipindex": "1", "linkclipref": "clipitem-1", "mediatype": "video", "trackindex": "1" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-14", "mediatype": "audio", "trackindex": "1" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-16", "mediatype": "audio", "trackindex": "2" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-1", "pixelaspectratio": "square", "pproTicksIn": "0", "pproTicksOut": "846720000000" }, "my_hook_function_was_here": true }, "name": "sc01_sh010_anim.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 15.0, "value": 50.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 15.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-1", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_sh010_anim.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "fcp_xml": { "@MZ.TrackTargeted": "0", "@TL.SQTrackExpanded": "0", "@TL.SQTrackExpandedHeight": "25", "@TL.SQTrackShy": "0", "enabled": "TRUE", "locked": "FALSE" } }, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 13.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-2", "alphatype": "none", "enabled": "TRUE", "labels": { "label2": "Iris" }, "link": [ { "clipindex": "1", "linkclipref": "clipitem-2", "mediatype": "video", "trackindex": "2" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-13", "mediatype": "audio", "trackindex": "1" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-15", "mediatype": "audio", "trackindex": "2" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-1", "pproTicksIn": "0", "pproTicksOut": "846720000000" }, "my_hook_function_was_here": true }, "name": "sc01_sh010_anim.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-1", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_sh010_anim.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 52.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-3", "alphatype": "none", "anamorphic": "FALSE", "enabled": "TRUE", "labels": { "label2": "Iris" }, "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-2", "pixelaspectratio": "square", "pproTicksIn": "0", "pproTicksOut": "1329350400000" }, "my_hook_function_was_here": true }, "name": "sc01_sh020_anim.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 157.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-2", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_sh020_anim.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 175.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_sh020_anim.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-4", "alphatype": "none", "anamorphic": "FALSE", "enabled": "TRUE", "labels": { "label2": "Iris" }, "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-3", "pixelaspectratio": "square", "pproTicksIn": "0", "pproTicksOut": "1989792000000" }, "my_hook_function_was_here": true }, "name": "sc01_sh030_anim.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 235.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [ { "OTIO_SCHEMA": "Marker.2", "metadata": { "fcp_xml": { "comment": null } }, "name": "", "color": "RED", "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 73.0 } } } ], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-3", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_sh030_anim.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 400.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_sh030_anim.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Transition.1", "metadata": { "fcp_xml": { "alignment": "end-black", "cutPointTicks": "160876800000" } }, "name": "Cross Dissolve", "in_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 19.0 }, "out_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 }, "transition_type": "SMPTE_Dissolve" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 79.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Stack.1", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-5", "enabled": "TRUE", "labels": { "label2": "Forest" }, "masterclipid": "masterclip-4", "pproTicksIn": "0", "pproTicksOut": "2709504000000" } }, "name": "sc01_sh010_anim", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 320.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "children": [] } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "fcp_xml": { "@MZ.TrackTargeted": "0", "@TL.SQTrackExpanded": "0", "@TL.SQTrackExpandedHeight": "25", "@TL.SQTrackShy": "0", "enabled": "TRUE", "locked": "FALSE" } }, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 15.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-10", "alphatype": "straight", "anamorphic": "FALSE", "enabled": "TRUE", "labels": { "label2": "Lavender" }, "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-5", "pixelaspectratio": "square", "pproTicksIn": "914457600000000", "pproTicksOut": "922425235200000" }, "my_hook_function_was_here": true }, "name": "test_title", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 941.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 108000.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "GeneratorReference.1", "metadata": { "fcp_xml": { "@id": "file-4", "media": { "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "DF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "test_title", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "generator_kind": "Slug", "parameters": {} } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "fcp_xml": { "@MZ.TrackTargeted": "0", "@TL.SQTrackExpanded": "0", "@TL.SQTrackExpandedHeight": "25", "@TL.SQTrackShy": "0", "enabled": "TRUE", "locked": "FALSE" } }, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 956.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-11", "alphatype": "none", "anamorphic": "FALSE", "enabled": "TRUE", "labels": { "label2": "Iris" }, "link": [ { "clipindex": "1", "linkclipref": "clipitem-11", "mediatype": "video", "trackindex": "4" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-23", "mediatype": "audio", "trackindex": "7" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-25", "mediatype": "audio", "trackindex": "8" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-6", "pixelaspectratio": "square", "pproTicksIn": "287884800000", "pproTicksOut": "2159136000000" }, "my_hook_function_was_here": true }, "name": "sc01_master_layerA_sh030_temp.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 208.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 133.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-5", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_master_layerA_sh030_temp.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 400.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 99.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Transition.1", "metadata": { "fcp_xml": { "alignment": "center", "cutPointTicks": "101606400000" } }, "name": "Cross Dissolve", "in_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 12.0 }, "out_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 13.0 }, "transition_type": "SMPTE_Dissolve" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-12", "alphatype": "none", "enabled": "TRUE", "labels": { "label2": "Iris" }, "link": [ { "clipindex": "3", "linkclipref": "clipitem-12", "mediatype": "video", "trackindex": "4" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-24", "mediatype": "audio", "trackindex": "7" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-26", "mediatype": "audio", "trackindex": "8" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-1", "pproTicksIn": "50803200000", "pproTicksOut": "846720000000" }, "my_hook_function_was_here": true }, "name": "sc01_sh010_anim.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 82.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 18.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-1", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_sh010_anim.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "fcp_xml": { "@MZ.TrackTargeted": "1", "@PannerCurrentValue": "0.5", "@PannerIsInverted": "true", "@PannerName": "Balance", "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", "@TL.SQTrackAudioKeyframeStyle": "0", "@TL.SQTrackExpanded": "0", "@TL.SQTrackExpandedHeight": "25", "@TL.SQTrackShy": "0", "@currentExplodedTrackIndex": "0", "@premiereTrackType": "Stereo", "@totalExplodedTrackCount": "2", "enabled": "TRUE", "locked": "FALSE", "outputchannelindex": "1" } }, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 13.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-13", "@premiereChannelType": "stereo", "enabled": "TRUE", "labels": { "label2": "Iris" }, "link": [ { "clipindex": "1", "linkclipref": "clipitem-2", "mediatype": "video", "trackindex": "2" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-13", "mediatype": "audio", "trackindex": "1" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-15", "mediatype": "audio", "trackindex": "2" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-1", "pproTicksIn": "0", "pproTicksOut": "846720000000", "sourcetrack": { "mediatype": "audio", "trackindex": "1" } }, "my_hook_function_was_here": true }, "name": "sc01_sh010_anim.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-1", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_sh010_anim.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 423.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-14", "@premiereChannelType": "stereo", "enabled": "TRUE", "labels": { "label2": "Iris" }, "link": [ { "clipindex": "1", "linkclipref": "clipitem-1", "mediatype": "video", "trackindex": "1" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-14", "mediatype": "audio", "trackindex": "1" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-16", "mediatype": "audio", "trackindex": "2" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-1", "pproTicksIn": "0", "pproTicksOut": "846720000000", "sourcetrack": { "mediatype": "audio", "trackindex": "1" } }, "my_hook_function_was_here": true }, "name": "sc01_sh010_anim.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-1", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_sh010_anim.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "fcp_xml": { "@MZ.TrackTargeted": "1", "@PannerCurrentValue": "0.5", "@PannerIsInverted": "true", "@PannerName": "Balance", "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", "@TL.SQTrackAudioKeyframeStyle": "0", "@TL.SQTrackExpanded": "0", "@TL.SQTrackExpandedHeight": "25", "@TL.SQTrackShy": "0", "@currentExplodedTrackIndex": "0", "@premiereTrackType": "Stereo", "@totalExplodedTrackCount": "2", "enabled": "TRUE", "locked": "FALSE", "outputchannelindex": "1" } }, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 335.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-17", "@premiereChannelType": "stereo", "enabled": "TRUE", "labels": { "label2": "Caribbean" }, "link": [ { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-17", "mediatype": "audio", "trackindex": "3" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-19", "mediatype": "audio", "trackindex": "4" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-7", "pproTicksIn": "0", "pproTicksOut": "1439424000000", "sourcetrack": { "mediatype": "audio", "trackindex": "1" } }, "my_hook_function_was_here": true }, "name": "sc01_placeholder.wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 170.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 8497.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-6", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "DF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_placeholder.wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 170.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 8497.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_placeholder.wav" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 131.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Stack.1", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-18", "@premiereChannelType": "stereo", "enabled": "TRUE", "labels": { "label2": "Forest" }, "link": [ { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-18", "mediatype": "audio", "trackindex": "3" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-20", "mediatype": "audio", "trackindex": "4" } ], "masterclipid": "masterclip-4", "pproTicksIn": "0", "pproTicksOut": "2489356800000" } }, "name": "sc01_sh010_anim", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 294.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "children": [] } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "fcp_xml": { "@MZ.TrackTargeted": "1", "@PannerCurrentValue": "0.5", "@PannerIsInverted": "true", "@PannerName": "Balance", "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", "@TL.SQTrackAudioKeyframeStyle": "0", "@TL.SQTrackExpanded": "0", "@TL.SQTrackExpandedHeight": "25", "@TL.SQTrackShy": "0", "@currentExplodedTrackIndex": "0", "@premiereTrackType": "Stereo", "@totalExplodedTrackCount": "2", "enabled": "TRUE", "locked": "FALSE", "outputchannelindex": "1" } }, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 153.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-21", "@premiereChannelType": "stereo", "enabled": "TRUE", "labels": { "label2": "Caribbean" }, "link": [ { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-21", "mediatype": "audio", "trackindex": "5" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-22", "mediatype": "audio", "trackindex": "6" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-8", "pproTicksIn": "0", "pproTicksOut": "1676505600000", "sourcetrack": { "mediatype": "audio", "trackindex": "1" } }, "my_hook_function_was_here": true }, "name": "track_08.wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 198.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 6896.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-7", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "DF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "track_08.wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 198.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 6896.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/track_08.wav" } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "fcp_xml": { "@MZ.TrackTargeted": "0", "@PannerCurrentValue": "0.5", "@PannerIsInverted": "true", "@PannerName": "Balance", "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", "@TL.SQTrackAudioKeyframeStyle": "0", "@TL.SQTrackExpanded": "0", "@TL.SQTrackExpandedHeight": "25", "@TL.SQTrackShy": "0", "@currentExplodedTrackIndex": "0", "@premiereTrackType": "Stereo", "@totalExplodedTrackCount": "2", "enabled": "TRUE", "locked": "FALSE", "outputchannelindex": "1" } }, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 956.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-23", "@premiereChannelType": "stereo", "enabled": "TRUE", "labels": { "label2": "Iris" }, "link": [ { "clipindex": "1", "linkclipref": "clipitem-11", "mediatype": "video", "trackindex": "4" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-23", "mediatype": "audio", "trackindex": "7" }, { "clipindex": "1", "groupindex": "1", "linkclipref": "clipitem-25", "mediatype": "audio", "trackindex": "8" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-6", "pproTicksIn": "287884800000", "pproTicksOut": "2049062400000", "sourcetrack": { "mediatype": "audio", "trackindex": "1" } }, "my_hook_function_was_here": true }, "name": "sc01_master_layerA_sh030_temp.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 221.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 133.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-5", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_master_layerA_sh030_temp.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 400.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 99.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "fcp_xml": { "@frameBlend": "FALSE", "@id": "clipitem-24", "@premiereChannelType": "stereo", "enabled": "TRUE", "labels": { "label2": "Iris" }, "link": [ { "clipindex": "3", "linkclipref": "clipitem-12", "mediatype": "video", "trackindex": "4" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-24", "mediatype": "audio", "trackindex": "7" }, { "clipindex": "2", "groupindex": "1", "linkclipref": "clipitem-26", "mediatype": "audio", "trackindex": "8" } ], "logginginfo": { "description": null, "lognote": null, "scene": null, "shottake": null }, "masterclipid": "masterclip-1", "pproTicksIn": "152409600000", "pproTicksOut": "846720000000", "sourcetrack": { "mediatype": "audio", "trackindex": "1" } }, "my_hook_function_was_here": true }, "name": "sc01_sh010_anim.mov", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 94.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 6.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": { "fcp_xml": { "@id": "file-1", "media": { "audio": { "channelcount": "2", "samplecharacteristics": { "depth": "16", "samplerate": "48000" } }, "video": { "samplecharacteristics": { "anamorphic": "FALSE", "fielddominance": "none", "height": "720", "pixelaspectratio": "square", "rate": { "ntsc": "FALSE", "timebase": "30" }, "width": "1280" } } }, "rate": { "ntsc": "FALSE", "timebase": "30" }, "timecode": { "displayformat": "NDF", "rate": { "ntsc": "FALSE", "timebase": "30" }, "reel": { "name": null } } } }, "name": "sc01_sh010_anim.mov", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 100.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 30.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Audio" } ] } }opentimelineio-0.18.1/tests/sample_data/OpenTimelineIO@3xLight.png0000664000175000017500000002500615110656141022717 0ustar memePNG  IHDR;g pHYs!7!73Xz IDATx1rHqx˩D*rD`˚E,X:&`^h9ZV~ nb͘-$_}~Oϳ,̲p"{e^}k$' Y'veْ k9`pfLY^T^]#I/k?kZhg& V]̋)8xWf.'3>ƠL͋MbӃTk2 U{EɋUJG ?=0rL5cTk兦IFˌhfsq8vx\z(0x{~aGb~Iu=8+iRUr.*x01 u|g%ZjC6 nj1?:͋MXag~.X Ҡuii^y33lz?Ξ_}8UCw}Dzno[k7cVe#5F McQɋ͙t)mpa0k ;o@Թ9mi3kM S>`U^lnչ_~(([V^ Gwc_`#/69s[P;wU9,t^KǠ<߷%Y`ЎM_r1?9}NH:7Ugڽ/L M /zt ZۭN_\xӦ`zNN3gZPu&zQw赻dZW}i~tN9G)\?w;A>203u^\A͢^&3r|L19w81쪒8^%@z*sfRU_ӡ0q0ڽ~z P\!GyJ&O~?y y<(]̨o:/kuy>n <}w;XqO٩'D(]VKl8lޙD;ҏ9n,%95ƶX~&~ އ1V _l/g!emhv QwySsJizL/*Cȵl~xtjJwzenq1Hv]CS]LaAVG1'-v |.NޡǏ"}=i7n^w Lm.s95?W[ۦĨk ]520뗏m)8Svdʎc೺Mo^lVC϶4=F>G˩P7 隵18+Z'3󁪮2^M$Y籟ѝVU_#ΪJAPΊ4(O:۾5Pէz E(/6W BwΎZ0#Ьkh}wHcq/԰GYT:ᔪ.0M11ucxMc B7 ӫ^kvpjuݏ[Q:p95MncGx` l/ Vʟ_vhHUocꀩ5Nb(g=O1tZ܉/44uEpVzXpVehH6rs=MI?s<}5XyX76>x9}R@ƶ=ϻY@ \,gZ06gE%COyIk9*Ns9%a5N; e' kGg%SirEc Jfi*u(WiqЀZ YɜCШgVxr*e1S'&n>_nLdv}/Bl+P IJDYuK_ H轊!P<}:ﶻ4ܜ3tS J=]I焙U^%f .b+&RuV*(7b&نg!Regdee]FU351SNan^HpdAjNhS2:əK1gYe3otJ58+8w%/sB ZbpV:ݝ>&zm?g$Bv_}ͧ Zb<+`Lg 0}٢Rk}m%hQ޿)jK/La.1#HZgw ;+P;h&@[aR9ʥCs0m Nf>Ѐۘ+9107385U4 6ުJdۮ<esX1m@ló) b[fmhbb~dF[Ue٧YpJ33uޥcȼ_ 9}w,,vrKv) yyo9w!Y%}.(*KOR@?i(ǒ @lNimD/#j,= u((ޘ “.ÕCWy,N|iw㱸 epT? DX6Pyf .4^d|7zYʁ nXCh]}DK^4NI\wc@!uΎm5:nFdjI7lGPg#M[PuulIDrԑ.X@IkB̕0hVFESCNEijO>ړ-?:|Wvy^6 "Ug%)sS7?t z`{>ai~!߶w1]OEl#k׶@Su7r}?m\s!o,3&gV;}ejƝj05-#z[!:w>eN87ZA) 1eρ7Xӆ ,obĶjٱXl;l?Lea\ u:}Tv'Q<``l;~b oF5UY7J>F# ~l1zfJCt5m:⍯an=thBWp1TȆ87gLJFt}_d9L @b߮;E>ئ'6m=:\^)|݁qT9Dx` .^٘>ks=”M繯T݀븹 \T m!4V3N?kSF^|PuZI*#V>3ۑVFN_ׁ~[o6xABBUh6E/Fr81OOt:gƢv˒bc=,eS7lW=; ЯPH2Iwb1߻#wfեz\Itm7uj].ܫ10鵸Tԅ:+^fol1*W*aUڬUa:j`B>c|4l~ח>ztP}Ovn0Gx`L\6n\aSOb_WsbS`33n@v~qSǭiEC>P՞f5xw*/6(?}CR謁Jؕtۦ ipq6ԙFSBUe1jce{e}x7WejwԉoZza)e(]jgڃ7{Uh]:GDٞwޱ_i|̈́g̱,u.k4+FS<{gjv>*ަƢM`{0$$ Z_SuRm{:%3uS0+@qe]2wM(sc!y$\ۢS>R``gFEakC6EpYl;{ؾs@?4)/+%KN0MLS4 #CM.h]2ڴRcRb#P{`)hzk4{v$Δ%"gJݽ'X ~ݷ{dfW23Et+}حpI|fbs>墷QcT@b[Smty5%D9>wXt>܍Ȝh16ZfCcʍ( Lir.29Kk_z|J:m1eثtApm|Z}{;h:%;cw^4}SF IDAT0FFTZkcOYQ-ilہ_[58Fѿ~UZgM,%0z ѭo ºyʽsmsܦuݢX?bzqN' H]h bk6u^kh}*_tE-mg-]ThQhvH4루$e45l֎#]\G4T/ 0:3FJS3=4E?Sr*gFMAoZFAhzs1ݡzde6Ao ]>t9V.Ӄ>^pg)NbaD))9p\$JZҥ͹qnSsm OU?ɺ u w]z ׻VegS_޵H_/ sbz:{J^,::s:w[@IZ3)1OIc1@*!ZAagPFYL(mIOkHlŃ]K{K* 6CJm/eZls1~Gͺ6 +|;QJ'>1(4\;w^u]Naiub ^z~]1]w ʏ *S ۚ6mktiufIEQg%*VΏ~Ry~R ]3󡂉%'y HcCiViK]lC 2tyC<qhZOW $TUZ_d$@upVRu~P$Ӿ=ޱI2R[4Y_ELYI; MEqW{ E:- LW3Flw-w9/|wg&M[46;yR=eDەkvA;f|萢J(v58s wdX܅ itDUR)lcoŽXm1X4g3S0ꟑw?b4;a&2_"\vi\4faD~U}P} 4HU&tEVCrB#glpIlX8p cekM6H950ei24mafDUIΕgE*>~_jT$mSC鰧ҭF t^ oX@uArZ?lym8T(K2fja6?_y!vSbv<3[rumXy^lL9Y߃g.䗱Om)׃ӎFg໘ϓ9+`GU'+OuyV;]>X(ۆj;GP6QΜ~-{']]=b-m!0\TFu\MRS-˰qbGîij0hm{bsV,I<`B{u^9LhtZzt5? á+CvѢEpjqAH3֋廟4{v'. ]XvßSY7/6kF˵#}㹫Qa.ǾpUy ЏjYy^.M%0Ysܩ}xXi5Xi#TO>?^iGUP@xxTĂ g@3Ѐ h@x4 <<}߇Y~ ceyt/C |ye qDGڄH/fLڄ{Zb&Ѐ h@x4 < g@,֏=83G 1mh@x4 < g@3Ѐ h@x4 < g@3ieye/v?}w|cz=meY~E!IENDB`opentimelineio-0.18.1/tests/sample_data/preflattened.otio0000664000175000017500000002024315110656141021375 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "OTIO TEST - multitrack.Exported.03", "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "tech.fux (loop)-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 482 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "t-hawk (loop)-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 480 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "out-b (loop)-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 236 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 159 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "KOLL-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 640 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Filler 2", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 119 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 313 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "MissingReference.1", "available_range": null, "metadata": {}, "name": null }, "metadata": {}, "name": "brokchrd (loop)-HD.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 720 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "Filler 3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 605 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Sequence 2", "source_range": null } ], "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "tracks", "source_range": null } }opentimelineio-0.18.1/tests/sample_data/multiple_track.otio0000775000175000017500000003137415110656141021751 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "Figure 3 - Multiple Tracks", "global_start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 }, "tracks": { "OTIO_SCHEMA": "Stack.1", "metadata": {}, "name": "tracks", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Track.1", "metadata": {}, "name": "Track-001", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-001", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "triangle.mp4", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "target_url": "file:///folder/titles.mov" } }, { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-002", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 6 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 2 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Media-002", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 9 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "target_url": "file:///folder/wind-up.mov" } }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 4 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-004", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 6 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 100 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Media-004", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 6 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 100 } }, "target_url": "file:///folder/credits.mov" } } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": {}, "name": "Track-002", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 7 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-003", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 9 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 100 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Media-003", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 9 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 100 } }, "target_url": "file:///folder/punchline.mov" } } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": {}, "name": "Track-003", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 16 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": -7 } }, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-005", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 9 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 100 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Media-003", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 9 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 100 } }, "target_url": "file:///folder/punchline.mov" } } ], "kind": "Video" } ] } } opentimelineio-0.18.1/tests/sample_data/OpenTimelineIO@3xDark.png0000664000175000017500000002500315110656141022526 0ustar memePNG  IHDR;g pHYs!7!73Xz IDATxAr8qf*۔?ݫ^ڽLŽx7 D>ADrk3qy.c/Q/}͈ Ok$(<{xxЍ?y/˲Ye9߳,x @:le VY @֙gTUHYwF7WM$³̵@:;M_Zv73$v؟|]7Gxo @ʨ<؋nRFpGx@/H43g; َ(@#8Cxhg" ˲^:g|,[fYvK5Urc~DYd^]S8c@#8@x430g4@#8Bx4308g$@#8Dxhg`Q .:|g`О=<< $gY&6Un'/^<0g ݪ{af^vh '%,@FpPBx8³l@x-43g=;"8؂g^ve[wuMpݳiMڧ*5lAYOxm^SFp`ʳ,+²] "<j0mAx <j5g@ 3P懇eY6y8hg z\Ug˲h =Xa&P Ax <j5g@,˲l< QOoh,gG fg3H>{*X Zg~rYyz$'cm$S=q49Ȳl_ 4dO㣣Ut,JX NsJHyU#1gm . ο<|Fؙ<,mʃbtcq<˲ `(H)H >N"<5j \f~ȲlʴN]lv=N2A'TB@" 6yc~tE㤙#i3kI+QyH3M>4ZT xg'3!]O*ϴDAS5|1Aߩ b@5|iɛ5|mF&`DYpj geUFAҢ!J&[kowCd,+'WZ*Pq8@>}88 AXz ո`麀.⦡Zk?U 5.<&3m4'7 6=3Jh @`~TWtl<\5;Ԙ3y8g'zg \w<4h+ں40'X<1A'4y&'˧k]4gmYAsǪsNX㻎>8+hXi Y3}A@2 Bbqy̱LM6Rs8ɴ7:O\*F:6gz%'g?Ɔ#m`*옾 D?cAr)W!49k;Y{ _c( ƥn=-HѪt Y`u|yv*]ocFAq)oC%Y`5ЎXr8CAHwUg˓ʣql Jq:b_ 嵦[38|qAi:71景Aڙ3-tkƨ\N]qjC&\#}~V קt{SV ;؞ceuD~ t>.v _UX?H!@xy F?<;\+{gXMU/isnf#}*6^e>5j~˲C̀e`E˔ǩJ>+-Y.MZC|M s9+PO ϖlwZjƏ+3dk+6}!7 XqM1'D(@U_6~FR}Eh}MP{.f{M[`s1c="<. >V _l3˲S4N[]y>Y9O%@4=aWk !G6k?<95%;@=yٴ8\$!)pE&f0MnFwYLB9/N>Ǐ"}<7x[)B#!OTF:~Msc?* djd\J_=%s(fp@H_ۑ)*n|2lKST9s1N2sjg3@]ok͎2]$Y~ G{08+)_#ʊJݝ8V*L M7e\Igm>py]U[]Tih<\)0 =8;V'hi-f] P(Du= }ŝSu=sS v5` TuiO}!hcM7Ў«7Kz:>S .S0{)_Wl~mp?[.Xg8`u v^qXӺw1 Q' B+g;.fPӹRh 0͠^C(‡mci]U(B^k-9C؄q5qͳ?w]wug(ӹ*2Af-6lX`lsE%^Ik9*As1%agkd,зLɕQ-8+B\4Jd_fg\A7b zpbF)8s}Uވ%p8i5qm->bÀq"w]k;u|z.ʶrjPtNB+U_Ea@U  Bѥidks mwiޅf̵̧{ $ #]NK48+*%m?v Ugbb3+mHmx-UV}|}AQֵj.x0W7]/Yi $#ArRYV]R s]ɋi/okmw>G󬗛QPhۏkIx YZ1bu]@; Pn͌,fYrϯY+\UO!g˽rtdžaZgKpvۆ΃kA;pR5UTpB>܅eY!V@y)HmpZhjm:-c cѭ0^gY n| Ǚmܕ~PM72E5W.-σ~gggp2򱼀niS}3lZa;R l54;e~̩<2lؖ{4F(ڄ&(yX\u$2ӁѺ]?eYvQv(\9g,;mӁ1*~s 9}q;X ccvNW.{@ZD..0j{CI7 aP'-m$<2)CZsmΠ:KOcB J|6TxCCXy8\9T~]?N7v<n9\ypi9}akEnJ!4zVgD_¶7q@ @h}[Ev^uVpxqgAAl!7LKru[$T&BMٶ L=O˅n~vڸ)|Bɒ^O|ZmxfcUW WFx]naHWӂ>o9 1b y-S;Hxk:V&Ĕ 7tB`M&3;ezb͹cgb*2qK+(4PqHxs"U"<is**P\G3̺ Pq7: k_l4D׉Qgf ^À&t筇w U!!ӶѪ*].twuf}$<طN`!ul;O:DwZcވf$v}\bY݆kR Y=I} }u!njGYG w+(t{ R)7UѪmmVWc3)@k3'=]hF'[{P{}/3cpCTu5*R"bS͝Sv|a4lh+8~e}}gRe[9:9n?u浱K>1Y" v_ge&T=Cb2L6j2*$K>%@! g丽Im+c:CSd0 @l!7 ![3G;}'Hm?EUmޖ}]cOȲ+cpIgg(dbWuf6W#p' qqbqxS @mIvFqPwop}0xley**ll΋\>XiɩÎc³ԦV g/8 7"Z=c[7L?HkDx uq@lWxz k;l;WǕG4`By`m}6~QYgއgd[iw]L̘ u >[ʒUufQFr?GmCm6"tku!Hljݳ!GOՙIg`ޛ˝qwRi: ¶=WZ{3+Us7`:n.1Urt߶b> @ӏT$9T]K=I厕N+wN[ׁ~[g6񂄄s:grU˒|bP՞f5xw*'7v풂 ʫ]9L׼Y @ohΛaG!݆:ݔPUgY̻X6³urBhyd ^8Ԕ2vpFԯ 5ux S-7UhX\?#s ϻXԾg3}sX&Mnx>L}TNEvHH趽TA|)eЕJFMu5؆ M}p~HK'<+ 3\;Ih b;6;la>LnE_VK='3A0LS4 =CM.k]2RcRb#XybO)hz*O[=5Nz^ygʒ)@3wE3 ߶ٕLyŕ·w'6uIӜ3Wy>Wnbۨ?ӱLSMƼhJXyssXtw>ڍLO8>ZfS* L闫r-29Km3}u ЮbR1nw4-ޠۿ*l5>R5yom}' IDATʖ7Zì9^ Ucm>m$p̍WeApqt^ҝ +j ێkY!vmu,^W,#֪Y@*c`.MMf4]QejkY2gzK C*}-f}Ӑ#yN0#j}̳`ſUxZcIq֠ϟz93 ߣYNNhzǡz`qm6ۨCm M>9V.Ӄ>u^pg)AbvÈHSR$HKkH3)vvO{ ^4QYЬؾ޵,Qpvm1uM]T1ձbq9],[ZgOU 6ՋECT@Kg\h7`Z,^T{j7B!Bm9/O\g&A0%fT5[=^\*@9Qc$0+Dm[P i¡{'vTάg*BbZI3$]T("1 .v[cg'5ݝnK$KƨD7BlqZqV <0m֢㼯j LiqcwZl'S5589ѺO7ZxԬ1oP2ah6Ǡ0Ҕ֝Mئۖuxy!nvb dSMZ㤛btiYFx`hԈx8:]vy<-f4eҹ7iM[Zo+ &[ǎҨL ؆d*|kHq,PA(h0UIଠI>'Q0 X[4YeLYAJ,xHSQǞq͏23CxˍFpԨKnzBpV03#]cdG>;3-M9`b^TmvZ3 "]YRv#- 'RTEݜ% +d ʉRބ?Xm:1w5-4@Xj ¨" ~TEov0OdݿFtsuJ4sՇ}UQ g C巑v].u.׿՝6SF2@+:;@F8 |RT΢4i603^" VqRk%5s-&lySCݹׂM遶? ә9v:?:/zc!h@Ow PlJL!4'3 OxVZ >J}^L_qR ٻ{<?LVkiOEqmXQ:UggGL>(փӎF9"Lc}P :`P<e(s>}$7 {ԯt)* jLT1TyEAyU{crGtCorW L\wEuĬ+H7Tu4NZ>_1Ϗ 3m5g@ 3P Axx,;4tpe?{it<ϲ̄g9qmB$7ӧ~iʣ 1mAx <j5g@ 3ye']HI圣` <j5g@ 3P Ax <j5g@ 3P <˲,^n~ Th*˲5©m5P$IENDB`opentimelineio-0.18.1/tests/sample_data/big_int.otio0000664000175000017500000000676415110656141020347 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "testdata", "global_start_time": null, "tracks": { "OTIO_SCHEMA": "Stack.1", "metadata": {}, "name": "tracks", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Track.1", "metadata": {}, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.1", "metadata": { "int_test": { "maxint32": 2147483647, "minint32": -2147483647, "smallest_int64": 2147483648, "neg_smallest_int64": -2147483648, "verybig": 3450100000, "negverybig": -3450100000, "int128": 170141183460469231731687303715884105728, "int256": 57896044618658097711785492504343953926634992332820282019728792003956564819968 }, "float": { "double": 1.1, "floatinf": Inf, "floatneginf": -Infinity, "floatnan": NaN }, "unicode": { "utf8": "Viel glück und hab spaß!" } }, "name": "black", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 24.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 240.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 } }, "target_url": "black.mov" } } ], "kind": "Video" } ] } } opentimelineio-0.18.1/tests/sample_data/effects.otio0000664000175000017500000127013615110656141020350 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": { "Resolve_OTIO": { "Resolve OTIO Meta Version": "1.0" } }, "name": "", "global_start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86400.0 }, "tracks": { "OTIO_SCHEMA": "Stack.1", "metadata": {}, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Locked": false } }, "name": "Video 1", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0003.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 444.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Transform", "Enabled": true, "Name": "Transform", "Parameters": [ { "Default Parameter Value": 1.0, "Key Frames": { "3": { "Value": 2.110382080078125, "Variant Type": "Double" }, "424": { "InBez": { "InBez": [ -49.75, 0.0 ], "Variant Type": "POINTF" }, "Value": 1.5003825426101685, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomX", "Parameter Value": 1.1899998188018799, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 1.0, "Key Frames": { "3": { "Value": 2.110382080078125, "Variant Type": "Double" }, "424": { "InBez": { "InBez": [ -49.75, 0.0 ], "Variant Type": "POINTF" }, "Value": 1.5003825426101685, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomY", "Parameter Value": 1.1899998188018799, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "203": { "InBez": { "InBez": [ -50.0, -0.009602577785650889 ], "Variant Type": "POINTF" }, "OutBez": { "OutBez": [ 50.0, 0.009602577785653164 ], "Variant Type": "POINTF" }, "Value": -0.2515834867954254, "Variant Type": "Double" }, "3": { "Value": -0.2515834867954254, "Variant Type": "Double" }, "424": { "InBez": { "InBez": [ -55.25, -0.12587182223796845 ], "Variant Type": "POINTF" }, "Value": 0.25190380215644839, "Variant Type": "Double" } }, "Parameter ID": "transformationPan", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "203": { "InBez": { "InBez": [ -50.0, -0.03884801547974348 ], "Variant Type": "POINTF" }, "OutBez": { "OutBez": [ 50.0, 0.03884801547974348 ], "Variant Type": "POINTF" }, "Value": 0.17423609438984523, "Variant Type": "Double" }, "3": { "Value": 0.02886868268251419, "Variant Type": "Double" }, "424": { "InBez": { "InBez": [ -55.25, -0.041354178032654179 ], "Variant Type": "POINTF" }, "Value": 0.33965280652046206, "Variant Type": "Double" } }, "Parameter ID": "transformationTilt", "Parameter Value": 0.11501597613096237, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 } ], "Type": 2 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Cropping", "Enabled": true, "Name": "Cropping", "Parameters": [], "Type": 3 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Dynamic Zoom", "Enabled": false, "Name": "Dynamic Zoom", "Parameters": [ { "Default Parameter Value": [ 0.0, 0.0 ], "Key Frames": { "-85200": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, "-86200": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" } }, "Parameter ID": "dynamicZoomCenter", "Parameter Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, { "Default Parameter Value": 1.0, "Key Frames": { "-85200": { "Value": 1.0, "Variant Type": "Double" }, "-86200": { "Value": 0.8, "Variant Type": "Double" } }, "Parameter ID": "dynamicZoomScale", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.01 } ], "Type": 59 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Composite", "Enabled": true, "Name": "Composite", "Parameters": [], "Type": 1 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Lens Correction", "Enabled": true, "Name": "Lens Correction", "Parameters": [], "Type": 43 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Retime and Scaling", "Enabled": true, "Name": "Retime and Scaling", "Parameters": [], "Type": 22 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Video Faders", "Enabled": true, "Name": "Video Faders", "Parameters": [], "Type": 36 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0003.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0003.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Transition.1", "metadata": { "Resolve_OTIO": { "Effects": { "Effect Name": "Cross Fade 0 dB", "Enabled": true, "Name": "Cross Fade", "Parameters": [], "Type": 40 }, "Transition Type": "Cross Fade 0DB" } }, "name": "Cross Fade 0 dB", "in_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 30.0 }, "out_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 30.0 }, "transition_type": "SMPTE_Dissolve" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0005.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 56.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0005.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0005.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0006.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 13.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0006.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0006.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0007.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 18.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0007.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0007.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0009.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 30.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0009.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0009.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0010.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 34.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0010.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0010.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0011.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 18.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0011.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0011.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0012.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 16.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0012.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0012.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0013.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 42.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0013.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0013.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Panel 3.3.5.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 145.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Panel 3.3.5.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames_v2/Panel 3.3.5.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Panel 3.3.5.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 202.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Panel 3.3.5.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames_v2/Panel 3.3.5.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0014.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 37.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0014.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0014.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "Picchu_V7_310821_0015.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 70.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Transform", "Enabled": true, "Name": "Transform", "Parameters": [ { "Default Parameter Value": 1.0, "Key Frames": { "0": { "Value": 1.0, "Variant Type": "Double" }, "67": { "Value": 1.179999828338623, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomX", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 1.0, "Key Frames": { "0": { "Value": 1.0, "Variant Type": "Double" }, "67": { "Value": 1.179999828338623, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomY", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "0": { "Value": 0.0, "Variant Type": "Double" }, "67": { "Value": -0.0234375, "Variant Type": "Double" } }, "Parameter ID": "transformationPan", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "0": { "Value": 0.0, "Variant Type": "Double" }, "67": { "Value": -0.043130990117788318, "Variant Type": "Double" } }, "Parameter ID": "transformationTilt", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 } ], "Type": 2 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Cropping", "Enabled": true, "Name": "Cropping", "Parameters": [], "Type": 3 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Dynamic Zoom", "Enabled": false, "Name": "Dynamic Zoom", "Parameters": [ { "Default Parameter Value": [ 0.0, 0.0 ], "Key Frames": { "-85384": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, "-86384": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" } }, "Parameter ID": "dynamicZoomCenter", "Parameter Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, { "Default Parameter Value": 1.0, "Key Frames": { "-85384": { "Value": 1.0, "Variant Type": "Double" }, "-86384": { "Value": 0.8, "Variant Type": "Double" } }, "Parameter ID": "dynamicZoomScale", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.01 } ], "Type": 59 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Composite", "Enabled": true, "Name": "Composite", "Parameters": [], "Type": 1 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Lens Correction", "Enabled": true, "Name": "Lens Correction", "Parameters": [], "Type": 43 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Retime and Scaling", "Enabled": true, "Name": "Retime and Scaling", "Parameters": [], "Type": 22 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Video Faders", "Enabled": true, "Name": "Video Faders", "Parameters": [], "Type": 36 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Picchu_V7_310821_0015.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/Picchu_V7_310821_0015.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Locked": false } }, "name": "Video 2", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "A_title.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 444.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Transform", "Enabled": true, "Name": "Transform", "Parameters": [ { "Default Parameter Value": 1.0, "Key Frames": { "3": { "Value": 2.110382080078125, "Variant Type": "Double" }, "424": { "InBez": { "InBez": [ -49.75, 0.0 ], "Variant Type": "POINTF" }, "Value": 1.5003825426101685, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomX", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 1.0, "Key Frames": { "3": { "Value": 2.110382080078125, "Variant Type": "Double" }, "424": { "InBez": { "InBez": [ -49.75, 0.0 ], "Variant Type": "POINTF" }, "Value": 1.5003825426101685, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomY", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "203": { "InBez": { "InBez": [ -50.0, -0.009602577785650889 ], "Variant Type": "POINTF" }, "OutBez": { "OutBez": [ 50.0, 0.009602577785653164 ], "Variant Type": "POINTF" }, "Value": -0.2515834867954254, "Variant Type": "Double" }, "3": { "Value": -0.2515834867954254, "Variant Type": "Double" }, "424": { "InBez": { "InBez": [ -55.25, -0.12587182223796845 ], "Variant Type": "POINTF" }, "Value": 0.25190380215644839, "Variant Type": "Double" } }, "Parameter ID": "transformationPan", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "203": { "InBez": { "InBez": [ -50.0, -0.03884801547974348 ], "Variant Type": "POINTF" }, "OutBez": { "OutBez": [ 50.0, 0.03884801547974348 ], "Variant Type": "POINTF" }, "Value": 0.17423609438984523, "Variant Type": "Double" }, "3": { "Value": 0.02886868268251419, "Variant Type": "Double" }, "424": { "InBez": { "InBez": [ -55.25, -0.041354178032654179 ], "Variant Type": "POINTF" }, "Value": 0.33965280652046206, "Variant Type": "Double" } }, "Parameter ID": "transformationTilt", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 } ], "Type": 2 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Cropping", "Enabled": true, "Name": "Cropping", "Parameters": [], "Type": 3 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Dynamic Zoom", "Enabled": false, "Name": "Dynamic Zoom", "Parameters": [ { "Default Parameter Value": [ 0.0, 0.0 ], "Key Frames": { "-85212": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, "-86212": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" } }, "Parameter ID": "dynamicZoomCenter", "Parameter Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, { "Default Parameter Value": 1.0, "Key Frames": { "-85212": { "Value": 1.0, "Variant Type": "Double" }, "-86212": { "Value": 0.8, "Variant Type": "Double" } }, "Parameter ID": "dynamicZoomScale", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.01 } ], "Type": 59 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Composite", "Enabled": true, "Name": "Composite", "Parameters": [ { "Default Parameter Value": 100.0, "Key Frames": { "316": { "Value": 100.0, "Variant Type": "Double" }, "370": { "Value": 0.0, "Variant Type": "Double" }, "73": { "Value": 0.0, "Variant Type": "Double" }, "98": { "Value": 100.0, "Variant Type": "Double" } }, "Parameter ID": "opacity", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 } ], "Type": 1 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Lens Correction", "Enabled": true, "Name": "Lens Correction", "Parameters": [], "Type": 43 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Retime and Scaling", "Enabled": true, "Name": "Retime and Scaling", "Parameters": [], "Type": 22 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Video Faders", "Enabled": true, "Name": "Video Faders", "Parameters": [], "Type": 36 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "A_title.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/A_title.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 681.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Locked": false } }, "name": "Video 3", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "_bar.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 444.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Transform", "Enabled": true, "Name": "Transform", "Parameters": [ { "Default Parameter Value": 1.0, "Key Frames": { "265": { "Value": 1.0, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomX", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 1.0, "Key Frames": { "265": { "Value": 1.0, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomY", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "265": { "Value": 0.0, "Variant Type": "Double" } }, "Parameter ID": "transformationPan", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "265": { "Value": -1.6506849527359009, "Variant Type": "Double" } }, "Parameter ID": "transformationTilt", "Parameter Value": -1.6506849527359009, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 } ], "Type": 2 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Cropping", "Enabled": true, "Name": "Cropping", "Parameters": [], "Type": 3 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Dynamic Zoom", "Enabled": false, "Name": "Dynamic Zoom", "Parameters": [ { "Default Parameter Value": [ 0.0, 0.0 ], "Key Frames": { "-85341": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, "-86341": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" } }, "Parameter ID": "dynamicZoomCenter", "Parameter Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, { "Default Parameter Value": 1.0, "Key Frames": { "-85341": { "Value": 1.0, "Variant Type": "Double" }, "-86341": { "Value": 0.8, "Variant Type": "Double" } }, "Parameter ID": "dynamicZoomScale", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.01 } ], "Type": 59 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Composite", "Enabled": true, "Name": "Composite", "Parameters": [], "Type": 1 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Lens Correction", "Enabled": true, "Name": "Lens Correction", "Parameters": [], "Type": 43 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Retime and Scaling", "Enabled": true, "Name": "Retime and Scaling", "Parameters": [], "Type": 22 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Video Faders", "Enabled": true, "Name": "Video Faders", "Parameters": [], "Type": 36 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": false, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "_bar.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/_bar.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 681.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Locked": false } }, "name": "Video 4", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "A_title.png", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 444.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Transform", "Enabled": true, "Name": "Transform", "Parameters": [ { "Default Parameter Value": 1.0, "Key Frames": { "252": { "InBez": { "InBez": [ -104.21834821101271, -0.02666666666666667 ], "Variant Type": "POINTF" }, "Value": 1.8203825426101688, "Variant Type": "Double" }, "65": { "Value": 2.110382080078125, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomX", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 1.0, "Key Frames": { "252": { "InBez": { "InBez": [ -104.21834821101271, -0.02666666666666667 ], "Variant Type": "POINTF" }, "Value": 1.8203825426101688, "Variant Type": "Double" }, "65": { "Value": 2.110382080078125, "Variant Type": "Double" } }, "Parameter ID": "transformationZoomY", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "265": { "InBez": { "InBez": [ -50.0, -0.009602577785650889 ], "Variant Type": "POINTF" }, "OutBez": { "OutBez": [ 50.0, 0.009602577785653164 ], "Variant Type": "POINTF" }, "Value": -0.2515834867954254, "Variant Type": "Double" }, "486": { "InBez": { "InBez": [ -55.25, -0.12587182223796845 ], "Variant Type": "POINTF" }, "Value": 0.25190380215644839, "Variant Type": "Double" }, "65": { "Value": -0.2515834867954254, "Variant Type": "Double" } }, "Parameter ID": "transformationPan", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 }, { "Default Parameter Value": 0.0, "Key Frames": { "265": { "InBez": { "InBez": [ -50.0, -0.03884801547974348 ], "Variant Type": "POINTF" }, "OutBez": { "OutBez": [ 50.0, 0.03884801547974348 ], "Variant Type": "POINTF" }, "Value": 0.17423609438984523, "Variant Type": "Double" }, "486": { "InBez": { "InBez": [ -55.25, -0.041354178032654179 ], "Variant Type": "POINTF" }, "Value": 0.33965280652046206, "Variant Type": "Double" }, "65": { "Value": 0.02886868268251419, "Variant Type": "Double" } }, "Parameter ID": "transformationTilt", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 4.0, "minValue": -4.0 } ], "Type": 2 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Cropping", "Enabled": true, "Name": "Cropping", "Parameters": [], "Type": 3 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Dynamic Zoom", "Enabled": false, "Name": "Dynamic Zoom", "Parameters": [ { "Default Parameter Value": [ 0.0, 0.0 ], "Key Frames": { "-85150": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, "-86150": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" } }, "Parameter ID": "dynamicZoomCenter", "Parameter Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, { "Default Parameter Value": 1.0, "Key Frames": { "-85150": { "Value": 1.0, "Variant Type": "Double" }, "-86150": { "Value": 0.8, "Variant Type": "Double" } }, "Parameter ID": "dynamicZoomScale", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.01 } ], "Type": 59 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Composite", "Enabled": true, "Name": "Composite", "Parameters": [ { "Default Parameter Value": 100.0, "Key Frames": { "132": { "Value": 0.0, "Variant Type": "Double" }, "183": { "Value": 98.66666666666667, "Variant Type": "Double" }, "378": { "Value": 100.0, "Variant Type": "Double" }, "432": { "Value": 0.0, "Variant Type": "Double" } }, "Parameter ID": "opacity", "Parameter Value": 0.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.0 } ], "Type": 1 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Lens Correction", "Enabled": true, "Name": "Lens Correction", "Parameters": [], "Type": 43 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Retime and Scaling", "Enabled": true, "Name": "Retime and Scaling", "Parameters": [], "Type": 22 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Video Faders", "Enabled": true, "Name": "Video Faders", "Parameters": [], "Type": 36 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": false, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "A_title.png", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/frames/A_title.png" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 681.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Locked": false } }, "name": "Video 5", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "sq0100_sh0010_layout_v004.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 500.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Transform", "Enabled": true, "Name": "Transform", "Parameters": [], "Type": 2 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Cropping", "Enabled": true, "Name": "Cropping", "Parameters": [], "Type": 3 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Dynamic Zoom", "Enabled": false, "Name": "Dynamic Zoom", "Parameters": [ { "Default Parameter Value": [ 0.0, 0.0 ], "Key Frames": { "0": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, "1000": { "Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" } }, "Parameter ID": "dynamicZoomCenter", "Parameter Value": [ 0.0, 0.0 ], "Variant Type": "POINTF" }, { "Default Parameter Value": 1.0, "Key Frames": { "0": { "Value": 0.8, "Variant Type": "Double" }, "1000": { "Value": 1.0, "Variant Type": "Double" } }, "Parameter ID": "dynamicZoomScale", "Parameter Value": 1.0, "Variant Type": "Double", "maxValue": 100.0, "minValue": 0.01 } ], "Type": 59 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Composite", "Enabled": true, "Name": "Composite", "Parameters": [], "Type": 1 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Lens Correction", "Enabled": true, "Name": "Lens Correction", "Parameters": [], "Type": 43 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Retime and Scaling", "Enabled": true, "Name": "Retime and Scaling", "Parameters": [], "Type": 22 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Video Faders", "Enabled": true, "Name": "Video Faders", "Parameters": [], "Type": 36 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/Layout/delivery_001/sq0100_sh0010_layout_v004.mp4" } }, "name": "sq0100_sh0010_layout_v004.mp4", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 500.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "sq0100_sh0020_layout_v005.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 316.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/Layout/delivery_002/sq0100_sh0020_layout_v005.mp4" } }, "name": "sq0100_sh0020_layout_v005.mp4", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 316.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "sq0100_sh0030_layout_v004.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 211.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/Layout/delivery_001/sq0100_sh0030_layout_v004.mp4" } }, "name": "sq0100_sh0030_layout_v004.mp4", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 211.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": {} }, "name": "sq0100_sh0050_layout_v004.mp4", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 98.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/Layout/delivery_001/sq0100_sh0050_layout_v004.mp4" } }, "name": "sq0100_sh0050_layout_v004.mp4", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 98.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Locked": false } }, "name": "Video 6", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1125.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Locked": false } }, "name": "Video 7", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1125.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Locked": false } }, "name": "Video 8", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1125.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Video" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 1", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1125.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 2", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 347.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "06_Mayu_Sfx_ladderStruggleAhhagg.wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 85.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 40.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -16.999998092651368, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "06_Mayu_Sfx_ladderStruggleAhhagg.wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1139.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/Mayu_Saywa_Kathy/wav/06_Mayu_Sfx_ladderStruggleAhhagg.wav" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 40.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "06_Mayu_Sfx_ladderStruggleAhhagg.wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 28.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 354.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -8.600000083446503, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "06_Mayu_Sfx_ladderStruggleAhhagg.wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1139.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/Mayu_Saywa_Kathy/wav/06_Mayu_Sfx_ladderStruggleAhhagg.wav" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 625.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 3", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 220.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "MayuYize_-01.wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 53.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": 13.85869565217391, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": false, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "MayuYize_-01.wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 168.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/Mayu_Yize/MayuYize_-01.wav" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 717.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "MayuYize_-03.wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 37.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": 11.413043478260864, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "MayuYize_-03.wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 107.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null, "target_url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/Mayu_Yize/MayuYize_-03.wav" } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 98.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 4", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1125.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 5", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 444.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 12.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 11.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -4.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/_deliveriesTO_EDIT/amaru/delivery_001/davinci/Picchu_Animatic_016.dra/MediaFiles/Sdisk/Picchu/davinci/sq200/Slide On Concrete Sound Effect (128 kbps).mp3" } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 2588.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 11.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 68.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -4.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/_deliveriesTO_EDIT/amaru/delivery_001/davinci/Picchu_Animatic_016.dra/MediaFiles/Sdisk/Picchu/davinci/sq200/Slide On Concrete Sound Effect (128 kbps).mp3" } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 2588.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 7.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 135.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -4.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/_deliveriesTO_EDIT/amaru/delivery_001/davinci/Picchu_Animatic_016.dra/MediaFiles/Sdisk/Picchu/davinci/sq200/Slide On Concrete Sound Effect (128 kbps).mp3" } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 2588.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 9.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 202.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -4.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/_deliveriesTO_EDIT/amaru/delivery_001/davinci/Picchu_Animatic_016.dra/MediaFiles/Sdisk/Picchu/davinci/sq200/Slide On Concrete Sound Effect (128 kbps).mp3" } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 2588.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 13.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 262.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -4.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/_deliveriesTO_EDIT/amaru/delivery_001/davinci/Picchu_Animatic_016.dra/MediaFiles/Sdisk/Picchu/davinci/sq200/Slide On Concrete Sound Effect (128 kbps).mp3" } }, "name": "Slide On Concrete Sound Effect (128 kbps).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 2588.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 580.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Snake Hissing sound effect no copyright _ snake sounds _ snake noises _ HQ (128 kbps).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 47.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 199.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/SFX/Snake Hissing sound effect no copyright _ snake sounds _ snake noises _ HQ (128 kbps).mp3" } }, "name": "Snake Hissing sound effect no copyright _ snake sounds _ snake noises _ HQ (128 kbps).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 690.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 6", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Cold wind sound effect (128 kbps).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 500.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": false, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/SFX/Cold wind sound effect (128 kbps).mp3" } }, "name": "Cold wind sound effect (128 kbps).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1290.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Cold wind sound effect (128 kbps).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 192.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 616.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": false, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/SFX/Cold wind sound effect (128 kbps).mp3" } }, "name": "Cold wind sound effect (128 kbps).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1290.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 433.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 7", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 676.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Andean_SFX-02.wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 191.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -4.306451612903228, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/SFX/Andean_SFX-02.wav" } }, "name": "Andean_SFX-02.wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 191.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 258.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 8", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "PICCHU MAQUETA .wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 759.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -11.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/_deliveriesTO_EDIT/amaru/delivery_001/davinci/Picchu_Animatic_016.dra/MediaFiles/Sdisk/Picchu/davinci/sq200/PICCHU MAQUETA .wav" } }, "name": "PICCHU MAQUETA .wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1491.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 366.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 9", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Pachamama Opening Theme-(1080p).wav", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1034.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": {}, "Parameter ID": "volume", "Parameter Value": -5.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": false, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/music/Pachamama Opening Theme-(1080p).wav" } }, "name": "Pachamama Opening Theme-(1080p).wav", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 2835.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 91.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 10", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 751.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Sea of Clouds-(1080p).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 374.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 448.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": { "194": { "Value": -2.0, "Variant Type": "Double" }, "241": { "Value": -29.540000000000008, "Variant Type": "Double" }, "96": { "Value": 0.0, "Variant Type": "Double" } }, "Parameter ID": "volume", "Parameter Value": -2.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/music/Sea of Clouds-(1080p).mp3" } }, "name": "Sea of Clouds-(1080p).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 3329.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Audio" }, { "OTIO_SCHEMA": "Track.1", "metadata": { "Resolve_OTIO": { "Audio Type": "Stereo", "Locked": false, "SoloOn": false } }, "name": "Audio 11", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Gap.1", "metadata": {}, "name": "", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 872.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "effects": [], "markers": [], "enabled": true }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "Resolve_OTIO": { "Channels": [ { "Source Channel ID": 0, "Source Track ID": 0 }, { "Source Channel ID": 1, "Source Track ID": 0 } ] } }, "name": "Sea of Clouds-(1080p).mp3", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 253.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 448.0 } }, "effects": [ { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Volume and Fades", "Enabled": true, "Name": "Volume", "Parameters": [ { "Default Parameter Value": 0.0, "Key Frames": { "194": { "Value": -2.0, "Variant Type": "Double" }, "241": { "Value": -29.540000000000008, "Variant Type": "Double" }, "96": { "Value": 0.0, "Variant Type": "Double" } }, "Parameter ID": "volume", "Parameter Value": -2.0, "Variant Type": "Double", "maxValue": 30.0, "minValue": -100.0 } ], "Type": 62 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pan", "Enabled": true, "Name": "Pan", "Parameters": [], "Type": 72 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Pitch", "Enabled": true, "Name": "Pitch", "Parameters": [], "Type": 67 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Clip Equaliser", "Enabled": false, "Name": "Equalizer", "Parameters": [], "Type": 64 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 0, "Parameter ID": "eq band index", "Parameter Value": 0, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 1, "Parameter ID": "eq band index", "Parameter Value": 1, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": true, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 2, "Parameter ID": "eq band index", "Parameter Value": 2, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" }, { "OTIO_SCHEMA": "Effect.1", "metadata": { "Resolve_OTIO": { "Effect Name": "Fairlight Equaliser Band", "Enabled": false, "Name": "Equalizer Band", "Parameters": [ { "Default Parameter Value": 3, "Parameter ID": "eq band index", "Parameter Value": 3, "Variant Type": "Int", "maxValue": 3.0, "minValue": -1.0 } ], "Type": 63 } }, "name": "", "effect_name": "Resolve Effect" } ], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": { "Resolve_OTIO": { "Media Url": "/Users/joshm/Downloads/picchu_edit/Picchu_v10_AZ.dra/MediaFiles/Xdisk/picchu/editorial/animatic/Picchu_Animatic_014.dra/MediaFiles/music/Sea of Clouds-(1080p).mp3" } }, "name": "Sea of Clouds-(1080p).mp3", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 3329.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 0.0 } }, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Audio" } ] } }opentimelineio-0.18.1/tests/sample_data/transition.otio0000775000175000017500000002024315110656141021115 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "Figure 2 - Transitions", "global_start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 }, "tracks": { "OTIO_SCHEMA": "Stack.1", "metadata": {}, "name": "tracks", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Track.1", "metadata": {}, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-001", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Media-001", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "target_url": "file:///folder/titles.mov" } }, { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-002", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 6 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 2 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Media-002", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 9 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "target_url": "file:///folder/wind-up.mov" } }, { "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "Transition", "in_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 2 }, "out_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 }, "transition_type": "SMPTE_Dissolve" }, { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-003", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 4 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 } }, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Media-003", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 10 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 1 } }, "target_url": "file:///folder/punchline.mov" } }, { "OTIO_SCHEMA": "Clip.1", "metadata": {}, "name": "Clip-004", "source_range": null, "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "metadata": {}, "name": "Media-004", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 6 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 100 } }, "target_url": "file:///folder/credits.mov" } } ], "kind": "Video" } ] } } opentimelineio-0.18.1/tests/sample_data/transition_test.otio0000664000175000017500000001275115110656141022156 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "transition_test", "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "t0", "transition_type": "SMPTE_Dissolve", "parameters": {}, "in_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 }, "out_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": null, "metadata": {}, "name": "A", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "t1", "transition_type": "SMPTE_Dissolve", "parameters": {}, "in_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 }, "out_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": null, "metadata": {}, "name": "B", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": null, "metadata": {}, "name": "C", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "t3", "transition_type": "SMPTE_Dissolve", "parameters": {}, "in_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 }, "out_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 } } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Sequence1", "source_range": null } ], "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "tracks", "source_range": null } } opentimelineio-0.18.1/tests/sample_data/simple_cut.otio0000775000175000017500000001226315110656141021072 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "Figure 1 - Simple Cut List", "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "metadata": {}, "target_url": "file:///folder/titles.mov", "name": "Media-001" }, "metadata": {}, "name": "Clip-001", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 3 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "metadata": {}, "target_url": "file:///folder/wind-up.mov", "name": "Media-002" }, "metadata": {}, "name": "Clip-002", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 6 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 2 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } }, "metadata": {}, "target_url": "file:///folder/punchline.mov", "name": "Media-003" }, "metadata": {}, "name": "Clip-003", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 4 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "enabled": true, "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 6 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 100 } }, "metadata": {}, "target_url": "file:///folder/credits.mov", "name": "Media-004" }, "metadata": {}, "name": "Clip-004", "source_range": null } ], "effects": [], "kind": "Video", "markers": [], "enabled": true, "metadata": {}, "name": "Track-001", "source_range": null } ], "effects": [], "markers": [], "enabled": true, "metadata": {}, "name": "tracks", "source_range": null } }opentimelineio-0.18.1/tests/sample_data/screening_example.otio0000664000175000017500000004676615110656141022432 0ustar meme{ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "Example_Screening.01", "global_start_time": null, "tracks": { "OTIO_SCHEMA": "Stack.1", "metadata": {}, "name": "tracks", "source_range": null, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Track.1", "metadata": {}, "name": "V", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 1049.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": -86243.0 } }, "effects": [], "markers": [], "enabled": true, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "SOURCE FILE: ZZ100_501.LAY3.01" ], "reel": "ZZ100_50" } }, "name": "ZZ100_501 (LAY3)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 31.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86501.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "SOURCE FILE: ZZ100_502A.LAY3.02" ], "reel": "ZZ100_50" } }, "name": "ZZ100_502A (LAY3)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 50.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86557.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "SOURCE FILE: ZZ100_503A.LAY1.01" ], "reel": "ZZ100_50" } }, "name": "ZZ100_503A (LAY1)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 28.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86601.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "SOURCE FILE: ZZ100_504C.LAY1.02" ], "reel": "ZZ100_50" } }, "name": "ZZ100_504C (LAY1)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 115.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86641.0 } }, "effects": [], "markers": [ { "OTIO_SCHEMA": "Marker.2", "metadata": { "cmx_3600": { "color": "RED" } }, "name": "ANIM FIX NEEDED", "color": "RED", "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 1.0, "value": 0.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86438.0 } } }, { "OTIO_SCHEMA": "Marker.2", "metadata": { "cmx_3600": { "color": "PINK" } }, "name": "ANIM FIX NEEDED", "color": "PINK", "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 1.0, "value": 0.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86462.0 } } } ], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "SOURCE FILE: ZZ100_504B.LAY1.02" ], "reel": "ZZ100_50" } }, "name": "ZZ100_504B (LAY1)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 101.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86753.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "SOURCE FILE: ZZ100_507C.LAY2.01" ], "reel": "ZZ100_50" } }, "name": "ZZ100_507C (LAY2)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 161.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86501.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "SOURCE FILE: ZZ100_508.LAY2.02" ], "reel": "ZZ100_50" } }, "name": "ZZ100_508 (LAY2)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 170.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86628.0 } }, "effects": [], "markers": [ { "OTIO_SCHEMA": "Marker.2", "metadata": { "cmx_3600": { "color": "GREEN" } }, "name": "", "color": "GREEN", "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 1.0, "value": 0.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86773.0 } } } ], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "SOURCE FILE: ZZ100_510.LAY1.02" ], "reel": "ZZ100_51" } }, "name": "ZZ100_510 (LAY1)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 136.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86722.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" }, { "OTIO_SCHEMA": "Clip.2", "metadata": { "cmx_3600": { "comments": [ "AVX2 EFFECT, RESIZE", "SOURCE FILE: ZZ100_510B.LAY1.02" ], "reel": "ZZ100_51" } }, "name": "ZZ100_510B (LAY1)", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 257.0 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24.0, "value": 86501.0 } }, "effects": [], "markers": [], "enabled": true, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Video" } ] } }opentimelineio-0.18.1/tests/test_transition.py0000664000175000017500000001242515110656141017360 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Transition class test harness.""" import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class TransitionTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_constructor(self): trx = otio.schema.Transition( name="AtoB", transition_type="SMPTE.Dissolve", metadata={ "foo": "bar" } ) self.assertEqual(trx.transition_type, "SMPTE.Dissolve") self.assertEqual(trx.name, "AtoB") self.assertEqual(trx.metadata, {"foo": "bar"}) def test_serialize(self): trx = otio.schema.Transition( name="AtoB", transition_type="SMPTE.Dissolve", metadata={ "foo": "bar" } ) encoded = otio.adapters.otio_json.write_to_string(trx) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(trx, decoded) def test_stringify(self): trx = otio.schema.Transition("SMPTE.Dissolve") in_offset = otio.opentime.RationalTime(5, 24) out_offset = otio.opentime.RationalTime(1, 24) trx.in_offset = in_offset trx.out_offset = out_offset self.assertMultiLineEqual( str(trx), "Transition(" '"{}", ' '"{}", ' '{}, ' "{}, " "{}" ")".format( str(trx.name), str(trx.transition_type), str(trx.in_offset), str(trx.out_offset), str(trx.metadata), ) ) self.assertMultiLineEqual( repr(trx), "otio.schema.Transition(" "name={}, " "transition_type={}, " "in_offset={}, " "out_offset={}, " "metadata={}" ")".format( repr(trx.name), repr(trx.transition_type), repr(trx.in_offset), repr(trx.out_offset), repr(trx.metadata), ) ) def test_setters(self): trx = otio.schema.Transition( name="AtoB", transition_type="SMPTE.Dissolve", metadata={ "foo": "bar" } ) self.assertEqual(trx.transition_type, "SMPTE.Dissolve") trx.transition_type = "EdgeWipe" self.assertEqual(trx.transition_type, "EdgeWipe") def test_parent_range(self): timeline = otio.schema.Timeline( tracks=[ otio.schema.Track( name="V1", children=[ otio.schema.Clip( name="A", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=1, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ) ), otio.schema.Transition( in_offset=otio.opentime.RationalTime( value=7, rate=30 ), out_offset=otio.opentime.RationalTime( value=10, rate=30 ), ), otio.schema.Clip( name="B", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ) ), ] ) ] ) trx = timeline.tracks[0][1] time_range = otio.opentime.TimeRange(otio.opentime.RationalTime(43, 30), otio.opentime.RationalTime(17, 30)) self.assertEqual(time_range, trx.range_in_parent()) self.assertEqual(time_range, trx.trimmed_range_in_parent()) trx = otio.schema.Transition( in_offset=otio.opentime.RationalTime( value=7, rate=30 ), out_offset=otio.opentime.RationalTime( value=10, rate=30 ), ) with self.assertRaises(otio.exceptions.NotAChildError): trx.range_in_parent() with self.assertRaises(otio.exceptions.NotAChildError): trx.trimmed_range_in_parent() if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_effect.py0000664000175000017500000000724215110656141016423 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class EffectTest(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): ef = otio.schema.Effect( name="blur it", effect_name="blur", metadata={"foo": "bar"}, enabled=False ) self.assertEqual(ef.enabled, False) encoded = otio.adapters.otio_json.write_to_string(ef) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(ef, decoded) self.assertEqual(decoded.name, "blur it") self.assertEqual(decoded.effect_name, "blur") self.assertEqual(decoded.metadata['foo'], 'bar') self.assertEqual(decoded.enabled, False) def test_eq(self): ef = otio.schema.Effect( name="blur it", effect_name="blur", metadata={"foo": "bar"} ) ef2 = otio.schema.Effect( name="blur it", effect_name="blur", metadata={"foo": "bar"} ) self.assertIsOTIOEquivalentTo(ef, ef2) def test_str(self): ef = otio.schema.Effect( name="blur it", effect_name="blur", metadata={"foo": "bar"}, enabled=False ) self.assertMultiLineEqual( str(ef), "Effect({}, {}, {}, {})".format( str(ef.name), str(ef.effect_name), str(ef.metadata), str(ef.enabled) ) ) self.assertMultiLineEqual( repr(ef), "otio.schema.Effect(" "name={}, " "effect_name={}, " "metadata={}, " "enabled={}" ")".format( repr(ef.name), repr(ef.effect_name), repr(ef.metadata), repr(ef.enabled) ) ) def test_setters(self): ef = otio.schema.Effect( name="blur it", effect_name="blur", metadata={"foo": "bar"} ) self.assertEqual(ef.effect_name, "blur") ef.effect_name = "flop" self.assertEqual(ef.effect_name, "flop") self.assertEqual(ef.enabled, True) class TestLinearTimeWarp(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): ef = otio.schema.LinearTimeWarp("Foo", 2.5, {'foo': 'bar'}) self.assertEqual(ef.effect_name, "LinearTimeWarp") self.assertEqual(ef.name, "Foo") self.assertEqual(ef.time_scalar, 2.5) self.assertEqual(ef.metadata, {"foo": "bar"}) self.assertEqual(ef.enabled, True) def test_serialize(self): ef = otio.schema.LinearTimeWarp("Foo", 2.5, {'foo': 'bar'}) encoded = otio.adapters.otio_json.write_to_string(ef) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(ef, decoded) def test_setters(self): ef = otio.schema.LinearTimeWarp("Foo", 2.5, {'foo': 'bar'}) self.assertEqual(ef.time_scalar, 2.5) ef.time_scalar = 5.0 self.assertEqual(ef.time_scalar, 5.0) class TestFreezeFrame(unittest.TestCase): def test_cons(self): ef = otio.schema.FreezeFrame("Foo", {'foo': 'bar'}) self.assertEqual(ef.effect_name, "FreezeFrame") self.assertEqual(ef.name, "Foo") self.assertEqual(ef.time_scalar, 0) self.assertEqual(ef.metadata, {"foo": "bar"}) self.assertEqual(ef.enabled, True) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_item.py0000775000175000017500000004324615110656141016134 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test harness for Item.""" import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils # add Item to the type registry for the purposes of unit testing # otio.core.register_type(otio.core.Item) class GapTester(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_str_gap(self): gp = otio.schema.Gap() self.assertMultiLineEqual( str(gp), "Gap(" + str(gp.name) + ", " + str(gp.source_range) + ", " + str(gp.effects) + ", " + str(gp.markers) + ", " + str(gp.enabled) + ", " + str(gp.metadata) + ")" ) self.assertMultiLineEqual( repr(gp), "otio.schema.Gap(" "name={}, " "source_range={}, " "effects={}, " "markers={}, " "enabled={}, " "metadata={}" ")".format( repr(gp.name), repr(gp.source_range), repr(gp.effects), repr(gp.markers), repr(gp.enabled), repr(gp.metadata), ) ) encoded = otio.adapters.otio_json.write_to_string(gp) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertJsonEqual(gp, decoded) def test_convert_from_filler(self): gp = otio.schema.Gap() gp._serializable_label = "Filler.1" encoded = otio.adapters.otio_json.write_to_string(gp) decoded = otio.adapters.otio_json.read_from_string(encoded) isinstance(decoded, otio.schema.Gap) def test_not_both_source_range_and_duration(self): with self.assertRaises(TypeError): otio.schema.Gap( duration=otio.opentime.RationalTime(10, 24), source_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(10, 24) ) ) self.assertJsonEqual( otio.schema.Gap( duration=otio.opentime.RationalTime(10, 24), ), otio.schema.Gap( source_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(10, 24) ) ) ) class ItemTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_constructor(self): tr = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 1), otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(name="foo", source_range=tr) self.assertEqual(it.source_range, tr) self.assertEqual(it.name, "foo") encoded = otio.adapters.otio_json.write_to_string(it) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(it, decoded) def test_copy_arguments(self): # make sure all the arguments are copied and not referenced tr = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(10, 24), ) name = "foobar" effects = [] markers = [] metadata = {} it = otio.core.Item( name=name, source_range=tr, effects=effects, markers=markers, metadata=metadata, ) name = 'foobaz' self.assertNotEqual(it.name, name) tr = otio.opentime.TimeRange( otio.opentime.RationalTime(1, tr.start_time.rate), duration=tr.duration ) self.assertNotEqual(it.source_range.start_time, tr.start_time) markers.append(otio.schema.Marker()) self.assertNotEqual(it.markers, markers) metadata['foo'] = 'bar' self.assertNotEqual(it.metadata, metadata) def test_duration(self): it = otio.core.Item() tr = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 1), otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(source_range=tr) self.assertEqual(it.duration(), tr.duration) def test_available_range(self): it = otio.core.Item() with self.assertRaises(NotImplementedError): it.available_range() def test_duration_and_source_range(self): it = otio.core.Item() with self.assertRaises(NotImplementedError): it.duration() self.assertEqual(None, it.source_range) tr = otio.opentime.TimeRange( otio.opentime.RationalTime(1, 1), otio.opentime.RationalTime(10, 1) ) it2 = otio.core.Item(source_range=tr) self.assertEqual(tr, it2.source_range) self.assertEqual(tr.duration, it2.duration()) self.assertIsNot(tr.duration, it2.duration()) def test_trimmed_range(self): it = otio.core.Item() with self.assertRaises(NotImplementedError): it.trimmed_range() tr = otio.opentime.TimeRange( otio.opentime.RationalTime(1, 1), otio.opentime.RationalTime(10, 1) ) it2 = otio.core.Item(source_range=tr) self.assertEqual(it2.trimmed_range(), tr) self.assertIsNot(it2.trimmed_range(), tr) def test_serialize(self): tr = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 1), otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(source_range=tr) encoded = otio.adapters.otio_json.write_to_string(it) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(it, decoded) def test_stringify(self): tr = otio.opentime.TimeRange( duration=otio.opentime.RationalTime(10, 1)) it = otio.core.Item(source_range=tr) self.assertMultiLineEqual( str(it), "Item(" "{}, " "{}, " "{}, " "{}, " "{}, " "{}" ")".format( str(it.name), str(it.source_range), str(it.effects), str(it.markers), str(it.enabled), str(it.metadata), ) ) self.assertMultiLineEqual( repr(it), "otio.core.Item(" "name={}, " "source_range={}, " "effects={}, " "markers={}, " "enabled={}, " "metadata={}" ")".format( repr(it.name), repr(it.source_range), repr(it.effects), repr(it.markers), repr(it.enabled), repr(it.metadata), ) ) def test_metadata(self): tr = otio.opentime.TimeRange( duration=otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(source_range=tr) it.metadata["foo"] = "bar" encoded = otio.adapters.otio_json.write_to_string(it) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(it, decoded) self.assertEqual(decoded.metadata["foo"], it.metadata["foo"]) foo = it.metadata.pop("foo") self.assertEqual(foo, "bar") foo = it.metadata.pop("foo", "default") self.assertEqual(foo, "default") with self.assertRaises(KeyError): it.metadata.pop("foo") def test_add_effect(self): tr = otio.opentime.TimeRange( duration=otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(source_range=tr) it.effects.append( otio.schema.Effect( effect_name="blur", metadata={ 'amount': '100' } ) ) encoded = otio.adapters.otio_json.write_to_string(it) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(it, decoded) self.assertJsonEqual(it.effects, decoded.effects) def test_add_marker(self): tr = otio.opentime.TimeRange( duration=otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(source_range=tr) it.markers.append( otio.schema.Marker( name="test_marker", marked_range=tr, metadata={ 'some stuff to mark': '100' } ) ) encoded = otio.adapters.otio_json.write_to_string(it) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(it, decoded) self.assertJsonEqual(it.markers, decoded.markers) def test_enabled(self): tr = otio.opentime.TimeRange( duration=otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(source_range=tr) self.assertEqual(it.enabled, True) it.enabled = False self.assertEqual(it.enabled, False) encoded = otio.adapters.otio_json.write_to_string(it) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(it, decoded) self.assertJsonEqual(it.enabled, decoded.enabled) def test_copy(self): tr = otio.opentime.TimeRange( duration=otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(source_range=tr, metadata={"foo": "bar"}) it.markers.append( otio.schema.Marker( name="test_marker", marked_range=tr, metadata={ 'some stuff to mark': '100' } ) ) it.effects.append( otio.schema.Effect( effect_name="blur", metadata={ 'amount': '100' } ) ) it.metadata["foo"] = "bar2" # deep copy should have different dictionaries it_dcopy = it.deepcopy() it_dcopy.metadata["foo"] = "not bar" self.assertNotEqual(it.metadata, it_dcopy.metadata) def test_copy_library(self): tr = otio.opentime.TimeRange( duration=otio.opentime.RationalTime(10, 1) ) it = otio.core.Item(source_range=tr, metadata={"foo": "bar"}) it.markers.append( otio.schema.Marker( name="test_marker", marked_range=tr, metadata={ 'some stuff to mark': '100' } ) ) it.effects.append( otio.schema.Effect( effect_name="blur", metadata={ 'amount': '100' } ) ) import copy # deep copy should have different dictionaries it_dcopy = copy.deepcopy(it) it_dcopy.metadata["foo"] = "not bar" self.assertNotEqual(it.metadata, it_dcopy.metadata) def test_visible_range(self): timeline = otio.schema.Timeline( tracks=[ otio.schema.Track( name="V1", children=[ otio.schema.Clip( name="A", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=1, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ) ), otio.schema.Transition( in_offset=otio.opentime.RationalTime( value=7, rate=30 ), out_offset=otio.opentime.RationalTime( value=10, rate=30 ), ), otio.schema.Clip( name="B", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ) ), otio.schema.Transition( in_offset=otio.opentime.RationalTime( value=17, rate=30 ), out_offset=otio.opentime.RationalTime( value=15, rate=30 ), ), otio.schema.Clip( name="C", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=50, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ) ), otio.schema.Clip( name="D", source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=1, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ) ) ] ) ] ) self.maxDiff = None self.assertListEqual( ["A", "B", "C", "D"], [item.name for item in timeline.find_clips()] ) self.assertListEqual( [ otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=1, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=50, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=1, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ), ], [item.trimmed_range() for item in timeline.find_clips()] ) self.assertListEqual( [ otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=1, rate=30 ), duration=otio.opentime.RationalTime( value=50 + 10, rate=30 ) ), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=100 - 7, rate=30 ), duration=otio.opentime.RationalTime( value=50 + 15 + 7, rate=30 ) ), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=33, rate=30 ), duration=otio.opentime.RationalTime( value=50 + 17, rate=30 ) ), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=1, rate=30 ), duration=otio.opentime.RationalTime( value=50, rate=30 ) ), ], [item.visible_range() for item in timeline.find_clips()] ) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_url_conversions.py0000664000175000017500000000605415110656141020421 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """ Unit tests of functions that convert between file paths and urls. """ import unittest import os import opentimelineio as otio SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") SCREENING_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "screening_example.otio") MEDIA_EXAMPLE_PATH_REL = os.path.relpath( os.path.join( os.path.dirname(__file__), "..", # root "docs", "_static", "OpenTimelineIO@3xDark.png" ) ) MEDIA_EXAMPLE_PATH_URL_REL = otio.url_utils.url_from_filepath( MEDIA_EXAMPLE_PATH_REL ) MEDIA_EXAMPLE_PATH_ABS = os.path.abspath( MEDIA_EXAMPLE_PATH_REL.replace( "3xDark", "3xLight" ) ) MEDIA_EXAMPLE_PATH_URL_ABS = otio.url_utils.url_from_filepath( MEDIA_EXAMPLE_PATH_ABS ) WINDOWS_ENCODED_URL = "file://host/S%3a/path/file.ext" WINDOWS_DRIVE_URL = "file://S:/path/file.ext" WINDOWS_DRIVE_PATH = "S:/path/file.ext" WINDOWS_ENCODED_UNC_URL = "file://unc/path/sub%20dir/file.ext" WINDOWS_UNC_URL = "file://unc/path/sub dir/file.ext" WINDOWS_UNC_PATH = "//unc/path/sub dir/file.ext" POSIX_LOCALHOST_URL = "file://localhost/path/sub dir/file.ext" POSIX_ENCODED_URL = "file:///path/sub%20dir/file.ext" POSIX_URL = "file:///path/sub dir/file.ext" POSIX_PATH = "/path/sub dir/file.ext" class TestConversions(unittest.TestCase): def test_roundtrip_abs(self): self.assertTrue(MEDIA_EXAMPLE_PATH_URL_ABS.startswith("file://")) full_path = os.path.abspath( otio.url_utils.filepath_from_url(MEDIA_EXAMPLE_PATH_URL_ABS) ) # should have reconstructed it by this point self.assertEqual(full_path, MEDIA_EXAMPLE_PATH_ABS) def test_roundtrip_rel(self): self.assertFalse(MEDIA_EXAMPLE_PATH_URL_REL.startswith("file://")) result = otio.url_utils.filepath_from_url(MEDIA_EXAMPLE_PATH_URL_REL) # should have reconstructed it by this point self.assertEqual(os.path.normpath(result), MEDIA_EXAMPLE_PATH_REL) def test_windows_urls(self): for url in (WINDOWS_ENCODED_URL, WINDOWS_DRIVE_URL): processed_url = otio.url_utils.filepath_from_url(url) self.assertEqual(processed_url, WINDOWS_DRIVE_PATH) def test_windows_unc_urls(self): for url in (WINDOWS_ENCODED_UNC_URL, WINDOWS_UNC_URL): processed_url = otio.url_utils.filepath_from_url(url) self.assertEqual(processed_url, WINDOWS_UNC_PATH) def test_posix_urls(self): for url in (POSIX_ENCODED_URL, POSIX_URL, POSIX_LOCALHOST_URL): processed_url = otio.url_utils.filepath_from_url(url) self.assertEqual(processed_url, POSIX_PATH) def test_relative_url(self): # see github issue #1817 - when a relative URL has only one name after # the "." (ie ./blah but not ./blah/blah) self.assertEqual( otio.url_utils.filepath_from_url(os.path.join(".", "docs")), "docs", ) if __name__ == "__main__": unittest.main() opentimelineio-0.18.1/tests/test_serialized_schema.py0000664000175000017500000001021315110656141020632 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import os import sys import subprocess from opentimelineio.console import ( autogen_serialized_datamodel as asd, autogen_plugin_documentation as apd, autogen_version_map as avm ) @unittest.skipIf( os.environ.get("OTIO_DISABLE_SERIALIZED_SCHEMA_TEST"), "Serialized schema test disabled because " "$OTIO_DISABLE_SERIALIZED_SCHEMA_TEST is set to something other than ''" ) class SerializedSchemaTester(unittest.TestCase): def test_serialized_schema(self): """Test if the schema has changed since last time the serialized schema documentation was generated. """ pt = os.path.dirname(os.path.dirname(__file__)) fp = os.path.join(pt, "docs", "tutorials", "otio-serialized-schema.md") with open(fp) as fi: baseline_text = fi.read() test_text, _ = asd.generate_and_write_documentation() self.maxDiff = None self.longMessage = True self.assertMultiLineEqual( baseline_text, test_text, "\n The schema has changed and the autogenerated documentation in {}" " needs to be updated. run: `make doc-model-update`".format(fp) ) @unittest.skipIf( os.environ.get("OTIO_DISABLE_SERIALIZED_SCHEMA_TEST"), "Plugin documentation test disabled because " "$OTIO_DISABLE_SERIALIZED_SCHEMA_TEST is set to something other than ''" ) class PluginDocumentationTester(unittest.TestCase): def test_plugin_documentation(self): """Verify that the plugin manifest matches what is checked into the documentation. """ pt = os.path.dirname(os.path.dirname(__file__)) fp = os.path.join(pt, "docs", "tutorials", "otio-plugins.md") with open(fp) as fi: baseline_text = fi.read() test_text = apd.generate_and_write_documentation_plugins( public_only=True, sanitized_paths=True ) self.maxDiff = None self.longMessage = True self.assertMultiLineEqual( baseline_text, test_text, "\n The schema has changed and the autogenerated documentation in {}" " needs to be updated. run: `make doc-plugins-update`".format(fp) ) @unittest.skipIf( os.environ.get("OTIO_DISABLE_SERIALIZED_SCHEMA_TEST"), "CORE_VERSION_MAP generation test disabled because " "$OTIO_DISABLE_SERIALIZED_SCHEMA_TEST is set to something other than ''" ) class CoreVersionMapGenerationTester(unittest.TestCase): def test_core_version_map_generator(self): """Verify the current CORE_VERSION_MAP matches the checked in one.""" pt = os.path.dirname(os.path.dirname(__file__)) root = os.path.join(pt, "src", "opentimelineio") template_fp = os.path.join(root, "CORE_VERSION_MAP.last.cpp") target_fp = os.path.join(root, "CORE_VERSION_MAP.cpp") with open(target_fp) as fi: # sanitize line endings and remove empty lines for cross-windows # /*nix consistent behavior baseline_text = "\n".join( ln for ln in fi.read().splitlines() if ln ) proc = subprocess.Popen( [ sys.executable, avm.__file__, "-i", template_fp, "-d", ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout, _ = proc.communicate() test_text = stdout.decode("utf-8")[:-1] # sanitize the line endings test_text = "\n".join( ln for ln in test_text.splitlines() if ln ) self.maxDiff = None self.longMessage = True self.assertMultiLineEqual( baseline_text, test_text, "\n The CORE_VERSION_MAP has changed and the autogenerated one in" " {} needs to be updated. run: `make version-map-update`".format( target_fp ) ) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_multithreading.py0000664000175000017500000000465015110656141020207 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import threading import weakref import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class MultithreadingTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test1(self): self.sc = otio.schema.SerializableCollection() child = otio.core.SerializableObject() child.extra = 17 wc = weakref.ref(child) self.assertEqual(wc() is not None, True) self.sc.append(child) del child threads = [] for i in range(5): t = threading.Thread(target=self.bash_retainers1) t.daemon = True t.start() threads.append(t) for t in threads: t.join() self.assertEqual(self.sc[0].extra, 17) self.sc.pop() self.assertEqual(wc() is None, True) def bash_retainers1(self): otio._otio._testing.bash_retainers1(self.sc) def test2(self): sc = otio.schema.SerializableCollection() child = otio.core.SerializableObject() sc.append(child) self.materialized = False self.sc = sc.clone() self.lock = threading.Lock() # self.sc[0] has not been given out to Python yet threads = [] for i in range(5): t = threading.Thread(target=self.bash_retainers2) t.daemon = True t.start() threads.append(t) for t in threads: t.join() self.assertEqual(self.wc() is not None, True) self.assertEqual(self.wc().extra, 37) del self.sc self.assertEqual(self.wc() is None, True) def test3(self): t = threading.Thread(target=self.gil_scoping) t.daemon = True t.start() t.join() def test4(self): self.gil_scoping() def gil_scoping(self): otio._otio._testing.gil_scoping() def materialize(self): with self.lock: if not self.materialized: self.materialized = True child = self.sc[0] self.wc = weakref.ref(child) self.assertEqual(self.wc() is not None, True) child.extra = 37 del child def bash_retainers2(self): otio._otio._testing.bash_retainers2(self.sc, self.materialize) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_timeline_algo.py0000664000175000017500000003505315110656141020000 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test file for the track algorithms library.""" import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class TimelineTrimmingTests(unittest.TestCase, otio_test_utils.OTIOAssertions): """ test harness for timeline trimming function """ def make_sample_timeline(self): result = otio.adapters.read_from_string( """ { "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": null, "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "A", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "B", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "C", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } ], "effects": [], "markers": [], "metadata": {}, "name": "tracks", "source_range": null } }""", "otio_json" ) return result, result.tracks[0] def test_trim_to_existing_range(self): original_timeline, original_track = self.make_sample_timeline() self.assertEqual( original_track.trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(150, 24) ) ) # trim to the exact range it already has trimmed = otio.algorithms.timeline_trimmed_to_range( original_timeline, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(150, 24) ) ) # it shouldn't have changed at all self.assertIsOTIOEquivalentTo(original_timeline, trimmed) def test_trim_to_longer_range(self): original_timeline, original_track = self.make_sample_timeline() # trim to a larger range trimmed = otio.algorithms.timeline_trimmed_to_range( original_timeline, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(-10, 24), duration=otio.opentime.RationalTime(160, 24) ) ) # it shouldn't have changed at all self.assertJsonEqual(original_timeline, trimmed) def test_trim_front(self): original_timeline, original_track = self.make_sample_timeline() # trim off the front (clip A and part of B) trimmed = otio.algorithms.timeline_trimmed_to_range( original_timeline, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(60, 24), duration=otio.opentime.RationalTime(90, 24) ) ) self.assertNotEqual(original_timeline, trimmed) trimmed = trimmed.tracks[0] self.assertEqual(len(trimmed), 2) self.assertEqual( trimmed.trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(90, 24) ) ) # did clip B get trimmed? self.assertEqual(trimmed[0].name, "B") self.assertEqual( trimmed[0].trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(10, 24), duration=otio.opentime.RationalTime(40, 24) ) ) # clip C should have been left alone self.assertIsOTIOEquivalentTo(trimmed[1], original_track[2]) def test_trim_end(self): original_timeline, original_track = self.make_sample_timeline() # trim off the end (clip C and part of B) trimmed_timeline = otio.algorithms.timeline_trimmed_to_range( original_timeline, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(90, 24) ) ) # rest of the tests are on the track trimmed = trimmed_timeline.tracks[0] self.assertNotEqual(original_timeline, trimmed) self.assertEqual(len(trimmed), 2) self.assertEqual( trimmed.trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(90, 24) ) ) # clip A should have been left alone self.assertIsOTIOEquivalentTo(trimmed[0], original_track[0]) # did clip B get trimmed? self.assertEqual(trimmed[1].name, "B") self.assertEqual( trimmed[1].trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(40, 24) ) ) def test_trim_with_transitions(self): original_timeline, original_track = self.make_sample_timeline() self.assertEqual( otio.opentime.RationalTime(150, 24), original_timeline.duration() ) self.assertEqual(len(original_track), 3) # add a transition tr = otio.schema.Transition( in_offset=otio.opentime.RationalTime(12, 24), out_offset=otio.opentime.RationalTime(20, 24) ) original_track.insert(1, tr) self.assertEqual(len(original_track), 4) self.assertEqual( otio.opentime.RationalTime(150, 24), original_timeline.duration() ) # if you try to sever a Transition in the middle it should fail with self.assertRaises(otio.exceptions.CannotTrimTransitionsError): trimmed = otio.algorithms.timeline_trimmed_to_range( original_timeline, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(5, 24), duration=otio.opentime.RationalTime(50, 24) ) ) with self.assertRaises(otio.exceptions.CannotTrimTransitionsError): trimmed = otio.algorithms.timeline_trimmed_to_range( original_timeline, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(45, 24), duration=otio.opentime.RationalTime(50, 24) ) ) trimmed = otio.algorithms.timeline_trimmed_to_range( original_timeline, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(25, 24), duration=otio.opentime.RationalTime(50, 24) ) ) self.assertNotEqual(original_timeline, trimmed) expected = otio.adapters.read_from_string( """ { "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": null, "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "A", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 25 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 25.0 } } }, { "OTIO_SCHEMA": "Transition.1", "in_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 12 }, "metadata": {}, "name": null, "out_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 20 }, "transition_type": null }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "B", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 25 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } ], "effects": [], "markers": [], "metadata": {}, "name": "tracks", "source_range": null } } """, "otio_json" ) self.assertJsonEqual(expected, trimmed) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_marker.py0000775000175000017500000001057015110656141016451 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class MarkerTest(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_cons(self): tr = otio.opentime.TimeRange( otio.opentime.RationalTime(5, 24), otio.opentime.RationalTime(10, 24) ) m = otio.schema.Marker( name="marker_1", marked_range=tr, color=otio.schema.MarkerColor.GREEN, metadata={'foo': 'bar'} ) self.assertEqual(m.name, 'marker_1') self.assertEqual(m.metadata['foo'], 'bar') self.assertEqual(m.marked_range, tr) self.assertEqual(m.color, otio.schema.MarkerColor.GREEN) encoded = otio.adapters.otio_json.write_to_string(m) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(m, decoded) def test_upgrade(self): src = """ { "OTIO_SCHEMA" : "Marker.1", "metadata" : {}, "name" : null, "range" : { "OTIO_SCHEMA" : "TimeRange.1", "start_time" : { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 5, "value" : 0 }, "duration" : { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 5, "value" : 0 } } } """ marker = otio.adapters.read_from_string(src, "otio_json") self.assertEqual( marker.marked_range, otio.opentime.TimeRange( otio.opentime.RationalTime(0, 5), otio.opentime.RationalTime(0, 5), ) ) def test_equality(self): m = otio.schema.Marker() bo = otio.core.Item() self.assertNotEqual(m, bo) self.assertNotEqual(bo, m) def test_repr(self): tr = otio.opentime.TimeRange( otio.opentime.RationalTime(5, 24), otio.opentime.RationalTime(10, 24) ) m = otio.schema.Marker( name="marker_1", marked_range=tr, color=otio.schema.MarkerColor.GREEN, metadata={'foo': 'bar'} ) expected = ( "otio.schema.Marker(name='marker_1', " "marked_range={}, metadata={})".format( repr(tr), repr(m.metadata) ) ) self.assertEqual(repr(m), expected) def test_comment(self): src = """ { "OTIO_SCHEMA" : "Marker.1", "metadata" : {}, "name" : null, "comment": "foo bar", "range" : { "OTIO_SCHEMA" : "TimeRange.1", "start_time" : { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 5, "value" : 0 }, "duration" : { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 5, "value" : 0 } } } """ marker = otio.adapters.read_from_string(src, "otio_json") self.assertEqual(marker.comment, "foo bar") tr = otio.opentime.TimeRange( otio.opentime.RationalTime(5, 24), otio.opentime.RationalTime(10, 24) ) m = otio.schema.Marker( name="marker_1", marked_range=tr, metadata={'foo': 'bar'}, comment="foo bar2") self.assertEqual(m.comment, "foo bar2") encoded = otio.adapters.otio_json.write_to_string(m) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertEqual(decoded.comment, "foo bar2") def test_str(self): tr = otio.opentime.TimeRange( otio.opentime.RationalTime(5, 24), otio.opentime.RationalTime(10, 24) ) m = otio.schema.Marker( name="marker_1", marked_range=tr, color=otio.schema.MarkerColor.GREEN, metadata={'foo': 'bar'} ) expected = 'Marker(marker_1, {}, {})'.format( str(tr), str(m.metadata) ) self.assertEqual(str(m), expected) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_color.py0000664000175000017500000000665515110656141016314 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class ColorTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_convert_to(self): white = otio.core.Color.WHITE self.assertEqual(white.r, 1.0) self.assertEqual(white.g, 1.0) self.assertEqual(white.b, 1.0) self.assertEqual(white.a, 1.0) self.assertEqual(white.to_hex(), "#ffffffff") self.assertEqual(white.to_rgba_int_list(8), [255, 255, 255, 255]) self.assertEqual(white.to_agbr_integer(), 4294967295) self.assertEqual(white.to_rgba_float_list(), [1.0, 1.0, 1.0, 1.0]) black = otio.core.Color.BLACK self.assertEqual(black.r, 0.0) self.assertEqual(black.g, 0.0) self.assertEqual(black.b, 0.0) self.assertEqual(black.a, 1.0) self.assertEqual(black.to_hex(), "#000000ff") self.assertEqual(black.to_rgba_int_list(8), [0, 0, 0, 255]) self.assertEqual(black.to_agbr_integer(), 4278190080) self.assertEqual(black.to_rgba_float_list(), [0.0, 0.0, 0.0, 1.0]) def test_from_hex(self): all_reds = [ "f00", # 3 digits "f00f", # 4 digits "ff0000", # 6 digits "ff0000ff", # 8 digits "0xff0000", # prefix "#ff0000", # prefix ] for red_hex in all_reds + [s.upper() for s in all_reds]: red = otio.core.Color.from_hex(red_hex) self.assertEqual(red.r, 1.0) self.assertEqual(red.g, 0.0) self.assertEqual(red.b, 0.0) self.assertEqual(red.a, 1.0) def test_from_int_list(self): colors = ( [255, 255, 255], [0, 0, 0], [255, 0, 0, 0], ) for c in colors: actual = otio.core.Color.from_int_list(c, 8).to_rgba_int_list(8) if len(c) == 4: self.assertEqual(c, actual) elif len(c) == 3: self.assertEqual(c[0], actual[0]) self.assertEqual(c[1], actual[1]) self.assertEqual(c[2], actual[2]) self.assertEqual(255, actual[3]) def test_from_float_list(self): colors = ( [1.0, 1.0, 1.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0], ) for c in colors: actual = otio.core.Color.from_float_list(c).to_rgba_float_list() if len(c) == 4: self.assertEqual(c, actual) elif len(c) == 3: self.assertEqual(c[0], actual[0]) self.assertEqual(c[1], actual[1]) self.assertEqual(c[2], actual[2]) self.assertEqual(1.0, actual[3]) def test_from_agbr_int(self): self.assertEqual( otio.core.Color.from_agbr_int(4281740498).to_hex(), '#d2362cff' ) def test_repr(self): cl = otio.core.Color.ORANGE self.assertMultiLineEqual( repr(cl), 'otio.core.Color(' 'name={}, ' 'r={}, ' 'g={}, ' 'b={}, ' 'a={})'.format( repr(cl.name), repr(cl.r), repr(cl.g), repr(cl.b), repr(cl.a), ) ) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/CMakeLists.txt0000664000175000017500000000240615110656141016313 0ustar memeinclude_directories( ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/deps ${PROJECT_SOURCE_DIR}/src/deps/optional-lite/include ${PROJECT_SOURCE_DIR}/src/tests ${CMAKE_BINARY_DIR}/src ) list(APPEND tests_opentime test_opentime) foreach(test ${tests_opentime}) add_executable(${test} utils.h utils.cpp ${test}.cpp) target_link_libraries(${test} opentime) set_target_properties(${test} PROPERTIES FOLDER tests) add_test(NAME ${test} COMMAND ${test} # Set the pwd to the source directory so we can load the samples # like the python tests do WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endforeach() list(APPEND tests_opentimelineio test_clip test_serialization test_serializableCollection test_stack_algo test_timeline test_track test_editAlgorithm test_composition) foreach(test ${tests_opentimelineio}) add_executable(${test} utils.h utils.cpp ${test}.cpp) target_link_libraries(${test} opentimelineio) set_target_properties(${test} PROPERTIES FOLDER tests) add_test(NAME ${test} COMMAND ${test} # Set the pwd to the source directory so we can load the samples # like the python tests do WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endforeach() opentimelineio-0.18.1/tests/test_clip.cpp0000664000175000017500000002636315110656141016255 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include #include #include #include #include #include #include #include #include namespace otime = opentime::OPENTIME_VERSION; namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; int main(int argc, char** argv) { Tests tests; tests.add_test("test_cons", [] { std::string name = "test"; otime::RationalTime rt(5, 24); otime::TimeRange tr(rt, rt); otio::SerializableObject::Retainer mr( new otio::ExternalReference); mr->set_available_range( otime::TimeRange(rt, otime::RationalTime(10, 24))); mr->set_target_url("/var/tmp/test.mov"); otio::SerializableObject::Retainer cl(new otio::Clip); cl->set_name(name); cl->set_media_reference(mr); cl->set_source_range(tr); assertEqual(cl->name(), name); assertEqual(cl->source_range().value(), tr); std::string encoded = cl->to_json_string(); otio::SerializableObject::Retainer decoded( otio::SerializableObject::from_json_string(encoded)); assertTrue(cl->is_equivalent_to(*decoded)); }); tests.add_test("test_ranges", [] { otime::TimeRange tr( // 1 hour in at 24 fps otime::RationalTime(86400, 24), otime::RationalTime(200, 24)); otio::SerializableObject::Retainer cl( new otio::Clip("test_clip")); otio::SerializableObject::Retainer mr( new otio::ExternalReference); mr->set_target_url("/var/tmp/test.mov"); mr->set_available_range(tr); cl->set_media_reference(mr); assertEqual(cl->duration(), cl->trimmed_range().duration()); assertEqual(cl->duration(), tr.duration()); assertEqual(cl->trimmed_range(), tr); assertEqual(cl->available_range(), tr); cl->set_source_range(otime::TimeRange( // 1 hour + 100 frames otime::RationalTime(86500, 24), otime::RationalTime(50, 24))); assertNotEqual(cl->duration(), tr.duration()); assertNotEqual(cl->trimmed_range(), tr); assertEqual(cl->duration(), cl->source_range()->duration()); assertEqual(cl->trimmed_range(), cl->source_range().value()); }); tests.add_test("test_clip_v1_to_v2_null", [] { using namespace otio; otio::ErrorStatus status; SerializableObject::Retainer<> so = SerializableObject::from_json_string( R"( { "OTIO_SCHEMA": "Clip.1", "media_reference": null })", &status); assertFalse(is_error(status)); const Clip* clip = dynamic_cast(so.value); assertNotNull(clip); const MissingReference* media_ref = dynamic_cast(clip->media_reference()); assertNotNull(media_ref); }); tests.add_test("test_clip_v1_to_v2", [] { using namespace otio; otio::ErrorStatus status; SerializableObject::Retainer<> so = SerializableObject::from_json_string( R"( { "OTIO_SCHEMA": "Clip.1", "media_reference": { "OTIO_SCHEMA": "ExternalReference.1", "target_url": "unit_test_url", "available_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 8 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 10 } } } })", &status); assertFalse(is_error(status)); const Clip* clip = dynamic_cast(so.value); assertNotNull(clip); const ExternalReference* media_ref = dynamic_cast(clip->media_reference()); assertNotNull(media_ref); assertEqual( clip->active_media_reference_key().c_str(), Clip::default_media_key); assertEqual(media_ref->target_url().c_str(), "unit_test_url"); assertEqual(media_ref->available_range()->duration().value(), 8); assertEqual(media_ref->available_range()->duration().rate(), 24); assertEqual(media_ref->available_range()->start_time().value(), 10); assertEqual(media_ref->available_range()->start_time().rate(), 24); auto mediaReferences = clip->media_references(); auto found = mediaReferences.find(Clip::default_media_key); assertFalse(found == mediaReferences.end()); }); tests.add_test("test_clip_media_representation", [] { using namespace otio; static constexpr auto time_scalar = 1.5; SerializableObject::Retainer ltw(new LinearTimeWarp( LinearTimeWarp::Schema::name, LinearTimeWarp::Schema::name, time_scalar)); std::vector effects = { ltw }; static constexpr auto red = Marker::Color::red; SerializableObject::Retainer m(new Marker( LinearTimeWarp::Schema::name, TimeRange(), red)); std::vector markers = { m }; static constexpr auto high_quality = "high_quality"; static constexpr auto proxy_quality = "proxy_quality"; SerializableObject::Retainer media( new otio::ExternalReference()); SerializableObject::Retainer clip(new Clip( "unit_clip", media, std::nullopt, AnyDictionary(), effects, markers, high_quality)); assertEqual(clip->active_media_reference_key().c_str(), high_quality); assertEqual(clip->media_reference(), media.value); assertEqual(clip->active_media_reference_key().c_str(), high_quality); SerializableObject::Retainer ref1( new otio::ExternalReference()); SerializableObject::Retainer ref2( new otio::ExternalReference()); SerializableObject::Retainer ref3( new otio::ExternalReference()); clip->set_media_references( { { Clip::default_media_key, ref1 }, { high_quality, ref2 }, { proxy_quality, ref3 } }, high_quality); assertEqual(clip->media_reference(), ref2.value); clip->set_active_media_reference_key(proxy_quality); assertEqual(clip->media_reference(), ref3.value); clip->set_active_media_reference_key(Clip::default_media_key); assertEqual(clip->media_reference(), ref1.value); // setting the active reference to a key that does not exist // should return an error otio::ErrorStatus error; clip->set_active_media_reference_key("cloud", &error); assertTrue(otio::is_error(error)); assertEqual( error.outcome, otio::ErrorStatus::MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY); assertEqual(clip->media_reference(), ref1.value); // setting the references that doesn't have the active key should // also generate an error SerializableObject::Retainer ref4( new otio::ExternalReference()); otio::ErrorStatus error2; clip->set_media_references( { { "cloud", ref4 } }, high_quality, &error2); assertTrue(otio::is_error(error2)); assertEqual( error2.outcome, otio::ErrorStatus::MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY); assertEqual(clip->media_reference(), ref1.value); // setting an empty should also generate an error otio::ErrorStatus error3; clip->set_media_references({ { "", ref4 } }, "", &error3); assertTrue(otio::is_error(error3)); assertEqual( error3.outcome, otio::ErrorStatus::MEDIA_REFERENCES_CONTAIN_EMPTY_KEY); // setting the references and the active key at the same time // should work clip->set_media_references({ { "cloud", ref4 } }, "cloud"); assertEqual(clip->media_reference(), ref4.value); // basic test for an effect assertEqual(clip->effects().size(), effects.size()); auto effect = dynamic_cast( clip->effects().front().value); assertEqual(effect->time_scalar(), time_scalar); // basic test for a marker assertEqual(clip->markers().size(), markers.size()); auto marker = dynamic_cast( clip->markers().front().value); assertEqual(marker->color().c_str(), red); }); // test to ensure null error_status pointers are correctly handled tests.add_test("test_error_ptr_null", [] { using namespace otio; // tests for no image bounds on media reference on clip SerializableObject::Retainer clip(new Clip); // check that there is an error, and that it's the correct error otio::ErrorStatus mr_bounds_error; clip->available_image_bounds(&mr_bounds_error); assertTrue(otio::is_error(mr_bounds_error)); assertEqual( mr_bounds_error.outcome, otio::ErrorStatus::CANNOT_COMPUTE_BOUNDS); // check that if null ptr, nothing happens otio::ErrorStatus* null_test = nullptr; assertEqual( clip->available_image_bounds(null_test), std::optional() ); }); // test to ensure null error_status pointers are correctly handled // when there's no media reference tests.add_test("test_error_ptr_null_no_media", [] { using namespace otio; SerializableObject::Retainer clip(new Clip); // set media reference to empty Clip::MediaReferences empty_mrs; empty_mrs["empty"] = nullptr; clip->set_media_references(empty_mrs, "empty"); otio::ErrorStatus bounds_error_no_mr; clip->available_image_bounds(&bounds_error_no_mr); assertTrue(otio::is_error(bounds_error_no_mr)); assertEqual( bounds_error_no_mr.outcome, otio::ErrorStatus::CANNOT_COMPUTE_BOUNDS); // std::cout<< "bounds error details: " << bounds_error_no_mr.details << std::endl; otio::ErrorStatus* null_test_no_mr = nullptr; assertEqual( clip->available_image_bounds(null_test_no_mr), std::optional() ); }); tests.run(argc, argv); return 0; } opentimelineio-0.18.1/tests/test_serializable_collection.py0000664000175000017500000000770515110656141022054 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import unittest import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class SerializableColTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def setUp(self): self.maxDiff = None self.children = [ otio.schema.Clip(name="testClip"), otio.schema.MissingReference() ] self.md = {'foo': 'bar'} self.sc = otio.schema.SerializableCollection( name="test", children=self.children, metadata=self.md ) def test_ctor(self): self.assertEqual(self.sc.name, "test") self.assertEqual(self.sc[:], self.children) self.assertEqual(self.sc.metadata, self.md) def test_iterable(self): self.assertEqual(self.sc[0], self.children[0]) self.assertEqual([i for i in self.sc], self.children) self.assertEqual(len(self.sc), 2) # test recursive iteration sc = otio.schema.SerializableCollection( name="parent", children=[self.sc] ) self.assertEqual(len(list(sc.find_clips())), 1) # test deleting an item tmp = self.sc[0] del self.sc[0] self.assertEqual(len(self.sc), 1) self.sc[0] = tmp self.assertEqual(self.sc[0], tmp) with self.assertRaises(IndexError): self.sc[100] def test_serialize(self): encoded = otio.adapters.otio_json.write_to_string(self.sc) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(self.sc, decoded) def test_str(self): self.assertMultiLineEqual( str(self.sc), "SerializableCollection(" + str(self.sc.name) + ", " + str(list(self.sc)) + ", " + str(self.sc.metadata) + ")" ) def test_repr(self): self.assertMultiLineEqual( repr(self.sc), "otio.schema.SerializableCollection(" + "name=" + repr(self.sc.name) + ", " + "children=" + repr(list(self.sc)) + ", " + "metadata=" + repr(self.sc.metadata) + ")" ) def test_find_children(self): cl = otio.schema.Clip() tr = otio.schema.Track() tr.append(cl) tl = otio.schema.Timeline() tl.tracks.append(tr) sc = otio.schema.SerializableCollection() sc.append(tl) result = sc.find_children(otio.schema.Clip) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl) def test_find_children_search_range(self): range = otio.opentime.TimeRange( otio.opentime.RationalTime(0.0, 24.0), otio.opentime.RationalTime(24.0, 24.0)) cl0 = otio.schema.Clip() cl0.source_range = range cl1 = otio.schema.Clip() cl1.source_range = range cl2 = otio.schema.Clip() cl2.source_range = range tr = otio.schema.Track() tr.append(cl0) tr.append(cl1) tr.append(cl2) tl = otio.schema.Timeline() tl.tracks.append(tr) sc = otio.schema.SerializableCollection() sc.append(tl) result = sc.find_children(otio.schema.Clip, range) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl0) def test_find_children_shallow_search(self): cl = otio.schema.Clip() tr = otio.schema.Track() tr.append(cl) tl = otio.schema.Timeline() tl.tracks.append(tr) sc = otio.schema.SerializableCollection() sc.append(tl) result = sc.find_children(otio.schema.Clip, shallow_search=True) self.assertEqual(len(result), 0) result = sc.find_children(otio.schema.Clip, shallow_search=False) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_timeline.py0000775000175000017500000004713115110656141017001 0ustar meme#!/usr/bin/env python # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import math import os import sys import unittest import tempfile import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") BIG_INT_TEST = os.path.join(SAMPLE_DATA_DIR, "big_int.otio") class TimelineTests(unittest.TestCase, otio_test_utils.OTIOAssertions): def test_init(self): rt = otio.opentime.RationalTime(12, 24) tl = otio.schema.Timeline("test_timeline", global_start_time=rt) self.assertEqual(tl.name, "test_timeline") self.assertEqual(tl.global_start_time, rt) def test_metadata(self): rt = otio.opentime.RationalTime(12, 24) tl = otio.schema.Timeline("test_timeline", global_start_time=rt) tl.metadata['foo'] = "bar" self.assertEqual(tl.metadata['foo'], 'bar') encoded = otio.adapters.otio_json.write_to_string(tl) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(tl, decoded) self.assertEqual(tl.metadata, decoded.metadata) def test_big_integers(self): result = otio.adapters.read_from_file(BIG_INT_TEST) md = result.tracks[0][0].metadata["int_test"] # test the deserialized values from the file # positive integers self.assertEqual(md["maxint32"], 2147483647) self.assertEqual(md["smallest_int64"], 2147483648) self.assertEqual(md["verybig"], 3450100000) # negative self.assertEqual(md["minint32"], -2147483647) self.assertEqual(md["neg_smallest_int64"], -2147483648) self.assertEqual(md["negverybig"], -3450100000) # from memory supported_integers = [ # name value to enter ('minint32', -int(2**31 - 1)), ('maxint32', int(2**31 - 1)), ('maxuint32', int(2**32 - 1)), ('minint64', -int(2**63 - 1)), ('maxint64', int(2**63 - 1)), ] for (name, value) in supported_integers: md[name] = value self.assertEqual( md[name], value, "{} didn't match expected value: got {} expected {}".format( name, md[name], value ) ) self.assertEqual( type(md[name]), int, "{} didn't match expected type: got {} expected {}".format( name, type(md[name]), int ) ) # test that roundtripping through json functions serialized = otio.adapters.write_to_string( result, "otio_json" ) deserialized = otio.adapters.read_from_string( serialized, "otio_json" ) value_deserialized = ( deserialized.tracks[0][0].metadata["int_test"][name] ) self.assertEqual( value, value_deserialized, "{} did not round trip correctly. expected: {} got: {}".format( name, value, value_deserialized ) ) self.assertEqual( type(value), type(value_deserialized), "the type of {} did not round trip correctly. expected: " "{} got: {}".format( name, type(value), type(value_deserialized) ) ) supported_doubles = [ # name value to enter ('minint32', -float(2**31 - 1)), ('maxint32', float(2**31 - 1)), ('maxuint32', float(2**32 - 1)), ('minint64', -float(2**63 - 1)), ('maxint64', float(2**63 - 1)), ('maxdouble', sys.float_info.max), ('infinity', float('inf')), ('infinity_because_too_big', float(2 * sys.float_info.max)), ('nan', float('nan')), ('neg_infinity', float('-inf')), ] for (name, value) in supported_doubles: md[name] = value # float('nan') != float('nan'), so need to test isnan(value) instead if not math.isnan(value): self.assertEqual( md[name], value, "{} didn't match expected value: got '{}' ('{}') expected " "'{}' ('{}')".format( name, md[name], type(md[name]), value, type(value) ) ) else: self.assertTrue( math.isnan(md[name]), f"Expected {name} to be a nan, got {md[name]}" ) self.assertEqual( type(md[name]), float, "{} didn't match expected type: got {} expected {}".format( name, type(md[name]), float ) ) # test that roundtripping through json functions try: serialized = otio.adapters.write_to_string( result, "otio_json" ) except Exception as e: self.fail( "A problem occurred when attempting to serialize {} " "with value: {}. Error message was: {}".format( name, value, e ) ) try: deserialized = otio.adapters.read_from_string( serialized, "otio_json" ) except Exception as e: self.fail( "A problem occurred when attempting to serialize {} " "with value: {}. Error message was: {}".format( name, value, e ) ) value_deserialized = ( deserialized.tracks[0][0].metadata["int_test"][name] ) if not math.isnan(value): self.assertEqual( value, value_deserialized, "{} did not round trip correctly. expected: {}, of type {} " "got: {}, of type {}".format( name, value, type(value), value_deserialized, type(value_deserialized), ) ) else: self.assertTrue( math.isnan(value_deserialized), "Expected {} to be a nan, got {}".format( name, value_deserialized ) ) unsupported_values = [ # numbers -- supported python type but don't fit into the C++ types # (ie int but doesn't fit into int64_t) ('minuint64_not_int64', int(2**63), ValueError), ('maxuint64', int(2**64 - 1), ValueError), ('minint128', int(2**64), ValueError), # other kinds of python things ('object', object(), TypeError), ('set', set(), TypeError), ] for (name, value, exc) in unsupported_values: with self.assertRaises( exc, msg=f"Expected {name} to raise an exception." ): md[name] = value def test_unicode(self): result = otio.adapters.read_from_file(BIG_INT_TEST) md = result.tracks[0][0].metadata['unicode'] utf8_test_str = "Viel glück und hab spaß!" self.assertEqual(md['utf8'], utf8_test_str) tl = otio.schema.Timeline() tl.metadata['utf8'] = utf8_test_str self.assertEqual(tl.metadata['utf8'], utf8_test_str) encoded = otio.adapters.otio_json.write_to_string(tl) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(tl, decoded) self.assertEqual(tl.metadata, decoded.metadata) def test_unicode_file_name(self): with tempfile.TemporaryDirectory() as temp_dir: tl = otio.schema.Timeline("大平原") filename = os.path.join(temp_dir, "大平原.otio") otio.adapters.write_to_file(tl, filename) result = otio.adapters.read_from_file(filename) self.assertEqual(result.name, "大平原") def test_range(self): track = otio.schema.Track(name="test_track") tl = otio.schema.Timeline("test_timeline", tracks=[track]) rt = otio.opentime.RationalTime(5, 24) mr = otio.schema.ExternalReference( available_range=otio.opentime.range_from_start_end_time( otio.opentime.RationalTime(5, 24), otio.opentime.RationalTime(15, 24) ), target_url="/var/tmp/test.mov" ) cl = otio.schema.Clip( name="test clip1", media_reference=mr, source_range=otio.opentime.TimeRange(duration=rt), ) cl2 = otio.schema.Clip( name="test clip2", media_reference=mr, source_range=otio.opentime.TimeRange(duration=rt), ) cl3 = otio.schema.Clip( name="test clip3", media_reference=mr, source_range=otio.opentime.TimeRange(duration=rt), ) tl.tracks[0].append(cl) tl.tracks[0].extend([cl2, cl3]) self.assertEqual(tl.duration(), rt + rt + rt) self.assertEqual( tl.range_of_child(cl), tl.tracks[0].range_of_child_at_index(0) ) def test_available_image_bounds(self): track = otio.schema.Track(name="test_track") tl = otio.schema.Timeline("test_timeline", tracks=[track]) # three clips, each successive clip partially overlaps the previous cl = otio.schema.Clip( name="test clip1", media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(1.0, 1.0) ), target_url="/var/tmp/test.mov" ) ) cl2 = otio.schema.Clip( name="test clip2", media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(1.0, 1.0), otio.schema.V2d(2.0, 2.0) ), target_url="/var/tmp/test.mov" ) ) cl3 = otio.schema.Clip( name="test clip3", media_reference=otio.schema.ExternalReference( available_image_bounds=otio.schema.Box2d( otio.schema.V2d(2.0, 2.0), otio.schema.V2d(3.0, 3.0) ), target_url="/var/tmp/test.mov" ) ) gap = otio.schema.Gap(name="gap") tl.tracks[0].append(cl) tl.tracks[0].extend([cl2, cl3, gap]) union = otio.schema.Box2d( otio.schema.V2d(0.0, 0.0), otio.schema.V2d(3.0, 3.0) ) # union should be overlapping area, gap should be ignored self.assertEqual(tl.tracks[0].available_image_bounds, union) def test_iterators(self): self.maxDiff = None track = otio.schema.Track(name="test_track") tl = otio.schema.Timeline("test_timeline", tracks=[track]) rt = otio.opentime.RationalTime(5, 24) mr = otio.schema.ExternalReference( available_range=otio.opentime.range_from_start_end_time( otio.opentime.RationalTime(5, 24), otio.opentime.RationalTime(15, 24) ), target_url="/var/tmp/test.mov" ) cl = otio.schema.Clip( name="test clip1", media_reference=mr, source_range=otio.opentime.TimeRange( mr.available_range.start_time, rt ), ) cl2 = otio.schema.Clip( name="test clip2", media_reference=mr, source_range=otio.opentime.TimeRange( mr.available_range.start_time, rt ), ) cl3 = otio.schema.Clip( name="test clip3", media_reference=mr, source_range=otio.opentime.TimeRange( mr.available_range.start_time, rt ), ) tl.tracks[0].append(cl) tl.tracks[0].extend([cl2, cl3]) self.assertEqual([cl, cl2, cl3], list(tl.find_clips())) rt_start = otio.opentime.RationalTime(0, 24) rt_end = otio.opentime.RationalTime(1, 24) search_range = otio.opentime.TimeRange(rt_start, rt_end) self.assertEqual([cl], list(tl.find_clips(search_range))) # check to see if full range works search_range = tl.tracks.trimmed_range() self.assertEqual([cl, cl2, cl3], list(tl.find_clips(search_range))) # just one clip search_range = cl2.range_in_parent() self.assertEqual([cl2], list(tl.find_clips(search_range))) # the last two clips search_range = otio.opentime.TimeRange( start_time=cl2.range_in_parent().start_time, duration=cl2.trimmed_range().duration + rt_end ) self.assertEqual([cl2, cl3], list(tl.find_clips(search_range))) # no clips search_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( value=-10, rate=rt_start.rate ), duration=rt_end ) self.assertEqual([], list(tl.find_clips(search_range))) def test_str(self): self.maxDiff = None clip = otio.schema.Clip( name="test_clip", media_reference=otio.schema.MissingReference() ) track = otio.schema.Track(name="test_track", children=[clip]) tl = otio.schema.Timeline(name="test_timeline", tracks=[track]) self.assertMultiLineEqual( str(tl), 'Timeline(' + '"' + str(tl.name) + '", ' + str(tl.tracks) + ')' ) self.assertMultiLineEqual( repr(tl), 'otio.schema.Timeline(' + "name='" + tl.name + "', " + "tracks=" + repr(tl.tracks) + ')' ) def test_serialize_timeline(self): clip = otio.schema.Clip( name="test_clip", media_reference=otio.schema.MissingReference() ) tl = otio.schema.timeline_from_clips([clip]) encoded = otio.adapters.otio_json.write_to_string(tl) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(tl, decoded) string2 = otio.adapters.otio_json.write_to_string(decoded) self.assertEqual(encoded, string2) def test_serialization_of_subclasses(self): clip1 = otio.schema.Clip() clip1.name = "Test Clip" clip1.media_reference = otio.schema.ExternalReference( "/tmp/foo.mov" ) tl1 = otio.schema.timeline_from_clips([clip1]) tl1.name = "Testing Serialization" self.assertIsNotNone(tl1) otio_module = otio.adapters.from_name("otio_json") serialized = otio_module.write_to_string(tl1) self.assertIsNotNone(serialized) tl2 = otio_module.read_from_string(serialized) self.assertIsNotNone(tl2) self.assertEqual(type(tl1), type(tl2)) self.assertEqual(tl1.name, tl2.name) self.assertEqual(len(tl1.tracks), 1) self.assertEqual(len(tl2.tracks), 1) track1 = tl1.tracks[0] track2 = tl2.tracks[0] self.assertEqual(type(track1), type(track2)) self.assertEqual(len(track1), 1) self.assertEqual(len(track2), 1) clip2 = tl2.tracks[0][0] self.assertEqual(clip1.name, clip2.name) self.assertEqual(type(clip1), type(clip2)) self.assertEqual( type(clip1.media_reference), type(clip2.media_reference) ) self.assertEqual( clip1.media_reference.target_url, clip2.media_reference.target_url ) def test_tracks(self): tl = otio.schema.Timeline(tracks=[ otio.schema.Track( name="V1", kind=otio.schema.TrackKind.Video ), otio.schema.Track( name="V2", kind=otio.schema.TrackKind.Video ), otio.schema.Track( name="A1", kind=otio.schema.TrackKind.Audio ), otio.schema.Track( name="A2", kind=otio.schema.TrackKind.Audio ), ]) self.assertListEqual( ["V1", "V2"], [t.name for t in tl.video_tracks()] ) self.assertListEqual( ["A1", "A2"], [t.name for t in tl.audio_tracks()] ) def test_tracks_set_null_tracks(self): tl = otio.schema.Timeline(tracks=[ otio.schema.Track( name="V1", kind=otio.schema.TrackKind.Video ), otio.schema.Track( name="V2", kind=otio.schema.TrackKind.Video )]) self.assertEqual(len(tl.tracks), 2) self.assertTrue(isinstance(tl.tracks, otio.schema.Stack)) tl.tracks = None self.assertEqual(len(tl.audio_tracks()), 0) self.assertEqual(len(tl.video_tracks()), 0) self.assertTrue(isinstance(tl.tracks, otio.schema.Stack)) def test_find_children(self): cl = otio.schema.Clip() tr = otio.schema.Track() tr.append(cl) tl = otio.schema.Timeline() tl.tracks.append(tr) result = tl.find_children(otio.schema.Clip) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl) def test_find_children_search_range(self): range = otio.opentime.TimeRange( otio.opentime.RationalTime(0.0, 24.0), otio.opentime.RationalTime(24.0, 24.0)) cl0 = otio.schema.Clip() cl0.source_range = range cl1 = otio.schema.Clip() cl1.source_range = range cl2 = otio.schema.Clip() cl2.source_range = range tr = otio.schema.Track() tr.append(cl0) tr.append(cl1) tr.append(cl2) tl = otio.schema.Timeline() tl.tracks.append(tr) result = tl.find_children(otio.schema.Clip, range) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl0) def test_find_children_shallow_search(self): cl = otio.schema.Clip() tr = otio.schema.Track() tr.append(cl) tl = otio.schema.Timeline() tl.tracks.append(tr) result = tl.find_children(otio.schema.Clip, shallow_search=True) self.assertEqual(len(result), 0) result = tl.find_children(otio.schema.Clip, shallow_search=False) self.assertEqual(len(result), 1) self.assertEqual(result[0], cl) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_filter_algorithms.py0000664000175000017500000002541715110656141020711 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test harness for the filter algorithms.""" import unittest import copy import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils class FilterTest(unittest.TestCase, otio_test_utils.OTIOAssertions): maxDiff = None def test_copy_track(self): md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) test = otio.algorithms.filtered_composition(tr, lambda _: _) self.assertJsonEqual(tr, test) def test_copy_stack(self): """Test a no op filter that copies the timeline.""" md = {'test': 'bar'} tr = otio.schema.Stack(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) result = otio.algorithms.filtered_composition(tr, lambda _: _) self.assertJsonEqual(tr, result) self.assertIsNot(tr[0], result) def test_prune_clips_starting_with_a(self): """test a filter that removes things whose name starts with 'a'""" md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) tr.append(otio.schema.Clip(name='a_cl3', metadata=md)) tr_2 = otio.schema.Track(name='a', metadata=md) tr_2.append(otio.schema.Clip(name='cl1', metadata=md)) tr_2.append(otio.schema.Clip(name='a_cl2', metadata=md)) tr.append(tr_2) visited = [] def nothing_that_starts_with_a(thing): visited.append(thing.name) if thing.name.startswith('a'): return None else: return thing result = otio.algorithms.filtered_composition( tr, nothing_that_starts_with_a ) # make sure that the track being pruned means the child was never # visited self.assertNotIn('a_cl2', visited) # match the desired behavior of the function del tr[2] del tr[1] self.assertJsonEqual(tr, result) def test_prune_clips(self): """test a filter that removes clips""" md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) def no_clips(thing): if not isinstance(thing, otio.schema.Clip): return thing return None result = otio.algorithms.filtered_composition(tr, no_clips) self.assertEqual(0, len(result)) self.assertEqual(tr.metadata, result.metadata) # emptying the track should have the same effect del tr[:] self.assertIsOTIOEquivalentTo(tr, result) def test_prune_by_type_args(self): """Test pruning using the types_to_prune list""" md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) result = otio.algorithms.filtered_composition( tr, lambda _: _, types_to_prune=(otio.schema.Clip,) ) self.assertEqual(0, len(result)) self.assertEqual(tr.metadata, result.metadata) # emptying the track should have the same effect del tr[:] self.assertIsOTIOEquivalentTo(tr, result) def test_copy(self): md = {'test': 'bar'} tl = otio.schema.Timeline(name='foo', metadata=md) tl.tracks.append(otio.schema.Track(name='track1', metadata=md)) tl.tracks[0].append(otio.schema.Clip(name='cl1', metadata=md)) test = otio.algorithms.filtered_composition(tl, lambda _: _) # make sure the original timeline didn't get nuked self.assertEqual(len(tl.tracks), 1) self.assertJsonEqual(tl, test) def test_insert_tuple(self): """test a reduce that takes each clip in a sequence and triples it""" md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) def triple_clips(thing): if not isinstance(thing, otio.schema.Clip): return thing return (thing, copy.deepcopy(thing), copy.deepcopy(thing)) result = otio.algorithms.filtered_composition(tr, triple_clips) self.assertEqual(3, len(result)) self.assertEqual(tr.metadata, result.metadata) # emptying the track should have the same effect tr.extend([copy.deepcopy(tr[0]), copy.deepcopy(tr[0])]) self.assertJsonEqual(tr, result) class ReduceTest(unittest.TestCase, otio_test_utils.OTIOAssertions): maxDiff = None def test_copy_track(self): md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) self.assertJsonEqual( tr, otio.algorithms.filtered_with_sequence_context( tr, # no op - ignore all arguments and return original thing lambda _, thing, __: thing ) ) def test_copy_stack(self): """Test a no op reduce that copies the timeline.""" md = {'test': 'bar'} tr = otio.schema.Stack(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) result = otio.algorithms.filtered_with_sequence_context( tr, # no op - ignore all arguments and return original thing lambda _, thing, __: thing ) self.assertJsonEqual(tr, result) self.assertIsNot(tr[0], result) def test_prune_clips(self): """test a reduce that removes clips""" md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) def no_clips(_, thing, __): if not isinstance(thing, otio.schema.Clip): return thing return None result = otio.algorithms.filtered_with_sequence_context(tr, no_clips) self.assertEqual(0, len(result)) self.assertEqual(tr.metadata, result.metadata) # emptying the track should have the same effect del tr[:] self.assertIsOTIOEquivalentTo(tr, result) def test_prune_clips_using_types_to_prune(self): """Test pruning otio.schema.clip using the types_to_prune argument""" md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) result = otio.algorithms.filtered_with_sequence_context( tr, # no op - ignore all arguments and return original thing lambda _, thing, __: thing, # instead use types_to_prune types_to_prune=(otio.schema.Clip,) ) self.assertEqual(0, len(result)) self.assertEqual(tr.metadata, result.metadata) # emptying the track should have the same effect del tr[:] self.assertIsOTIOEquivalentTo(tr, result) def test_insert_tuple(self): """test a reduce that takes each clip in a sequence and triples it""" md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) tr.append(otio.schema.Clip(name='cl1', metadata=md)) def triple_clips(_, thing, __): if not isinstance(thing, otio.schema.Clip): return thing return (thing, copy.deepcopy(thing), copy.deepcopy(thing)) result = otio.algorithms.filtered_with_sequence_context( tr, triple_clips ) self.assertEqual(3, len(result)) self.assertEqual(tr.metadata, result.metadata) # emptying the track should have the same effect tr.extend((copy.deepcopy(tr[0]), copy.deepcopy(tr[0]))) self.assertJsonEqual(tr, result) def test_prune_clips_after_transitions(self): """test a reduce that removes clips that follow transitions""" md = {'test': 'bar'} tr = otio.schema.Track(name='foo', metadata=md) for i in range(5): ind = str(i) if i in (2, 3): tr.append(otio.schema.Transition(name='should_be_pruned' + ind)) tr.append(otio.schema.Clip(name='cl' + ind, metadata=md)) def no_clips_after_transitions(prev, thing, __): if ( isinstance(prev, otio.schema.Transition) or isinstance(thing, otio.schema.Transition) ): return None return thing result = otio.algorithms.filtered_with_sequence_context( tr, no_clips_after_transitions ) # emptying the track of transitions and the clips they follow and # should have the same effect del tr[2:6] self.assertJsonEqual(tr, result) self.assertEqual(3, len(result)) self.assertEqual(tr.metadata, result.metadata) # ...but that things have been properly deep copied self.assertIsNot(tr.metadata, result.metadata) def test_copy(self): """Test that a simple reduce results in a copy""" md = {'test': 'bar'} tl = otio.schema.Timeline(name='foo', metadata=md) tl.tracks.append(otio.schema.Track(name='track1', metadata=md)) tl.tracks[0].append(otio.schema.Clip(name='cl1', metadata=md)) test = otio.algorithms.filtered_with_sequence_context( tl, # no op - ignore all arguments and return original thing lambda _, thing, __: thing ) # make sure the original timeline didn't get nuked self.assertEqual(len(tl.tracks), 1) self.assertJsonEqual(tl, test) def test_prune_correct_duplicate(self): """test a reduce that removes the correct duplicate clip""" md = {'test': 'bar'} tr = otio.schema.Track() tr.append(otio.schema.Clip(metadata=md)) tr.append(otio.schema.Gap()) tr.append(otio.schema.Clip(metadata=md)) tr.append(otio.schema.Gap()) tr.append(otio.schema.Clip(metadata=md)) clips = [] def no_clip_2(_, thing, __): if isinstance(thing, otio.schema.Clip): clips.append(thing) if len(clips) == 2: return None return thing result = otio.algorithms.filtered_with_sequence_context(tr, no_clip_2) self.assertEqual(4, len(result)) self.assertTrue(isinstance(result[0], otio.schema.Clip)) self.assertTrue(isinstance(result[1], otio.schema.Gap)) self.assertTrue(isinstance(result[2], otio.schema.Gap)) self.assertTrue(isinstance(result[3], otio.schema.Clip)) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_examples.py0000664000175000017500000000274115110656141017004 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Unit tests for the 'examples'""" import unittest import sys import os import subprocess import tempfile import opentimelineio as otio class BuildSimpleTimelineExampleTest(unittest.TestCase): """use the build_simple_timeline.py example to generate timelines""" def test_duration(self): with tempfile.TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, "test_basic.otio") examples_path = os.path.join( os.path.dirname(os.path.dirname(__file__)), "examples", "build_simple_timeline.py", ) subprocess.check_call( [sys.executable, examples_path, temp_file], stdout=subprocess.PIPE ) known = otio.adapters.read_from_file(temp_file) # TODO: add checks against a couple of the adapters. # This used to include .edl and .xml for suffix in [".otio"]: this_test_file = temp_file.replace(".otio", suffix) subprocess.check_call( [sys.executable, examples_path, this_test_file], stdout=subprocess.PIPE ) test_result = otio.adapters.read_from_file(this_test_file) self.assertEqual(known.duration(), test_result.duration()) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_track_algo.py0000664000175000017500000004205515110656141017276 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test file for the track algorithms library.""" import unittest import copy import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils # for debugging # def print_expanded_tree(seq): # for thing in seq: # if isinstance(thing, tuple): # for i in thing: # print " ", i # else: # print thing class TransitionExpansionTests(unittest.TestCase): """ test harness for transition expansion function """ def test_expand_surrounded_by_clips(self): name = "test" rt = otio.opentime.RationalTime(5, 24) rt_2 = otio.opentime.RationalTime(1, 24) tr = otio.opentime.TimeRange(rt, rt + rt) avail_tr = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(50, 24) ) mr = otio.schema.ExternalReference( available_range=avail_tr, target_url="/var/tmp/test.mov" ) cl = otio.schema.Clip( name=name + "_pre", media_reference=mr, source_range=tr, ) seq = otio.schema.Track() seq.append(cl) in_offset = rt out_offset = rt_2 trx = otio.schema.Transition( name="AtoB", transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, in_offset=in_offset, out_offset=out_offset, metadata={ "foo": "bar" } ) seq.append(trx) cl_2 = copy.deepcopy(cl) cl_2.name = name + "_post" seq.append(copy.deepcopy(cl)) pre_duration = copy.deepcopy(seq[0].source_range.duration) # print # print "BEFORE:" # print_expanded_tree(seq) seq2 = otio.algorithms.track_with_expanded_transitions(seq) # print # print "AFTER:" # print_expanded_tree(seq2) self.assertNotEqual(pre_duration, seq2[0].source_range.duration) # check ranges on results # first clip is trimmed self.assertEqual( seq2[0].source_range.duration, cl_2.source_range.duration - in_offset ) self.assertEqual( seq2[1][0].source_range.start_time, cl.source_range.end_time_exclusive() - in_offset ) self.assertEqual( seq2[1][0].source_range.duration, in_offset + out_offset ) self.assertEqual( seq2[1][2].source_range.start_time, cl_2.source_range.start_time - in_offset ) self.assertEqual( seq2[1][2].source_range.duration, in_offset + out_offset ) # final clip is trimmed self.assertEqual( seq2[2].source_range.duration, cl_2.source_range.duration - out_offset ) # make sure that both transition clips are the same length self.assertEqual( seq2[1][0].source_range.duration, seq2[1][2].source_range.duration ) # big hammer stuff self.assertNotEqual(seq, seq2) self.assertNotEqual(seq[0], seq2[0]) self.assertNotEqual(seq[-1], seq2[-1]) self.assertNotEqual(seq[-1].source_range, seq2[-1].source_range) # leaving this here as a @TODO def DISABLED_test_expand_track(self): name = "test" rt = otio.opentime.RationalTime(5, 24) rt_2 = otio.opentime.RationalTime(1, 24) tr = otio.opentime.TimeRange(rt, rt + rt) avail_tr = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(50, 24) ) mr = otio.schema.ExternalReference( available_range=avail_tr, target_url="/var/tmp/test.mov" ) cl = otio.schema.Clip( name=name + "_pre", media_reference=mr, source_range=tr, ) seq = otio.schema.Track() seq.name = name seq.append(cl) in_offset = rt out_offset = rt_2 trx = otio.schema.Transition( name="AtoB", transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, in_offset=in_offset, out_offset=out_offset, metadata={ "foo": "bar" } ) seq.append(trx) seq_2 = copy.deepcopy(seq) seq_2.name = name + "_post" seq.append(seq_2) pre_duration = copy.deepcopy(seq[0].source_range.duration) # print # print "BEFORE:" # print_expanded_tree(seq) expanded_seq = otio.algorithms.track_with_expanded_transitions(seq) # print # print "AFTER:" # print_expanded_tree(expanded_seq) self.assertNotEqual( pre_duration, expanded_seq[0].source_range.duration ) # check ranges on results # first clip is trimmed self.assertEqual( expanded_seq[0].source_range.duration, seq_2.source_range.duration - in_offset ) self.assertEqual( expanded_seq[1][0].source_range.start_time, cl.source_range.end_time_exclusive() - in_offset ) self.assertEqual( expanded_seq[1][0].source_range.duration, in_offset + out_offset ) self.assertEqual( expanded_seq[1][2].source_range.start_time, seq_2.source_range.start_time - in_offset ) self.assertEqual( expanded_seq[1][2].source_range.duration, in_offset + out_offset ) # final clip is trimmed self.assertEqual( expanded_seq[2].source_range.duration, seq_2.source_range.duration - out_offset ) # make sure that both transition clips are the same length self.assertEqual( expanded_seq[1][0].source_range.duration, expanded_seq[1][2].source_range.duration ) # big hammer stuff self.assertNotEqual(seq, expanded_seq) self.assertNotEqual(seq[0], expanded_seq[0]) self.assertNotEqual(seq[-1], expanded_seq[-1]) self.assertNotEqual( seq[-1].source_range, expanded_seq[-1].source_range ) class TrackTrimmingTests(unittest.TestCase, otio_test_utils.OTIOAssertions): """ test harness for track trimming function """ def make_sample_track(self): return otio.adapters.read_from_string(""" { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "A", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "B", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "C", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } """, "otio_json") def test_trim_to_existing_range(self): original_track = self.make_sample_track() self.assertEqual( original_track.trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(150, 24) ) ) # trim to the exact range it already has trimmed = otio.algorithms.track_trimmed_to_range( original_track, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(150, 24) ) ) # it shouldn't have changed at all self.assertIsOTIOEquivalentTo(original_track, trimmed) def test_trim_to_longer_range(self): original_track = self.make_sample_track() # trim to a larger range trimmed = otio.algorithms.track_trimmed_to_range( original_track, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(-10, 24), duration=otio.opentime.RationalTime(160, 24) ) ) # it shouldn't have changed at all self.assertJsonEqual(original_track, trimmed) def test_trim_front(self): original_track = self.make_sample_track() # trim off the front (clip A and part of B) trimmed = otio.algorithms.track_trimmed_to_range( original_track, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(60, 24), duration=otio.opentime.RationalTime(90, 24) ) ) self.assertNotEqual(original_track, trimmed) self.assertEqual(len(trimmed), 2) self.assertEqual( trimmed.trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(90, 24) ) ) # did clip B get trimmed? self.assertEqual(trimmed[0].name, "B") self.assertEqual( trimmed[0].trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(10, 24), duration=otio.opentime.RationalTime(40, 24) ) ) # clip C should have been left alone self.assertIsOTIOEquivalentTo(trimmed[1], original_track[2]) def test_trim_end(self): original_track = self.make_sample_track() # trim off the end (clip C and part of B) trimmed = otio.algorithms.track_trimmed_to_range( original_track, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(90, 24) ) ) self.assertNotEqual(original_track, trimmed) self.assertEqual(len(trimmed), 2) self.assertEqual( trimmed.trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(90, 24) ) ) # clip A should have been left alone self.assertIsOTIOEquivalentTo(trimmed[0], original_track[0]) # did clip B get trimmed? self.assertEqual(trimmed[1].name, "B") self.assertEqual( trimmed[1].trimmed_range(), otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(40, 24) ) ) def test_trim_with_transitions(self): original_track = self.make_sample_track() self.assertEqual( otio.opentime.RationalTime(150, 24), original_track.duration() ) self.assertEqual(len(original_track), 3) # add a transition tr = otio.schema.Transition( in_offset=otio.opentime.RationalTime(12, 24), out_offset=otio.opentime.RationalTime(20, 24) ) original_track.insert(1, tr) self.assertEqual(len(original_track), 4) self.assertEqual( otio.opentime.RationalTime(150, 24), original_track.duration() ) # if you try to sever a Transition in the middle it should fail with self.assertRaises(otio.exceptions.CannotTrimTransitionsError): trimmed = otio.algorithms.track_trimmed_to_range( original_track, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(5, 24), duration=otio.opentime.RationalTime(50, 24) ) ) with self.assertRaises(otio.exceptions.CannotTrimTransitionsError): trimmed = otio.algorithms.track_trimmed_to_range( original_track, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(45, 24), duration=otio.opentime.RationalTime(50, 24) ) ) trimmed = otio.algorithms.track_trimmed_to_range( original_track, otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(25, 24), duration=otio.opentime.RationalTime(50, 24) ) ) self.assertNotEqual(original_track, trimmed) expected = otio.adapters.read_from_string(""" { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "A", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 25 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 25.0 } } }, { "OTIO_SCHEMA": "Transition.1", "name": "", "metadata": {}, "transition_type": "", "in_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 12 }, "out_offset": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 20 } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "B", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 25 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } """, "otio_json") self.assertJsonEqual(expected, trimmed) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_serialization.cpp0000664000175000017500000000742315110656141020177 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "utils.h" #include #include #include #include #include #include #include #include #include namespace otime = opentime::OPENTIME_VERSION; namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; int main(int argc, char** argv) { Tests tests; tests.add_test( "success with default indent", [] { otio::SerializableObject::Retainer cl = new otio::Clip(); otio::SerializableObject::Retainer tr = new otio::Track(); tr->append_child(cl); otio::SerializableObject::Retainer tl = new otio::Timeline(); tl->tracks()->append_child(tr); otio::ErrorStatus err; auto output = tl.value->to_json_string(&err, {}); assertFalse(otio::is_error(err)); assertEqual(output.c_str(), R"CONTENT({ "OTIO_SCHEMA": "Timeline.1", "metadata": {}, "name": "", "global_start_time": null, "tracks": { "OTIO_SCHEMA": "Stack.1", "metadata": {}, "name": "tracks", "source_range": null, "effects": [], "markers": [], "enabled": true, "color": null, "children": [ { "OTIO_SCHEMA": "Track.1", "metadata": {}, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "color": null, "children": [ { "OTIO_SCHEMA": "Clip.2", "metadata": {}, "name": "", "source_range": null, "effects": [], "markers": [], "enabled": true, "color": null, "media_references": { "DEFAULT_MEDIA": { "OTIO_SCHEMA": "MissingReference.1", "metadata": {}, "name": "", "available_range": null, "available_image_bounds": null } }, "active_media_reference_key": "DEFAULT_MEDIA" } ], "kind": "Video" } ] } })CONTENT"); }); tests.add_test( "success with indent set to 0", [] { otio::SerializableObject::Retainer so = new otio::SerializableObjectWithMetadata(); otio::ErrorStatus err; auto output = so.value->to_json_string(&err, {}, 0); assertFalse(otio::is_error(err)); assertEqual(output.c_str(), R"CONTENT({"OTIO_SCHEMA":"SerializableObjectWithMetadata.1","metadata":{},"name":""})CONTENT"); }); tests.add_test( "success with indent set to 2", [] { otio::SerializableObject::Retainer so = new otio::SerializableObjectWithMetadata(); otio::ErrorStatus err; auto output = so.value->to_json_string(&err, {}, 2); assertFalse(otio::is_error(err)); assertEqual(output.c_str(), R"CONTENT({ "OTIO_SCHEMA": "SerializableObjectWithMetadata.1", "metadata": {}, "name": "" })CONTENT"); }); tests.run(argc, argv); return 0; } opentimelineio-0.18.1/tests/utils.h0000664000175000017500000000330515110656141015063 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #undef NDEBUG #include #include #include #include #include #include #include void assertTrue(bool value); void assertFalse(bool value); template inline void assertEqual(T const& a, T const& b) { assert(a == b); } // We are not testing values outside of one million seconds. // At one million second, and double precision, the smallest // resolvable number that can be added to one million and return // a new value one million + epsilon is 5.82077e-11. // // This was calculated by searching iteratively for epsilon // around 1,000,000, with epsilon starting from 1 and halved // at every iteration, until epsilon when added to 1,000,000 // resulted in 1,000,000. constexpr double double_epsilon = 5.82077e-11; inline void assertEqual(double a, double b) { assert(std::abs(a - b) <= double_epsilon); } inline void assertEqual(const char* a, const char* b) { assert(a != nullptr); assert(b != nullptr); assert(strcmp(a, b) == 0); } inline void assertEqual(const void* a, const void* b) { assert(a == b); } template inline void assertNotEqual(T const& a, T const& b) { assert(a != b); } inline void assertNotEqual(double a, double b) { assert(std::abs(a - b) > double_epsilon); } inline void assertNotNull(const void* a) { assert(a != nullptr); } class Tests { public: void add_test(std::string const& name, std::function const& test); void run(int argc, char** argv); private: std::vector>> _tests; }; opentimelineio-0.18.1/tests/test_stack_algo.py0000664000175000017500000005105115110656141017273 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Test file for the stack algorithms library.""" import unittest import os import opentimelineio as otio import opentimelineio.test_utils as otio_test_utils SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data") MULTITRACK_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "multitrack.otio") PREFLATTENED_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "preflattened.otio") class StackAlgoTests(unittest.TestCase, otio_test_utils.OTIOAssertions): """ test harness for stack algo functions """ def setUp(self): self.trackZ = otio.adapters.read_from_string(""" { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "Z", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 150 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } """, "otio_json") self.trackZd = otio.adapters.read_from_string(""" { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "enabled": false, "metadata": {}, "name": "Z", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 150 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } """, "otio_json") self.track_d = otio.adapters.read_from_string(""" { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "enabled": true, "metadata": {}, "name": "Z", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 150 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "enabled": false, "metadata": {}, "name": "Sequence1", "source_range": null } """, "otio_json") self.trackABC = otio.adapters.read_from_string(""" { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "A", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "B", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "C", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } """, "otio_json") self.trackDgE = otio.adapters.read_from_string(""" { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "D", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "g", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "E", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } """, "otio_json") self.trackgFg = otio.adapters.read_from_string(""" { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "g1", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "F", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Gap.1", "effects": [], "markers": [], "media_reference": null, "metadata": {}, "name": "g2", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Sequence1", "source_range": null } """, "otio_json") def test_flatten_single_track(self): stack = otio.schema.Stack(children=[ self.trackABC ]) flat_track = otio.algorithms.flatten_stack(stack) # the result should be equivalent self.assertJsonEqual( flat_track[:], self.trackABC[:] ) # but not the same actual objects self.assertIsNot( flat_track[0], self.trackABC[0] ) self.assertIsNot( flat_track[1], self.trackABC[1] ) self.assertIsNot( flat_track[2], self.trackABC[2] ) def test_flatten_obscured_track(self): stack = otio.schema.Stack(children=[ self.trackABC, self.trackZ ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertJsonEqual( flat_track[:], self.trackZ[:] ) # It is an error to add an item to composition if it is already in # another composition. This clears out the old test composition # (and also clears out its parent pointers). del stack stack = otio.schema.Stack( children=[ self.trackZ, self.trackABC, ] ) flat_track = otio.algorithms.flatten_stack(stack) self.assertJsonEqual( flat_track[:], self.trackABC[:] ) def test_flatten_disabled_clip(self): stack = otio.schema.Stack(children=[ self.trackABC, self.trackZ ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertJsonEqual( flat_track[:], self.trackZ[:] ) del stack stack = otio.schema.Stack(children=[ self.trackABC, self.trackZd ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertJsonEqual( flat_track[:], self.trackABC[:] ) def test_flatten_disabled_track(self): stack = otio.schema.Stack(children=[ self.trackABC, self.trackZ ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertJsonEqual( flat_track[:], self.trackZ[:] ) del stack stack = otio.schema.Stack(children=[ self.trackABC, self.track_d ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertJsonEqual( flat_track[:], self.trackABC[:] ) def test_flatten_gaps(self): stack = otio.schema.Stack(children=[ self.trackABC, self.trackDgE ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertIsOTIOEquivalentTo(flat_track[0], self.trackDgE[0]) self.assertIsOTIOEquivalentTo(flat_track[1], self.trackABC[1]) self.assertIsOTIOEquivalentTo(flat_track[2], self.trackDgE[2]) self.assertIsNot(flat_track[0], self.trackDgE[0]) self.assertIsNot(flat_track[1], self.trackABC[1]) self.assertIsNot(flat_track[2], self.trackDgE[2]) # create a new stack out of the old parts, delete the old stack first del stack stack = otio.schema.Stack(children=[ self.trackABC, self.trackgFg ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertIsOTIOEquivalentTo(flat_track[0], self.trackABC[0]) self.assertIsOTIOEquivalentTo(flat_track[1], self.trackgFg[1]) self.assertIsOTIOEquivalentTo(flat_track[2], self.trackABC[2]) self.assertIsNot(flat_track[0], self.trackABC[0]) self.assertIsNot(flat_track[1], self.trackgFg[1]) self.assertIsNot(flat_track[2], self.trackABC[2]) def test_flatten_gaps_with_trims(self): stack = otio.schema.Stack(children=[ self.trackZ, self.trackDgE ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertIsOTIOEquivalentTo(flat_track[0], self.trackDgE[0]) self.assertEqual(flat_track[1].name, "Z") self.assertEqual( flat_track[1].source_range, otio.opentime.TimeRange( otio.opentime.RationalTime(50, 24), otio.opentime.RationalTime(50, 24) ) ) self.assertIsOTIOEquivalentTo(flat_track[2], self.trackDgE[2]) del stack stack = otio.schema.Stack(children=[ self.trackZ, self.trackgFg ]) flat_track = otio.algorithms.flatten_stack(stack) self.assertEqual(flat_track[0].name, "Z") self.assertEqual( flat_track[0].source_range, otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(50, 24) ) ) self.assertIsOTIOEquivalentTo(flat_track[1], self.trackgFg[1]) self.assertEqual(flat_track[2].name, "Z") self.assertEqual( flat_track[2].source_range, otio.opentime.TimeRange( otio.opentime.RationalTime(100, 24), otio.opentime.RationalTime(50, 24) ) ) def test_flatten_list_of_tracks(self): tracks = [ self.trackABC, self.trackDgE ] flat_track = otio.algorithms.flatten_stack(tracks) self.assertIsOTIOEquivalentTo(flat_track[0], self.trackDgE[0]) self.assertIsOTIOEquivalentTo(flat_track[1], self.trackABC[1]) self.assertIsOTIOEquivalentTo(flat_track[2], self.trackDgE[2]) tracks = [ self.trackABC, self.trackgFg ] flat_track = otio.algorithms.flatten_stack(tracks) self.assertIsOTIOEquivalentTo(flat_track[0], self.trackABC[0]) self.assertIsOTIOEquivalentTo(flat_track[1], self.trackgFg[1]) self.assertIsOTIOEquivalentTo(flat_track[2], self.trackABC[2]) def test_flatten_example_code(self): timeline = otio.adapters.read_from_file(MULTITRACK_EXAMPLE_PATH) preflattened = otio.adapters.read_from_file(PREFLATTENED_EXAMPLE_PATH) preflattened_track = preflattened.video_tracks()[0] flattened_track = otio.algorithms.flatten_stack( timeline.video_tracks() ) # the names will be different, so clear them both preflattened_track.name = "" flattened_track.name = "" self.assertOTIOEqual( preflattened_track, flattened_track ) def assertOTIOEqual(self, a, b): self.maxDiff = None self.assertMultiLineEqual( otio.adapters.write_to_string(a, 'otio_json'), otio.adapters.write_to_string(b, 'otio_json') ) def test_flatten_with_transition(self): stack = otio.schema.Stack( children=[ self.trackABC, self.trackDgE, ] ) stack[1].insert( 1, otio.schema.Transition( name="test_transition", in_offset=otio.opentime.RationalTime(10, 24), out_offset=otio.opentime.RationalTime(15, 24) ) ) flat_track = otio.algorithms.flatten_stack(stack) self.assertEqual(3, len(self.trackABC)) self.assertEqual(4, len(stack[1])) self.assertEqual(4, len(flat_track)) self.assertEqual(flat_track[1].name, "test_transition") def test_top_child_at_time(self): stack = otio.schema.Stack( children=[ self.trackABC, self.trackDgE, ] ) top_child = otio.algorithms.top_clip_at_time( stack, otio.opentime.RationalTime(0, 24) ) self.assertEqual(top_child, self.trackDgE[0]) stack.append( otio.schema.Track( children=[ otio.schema.Gap( source_range=otio.opentime.TimeRange( otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(10, 24) ) ) ] ) ) top_child = otio.algorithms.top_clip_at_time( stack, otio.opentime.RationalTime(0, 24) ) self.assertEqual(top_child, self.trackDgE[0]) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/tests/test_version_manifest.py0000664000175000017500000001101115110656141020527 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """unit tests for the version manifest plugin system""" import unittest import os import json import opentimelineio as otio from tests import utils FIRST_MANIFEST = """{ "OTIO_SCHEMA" : "PluginManifest.1", "version_manifests": { "UNIQUE_FAMILY": { "TEST_LABEL": { "second_thing": 3 } }, "LAYERED_FAMILY": { "June2022": { "SimpleClass": 2 }, "May2022": { "SimpleClass": 1 } } } } """ SECOND_MANIFEST = """{ "OTIO_SCHEMA" : "PluginManifest.1", "version_manifests": { "LAYERED_FAMILY": { "May2022": { "SimpleClass": 2 }, "April2022": { "SimpleClass": 1 } } } } """ class TestPlugin_VersionManifest(unittest.TestCase): def setUp(self): self.bak = otio.plugins.ActiveManifest() self.man = utils.create_manifest() otio.plugins.manifest._MANIFEST = self.man if "OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL" in os.environ: del os.environ["OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL"] def tearDown(self): otio.plugins.manifest._MANIFEST = self.bak utils.remove_manifest(self.man) if "OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL" in os.environ: del os.environ["OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL"] def test_read_in_manifest(self): self.assertIn("TEST_FAMILY_NAME", self.man.version_manifests) self.assertIn( "TEST_LABEL", self.man.version_manifests["TEST_FAMILY_NAME"] ) def test_full_map(self): d = otio.versioning.full_map() self.assertIn("TEST_FAMILY_NAME", d) self.assertIn( "TEST_LABEL", d["TEST_FAMILY_NAME"] ) def test_fetch_map(self): self.assertEqual( otio.versioning.fetch_map("TEST_FAMILY_NAME", "TEST_LABEL"), {"ExampleSchema": 2, "EnvVarTestSchema": 1, "Clip": 1} ) def test_env_variable_downgrade(self): @otio.core.register_type class EnvVarTestSchema(otio.core.SerializableObject): _serializable_label = "EnvVarTestSchema.2" foo_two = otio.core.serializable_field("foo_2") @otio.core.downgrade_function_from(EnvVarTestSchema, 2) def downgrade_2_to_1(_data_dict): return {"foo": _data_dict["foo_2"]} evt = EnvVarTestSchema() evt.foo_two = "asdf" result = json.loads(otio.adapters.otio_json.write_to_string(evt)) self.assertEqual(result["OTIO_SCHEMA"], "EnvVarTestSchema.2") # env variable should make a downgrade by default... os.environ["OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL"] = ( "TEST_FAMILY_NAME:TEST_LABEL" ) result = json.loads(otio.adapters.otio_json.write_to_string(evt)) self.assertEqual(result["OTIO_SCHEMA"], "EnvVarTestSchema.1") # ...but can still be overridden by passing in an argument result = json.loads(otio.adapters.otio_json.write_to_string(evt, {})) self.assertEqual(result["OTIO_SCHEMA"], "EnvVarTestSchema.2") def test_garbage_env_variables(self): cl = otio.schema.Clip() invalid_env_error = otio.exceptions.InvalidEnvironmentVariableError # missing ":" os.environ["OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL"] = ( "invalid_formatting" ) with self.assertRaises(invalid_env_error): otio.adapters.otio_json.write_to_string(cl) # asking for family/label that doesn't exist in the plugins os.environ["OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL"] = ( "nosuch:labelorfamily" ) with self.assertRaises(invalid_env_error): otio.adapters.otio_json.write_to_string(cl) def test_two_version_manifests(self): """test that two manifests layer correctly""" fst = otio.plugins.manifest.manifest_from_string(FIRST_MANIFEST) snd = otio.plugins.manifest.manifest_from_string(SECOND_MANIFEST) fst.extend(snd) self.assertIn("UNIQUE_FAMILY", fst.version_manifests) lay_fam = fst.version_manifests["LAYERED_FAMILY"] self.assertIn("June2022", lay_fam) self.assertIn("April2022", lay_fam) self.assertEqual(lay_fam["May2022"]["SimpleClass"], 2) if __name__ == '__main__': unittest.main() opentimelineio-0.18.1/setup.py0000664000175000017500000003235615110656316014136 0ustar meme#! /usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Setup.py for installing OpenTimelineIO For more information: - see README.md - http://opentimeline.io """ import multiprocessing import os import shlex import sys import platform import subprocess import tempfile import shutil from setuptools import ( setup, Extension, find_packages, ) import setuptools.command.build_ext import setuptools.command.build_py SOURCE_DIR = os.path.abspath(os.path.dirname(__file__)) PLAT_TO_CMAKE = { "win32": "Win32", "win-amd64": "x64", } def _debugInstance(x): for a in sorted(dir(x)): print("{}: {}".format(a, getattr(x, a))) def join_args(args): return ' '.join(map(shlex.quote, args)) class OTIO_build_ext(setuptools.command.build_ext.build_ext): """ def initialize_options(self): super(setuptools.command.build_ext.build_ext, self).initialize_options() """ built = False def run(self): self.announce('running OTIO build_ext', level=2) # Let the original build_ext class do its job. # This is rather important because build_ext.run takes care of a # couple of things, one of which is to copy the built files into # the source tree (in src/py-opentimelineio/opentimelineio) # when building in editable mode. super().run() def build_extension(self, _ext: Extension): # This works around the fact that we build _opentime and _otio # extensions as a one-shot cmake invocation. Setuptools calls # build_extension for each Extension registered in the setup function. if not self.built: self.build() self.built = True def build(self): self.build_temp_dir = ( os.environ.get("OTIO_CXX_BUILD_TMP_DIR") or os.path.abspath(self.build_temp) ) if not os.path.exists(self.build_temp_dir): os.makedirs(self.build_temp_dir) debug = (self.debug or bool(os.environ.get("OTIO_CXX_DEBUG_BUILD"))) self.build_config = ('Debug' if debug else 'Release') self.cmake_preflight_check() self.cmake_generate() self.cmake_install() def is_windows(self): return platform.system() == "Windows" def is_mingw(self): return self.plat_name.startswith('mingw') def generate_cmake_arguments(self): # Use the provided build dir so setuptools will be able to locate and # either install to the correct location or package. install_dir = os.path.abspath(self.build_lib) if not install_dir.endswith(os.path.sep): install_dir += os.path.sep cmake_args = [ # Python_EXECUTABLE is important as it tells CMake's FindPython # which Python executable to use. We absolutely want to use the # interpreter that was used to execute the setup.py. # See https://cmake.org/cmake/help/v3.20/module/FindPython.html#artifacts-specification # noqa: E501 # Also, be careful, CMake is case sensitive ;) '-DPython_EXECUTABLE=' + sys.executable, '-DOTIO_PYTHON_INSTALL:BOOL=ON', '-DOTIO_CXX_INSTALL:BOOL=OFF', '-DOTIO_SHARED_LIBS:BOOL=OFF', '-DCMAKE_BUILD_TYPE=' + self.build_config, '-DOTIO_PYTHON_INSTALL_DIR=' + install_dir, # turn off the C++ tests during a Python build '-DBUILD_TESTING:BOOL=OFF', # Python modules will be installed by setuptools. '-DOTIO_INSTALL_PYTHON_MODULES:BOOL=OFF', ] if self.is_windows(): if self.is_mingw(): cmake_args += ['-G Unix Makefiles'] else: cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] cxx_coverage = bool(os.environ.get("OTIO_CXX_COVERAGE_BUILD")) if cxx_coverage and not os.environ.get("OTIO_CXX_BUILD_TMP_DIR"): raise RuntimeError( "C++ code coverage requires that both OTIO_CXX_COVERAGE_BUILD=ON " "and OTIO_CXX_BUILD_TMP_DIR are specified as environment " "variables, otherwise coverage cannot be generated." ) if cxx_coverage: cmake_args += ['-DOTIO_CXX_COVERAGE=1'] # allow external arguments to cmake via the CMAKE_ARGS env var cmake_args += [ arg for arg in os.environ.get("CMAKE_ARGS", "").split(" ") if arg ] return cmake_args def cmake_preflight_check(self): """ Verify that CMake is greater or equal to the required version We do this so that the error message is clear if the minimum version is not met. """ self.announce('running cmake check', level=2) # We need to run cmake --check-system-vars because it will still generate # a CMakeCache.txt file. tmpdir = tempfile.mkdtemp(dir=self.build_temp_dir) args = ["--check-system-vars", SOURCE_DIR] + self.generate_cmake_arguments() proc = subprocess.Popen( ["cmake"] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=tmpdir, universal_newlines=True ) _, stderr = proc.communicate() if proc.returncode != 0: raise RuntimeError(stderr.strip()) shutil.rmtree(tmpdir) def cmake_generate(self): self.announce('running cmake generation', level=2) cmake_args = ['cmake', SOURCE_DIR] + self.generate_cmake_arguments() self.announce(join_args(cmake_args), level=2) subprocess.check_call( cmake_args, cwd=self.build_temp_dir, env=os.environ.copy() ) def cmake_install(self): self.announce('running cmake build', level=2) if self.is_windows() and not self.is_mingw(): multi_proc = '/m' else: multi_proc = f'-j{multiprocessing.cpu_count()}' cmake_args = [ 'cmake', '--build', '.', '--target', 'install', '--config', self.build_config, '--', multi_proc, ] self.announce(join_args(cmake_args), level=2) subprocess.check_call( cmake_args, cwd=self.build_temp_dir, env=os.environ.copy() ) # check the python version first if ( sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 9) ): sys.exit( 'OpenTimelineIO requires python3.9 or greater, detected version:' ' {}.{}'.format( sys.version_info[0], sys.version_info[1] ) ) # Metadata that gets stamped into the __init__ files during the build phase. PROJECT_METADATA = { "version": "0.18.1", "author": 'Contributors to the OpenTimelineIO project', "author_email": 'otio-discussion@lists.aswf.io', "license": 'Apache 2.0 License', } METADATA_TEMPLATE = """ __version__ = "{version}" __author__ = "{author}" __author_email__ = "{author_email}" __license__ = "{license}" """ def _append_version_info_to_init_scripts(build_lib): """Stamp PROJECT_METADATA into __init__ files.""" for module, parentdir in [ ("opentimelineio", "src/py-opentimelineio"), ]: target_file = os.path.join(build_lib, module, "__init__.py") source_file = os.path.join( os.path.dirname(__file__), parentdir, module, "__init__.py" ) # get the base data from the original file with open(source_file) as fi: src_data = fi.read() # write that + the suffix to the target file with open(target_file, 'w') as fo: fo.write(src_data) fo.write(METADATA_TEMPLATE.format(**PROJECT_METADATA)) class OTIO_build_py(setuptools.command.build_py.build_py): """Stamps PROJECT_METADATA into __init__ files.""" def run(self): super().run() # editable_mode isn't always present try: editable_mode = self.editable_mode except AttributeError: editable_mode = False if not self.dry_run and not editable_mode: # Only run when not in dry-mode (a dry run should not have any side effect) # and in non-editable mode. We don't want to edit files when in editable # mode because that could lead to modifications to the source files. # Note that setuptools will set self.editable_mode to True # when "pip install -e ." is run. _append_version_info_to_init_scripts(self.build_lib) # copied from first paragraph of README.md LONG_DESCRIPTION = """OpenTimelineIO is an interchange format and API for editorial cut information. OTIO is not a container format for media, rather it contains information about the order and length of cuts and references to external media. OTIO includes both a file format and an API for manipulating that format. It also includes a plugin architecture for writing adapters to convert from/to existing editorial timeline formats. It also implements a dependency- less library for dealing strictly with time, opentime. You can provide adapters for your video editing tool or pipeline as needed. Each adapter allows for import/export between that proprietary tool and the OpenTimelineIO format.""" setup( name='OpenTimelineIO', description='Editorial interchange format and API', long_description=LONG_DESCRIPTION, long_description_content_type='text/markdown', url='http://opentimeline.io', project_urls={ 'Source': 'https://github.com/AcademySoftwareFoundation/OpenTimelineIO', 'Documentation': 'https://opentimelineio.readthedocs.io/', 'Issues': 'https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues', }, classifiers=[ 'Development Status :: 4 - Beta', 'Topic :: Multimedia :: Graphics', 'Topic :: Multimedia :: Video', 'Topic :: Multimedia :: Video :: Display', 'Topic :: Multimedia :: Video :: Non-Linear Editor', 'Topic :: Software Development :: Libraries :: Python Modules', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Operating System :: OS Independent', 'Natural Language :: English', ], keywords='film tv editing editorial edit non-linear edl time', platforms='any', package_data={ 'opentimelineio': [ 'adapters/builtin_adapters.plugin_manifest.json', ], }, packages=( find_packages(where="src/py-opentimelineio") # opentimelineio ), ext_modules=[ # The full and correct module name is required here because # setuptools needs to resolve the name to find the built file # and copy it into the source tree. (Because yes, editable install # means that the install should point to the source tree, which # means the .sos need to also be there alongside the python files). Extension('opentimelineio._otio', sources=[]), Extension('opentimelineio._opentime', sources=[]), ], package_dir={ 'opentimelineio': 'src/py-opentimelineio/opentimelineio', }, # Disallow 3.9.0 because of https://github.com/python/cpython/pull/22670 python_requires='>3.9.0', # noqa: E501 install_requires=[ ], entry_points={ 'console_scripts': [ 'otiocat = opentimelineio.console.otiocat:main', 'otioconvert = opentimelineio.console.otioconvert:main', 'otiopluginfo = opentimelineio.console.otiopluginfo:main', 'otiostat = opentimelineio.console.otiostat:main', 'otiotool = opentimelineio.console.otiotool:main', ( 'otioautogen_serialized_schema_docs = ' 'opentimelineio.console.autogen_serialized_datamodel:main' ), ], }, extras_require={ 'dev': [ 'check-manifest', 'flake8>=3.5', 'coverage>=4.5', 'urllib3>=1.24.3' ], }, # because we need to open() the adapters manifest, we aren't zip-safe zip_safe=False, # The sequence of operations performed by setup.py is: # OTIO_install::initialize # OTIO_install::run # OTIO_build_py::run # the OpenTimelineIO egg is created # OTIO_build_ext::initialize_options # MANIFEST.in is read # the lack of CHANGELOG.md is then reported. # OTIO_build_ext::run is called # OTIO_build_ext::build # site-packages/opentimelineio* is populated with all scripts and extensions. # pyc's are created for every python script. # The egg is moved into site-packages, # wrapper scripts for all the otiotools are created in a bin directory at the # installation root. cmdclass={ 'build_py': OTIO_build_py, 'build_ext': OTIO_build_ext, }, # expand the project metadata dictionary to fill in those values **PROJECT_METADATA ) opentimelineio-0.18.1/ADOPTERS.md0000664000175000017500000000303715110656141014155 0ustar meme# OpenTimelineIO Adopters Below is a partial list of organizations and projects that are using OpenTimelineIO. If you would like to be added to this list, please submit a pull request to this file. | Name | Description | |------|------------------------------------------------------------------------------------------------------------------------------------------------| | [Adobe Premiere Pro](https://www.adobe.com/products/premiere.html) | [Timeline import/export](https://community.adobe.com/t5/premiere-pro-beta-discussions/new-in-beta-otio-import-and-export/td-p/14937493) (beta) | | [AVID Media Composer](https://www.avid.com/media-composer) | Timeline export (preview) | | [Black Magic Design DaVinci Resolve](https://www.blackmagicdesign.com/products/davinciresolve/) | Timeline import/export | | [CineSync](https://www.backlight.co/product/cinesync) | Timeline viewing | | [ColorFront Transkoder](https://colorfront.com/index.php?page=SOFTWARE&spage=Transkoder) | Timeline import | | [Hiero](https://www.foundry.com/products/nuke-family/hiero) | Timeline import/export | | [Kdenlive](https://kdenlive.org) | Timeline import/export | | [Nuke Studio](https://www.foundry.com/products/nuke) | Timeline import/export with full timeline round-tripping | | [OpenRV](https://github.com/AcademySoftwareFoundation/OpenRV) | Timeline import and viewing | opentimelineio-0.18.1/.codecov.yml0000664000175000017500000000060715110656141014635 0ustar memecodecov: notify: {} require_ci_to_pass: yes coverage: precision: 2 round: down range: "70...95" status: project: yes patch: yes changes: yes comment: layout: "reach, diff, flags, files, footer" behavior: default require_changes: no ignore: - "*aaf2*" - "*pkg_resources*" - "*pbr*" - "*mock*" - "*PIL*" - "*funcsigs*" - "/usr/*" - "*/deps/*" opentimelineio-0.18.1/docs/0000775000175000017500000000000015110656141013337 5ustar memeopentimelineio-0.18.1/docs/Makefile0000664000175000017500000001775515110656141015016 0ustar meme# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = -n -j8 SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " htmlstrict to make standalone HTML files and fails if any warnings or errors are produced" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " livehtml to make standalone HTML files served by a web server that will automatically reload and rebuild when a file changes" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: htmlstrict htmlstrict: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/htmlstrict -W --keep-going -w $(BUILDDIR)/htmlstrict/output.txt @echo @echo "Warnings check complete." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: livehtml livehtml: sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenTimelineIO.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenTimelineIO.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/OpenTimelineIO" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenTimelineIO" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." opentimelineio-0.18.1/docs/use-cases/0000775000175000017500000000000015110656141015227 5ustar memeopentimelineio-0.18.1/docs/use-cases/conform-new-renders-into-cut.md0000664000175000017500000000256015110656141023206 0ustar meme# Conform New Renders Into The Cut **Status: Done** This use case is in active use at several feature film production studios. ## Summary Artists working on the animation or visual effects for shots in a sequence often want to view their in-progress work in the context of a current cut of the film. This could be accomplished by importing their latest renders into the editing system, but that often involves many steps (e.g. transcoding, cutting the clips into the editing system, etc.) Instead, the artists would like to preview the cut with their latest work spliced in at their desk. ## Workflow * In Editorial: 1. Export an EDL from the editorial system (Media Composer, Adobe Premiere, Final Cut Pro X, etc.) 2. Export a QuickTime audio/video mixdown that matches that EDL 3. Send the EDL+ QuickTime to the animators or visual effects artists * At the Artist's Desk: 1. Open the EDL+QuickTime in a video player tool (RV, etc.) 2. Either: 2a. Use OpenTimelineIO to convert the EDL to OTIO or 2b. A plugin in the video player tool uses OpenTimelineIO to read the EDL. 3. In either case, we link the shots in the timeline to segments of the supplied QuickTime movie. 3. The artist can now play the sequence and see exactly what the editor saw. 4. The artist can now relink any or all of the shots to the latest renders (either via OpenTimelineIO or features of the video player tool) opentimelineio-0.18.1/docs/use-cases/animation-shot-frame-ranges.md0000664000175000017500000000671015110656141023054 0ustar meme# Animation Shot Frame Ranges Changed **Status: Planned** ## Summary This case is very similar to the [](/use-cases/shots-added-removed-from-cut). The editorial and animation departments are working with a sequence of shots simultaneously over the course of a few weeks. The initial delivery of rendered video clips from animation to editorial provides enough footage for the editor(s) to work with, at least as a starting point. As the cut evolves, the editor(s) may need more frames at the head or tail of some shots, or they may trim frames from the head or tail that are no longer needed. Usually there is an agreement that some extra frames, called handles, should be present at the head and tail of each shot to give the editors some flexibility. In the case where the editors need more frames than the handles provide, they might use a freeze frame effect, or a slow down effect to stretch the clip, or simply repeat a segment of a clip to fill the gap. This is a sign that new revisions of those shots should be animated and rendered with more frames to fill the needs of the cut. Furthermore, as the sequence nears completion, the cut becomes more stable and the cost of rendering frames becomes higher, so there is a desire to trim unused handles from the shots on the animation side. In both cases, we can use OTIO to compare the frame range of each shot between the two departments. ## Example Animation delivers the first pass of 100 shots to editorial. Editorial makes an initial cut of the sequence. In the cut, several shots are trimmed down to less than half of the initial length, but 2 shots need to be extended. Editorial exports an EDL or AAF of the sequence from Avid Media Composer and gives this cut to the animation department. Animation runs a Python script which compares the frame range of each shot used in the cut to the frame range of the most recent take of each shot being animated. Any shot that is too short must be extended and any shot that is more than 12 frames too long can be trimmed down. The revised shots are animated, re-rendered and re-delivered to editorial. Upon receiving these new deliveries, editorial will cut them into the sequence (see also [](/use-cases/conform-new-renders-into-cut)). For shots that used timing effects to temporarily extend them, those effects can be removed, since the new version of those shots is now longer. ## Features Needed in OTIO - EDL reading - Clip names for video track - Source frame range for each clip - [Timing effects](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues/39) - [AAF reading](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues/1) - Clip names across all video tracks, subclips, etc. - Source frame range for each clip - Timing effects - Timeline should include (done) - a Stack of tracks, each of which is a Sequence - Sequence should include (done) - a list of Clips - Clips should include (done) - Name - Metadata - Timing effects - [Timing effects](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues/39) - Source frame range of each clip as effected by timing effects. - Composition - Clips in lower tracks that are obscured (totally or partially) by overlapping clips in higher tracks are considered trimmed or hidden. - Visible frame range for each clip. ## Features of Python Script - Use OTIO to read the EDL or AAF - Iterate through every Clip in the Timeline, printing its name and visible frame rangeopentimelineio-0.18.1/docs/use-cases/shots-added-removed-from-cut.md0000664000175000017500000000563215110656141023147 0ustar meme# Shots Added or Removed From The Cut **Status: Planned** ## Summary The creative process of editing often involves adding, removing or replacing shots in a sequence. Other groups of people working on the same project want to know about changes to the list of shots in use on a day to day basis. For example, animators working on a shot should be informed if the shot they are working on has been cut from the sequence, so they can stop working on it. Similarly, if new shots are added, animation should start working on those shots. Since the creative decision about which shots are in or out of the cut comes from editorial, we can use OTIO to communicate these changes. ## Example Editorial is working on a short film in Avid Media Composer. They have several bins of media with live action footage, rendered animation clips, dialogue recordings, sound effects and music. The lead editor is actively working on the cut for the short film over the course of a few weeks. At the same time, the animation department is actively working on the animated shots for the film. As revisions are made to the animated shots, rendered clips are delivered to editorial with a well established naming convention. On a daily basis, an EDL or AAF is exported from Media Composer and passed to the animation department so they can stay up to date with the current cut. In each revision of the cut, Animation wants to know which shots have been added or removed. They run a Python script which uses OpenTimelineIO to read an EDL or AAF from editorial and produces a list of video clip names found in the cut. Some of these names match the animation department's shot naming convention - or contain shot tracking metadata - and can be compared to existing shots that the animators are working on. If there are shots being animated that are not in this cut, then animation can stop working on those shots, as they are no longer needed. When the editor wants to request a new shot with a new camera angle or new animation, he or she can duplicate an existing clip and give it a new name, or insert a placeholder with a previously unused name, or otherwise flag the new clip as a request for a new shot. When animation sees this newly requested shot in the cut, they can respond as appropriate and deliver the new shot to editorial when it is ready. ## Features Needed in OTIO - EDL reading (done) - Clip names across all tracks - [AAF reading](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues/1) - Clip names across all tracks, subclips, etc. - Timeline should include (done) - a Stack of tracks, each of which is a Sequence - Sequence should include (done) - a list of Clips - Clips should include (done) - Name - Metadata ## Features of Python Script - Use OTIO to read the EDL or AAF. (done) - Iterate through every Clip in the Timeline, printing its name. (done) - Compare these names to the shots in a production tracking system.opentimelineio-0.18.1/docs/_templates/0000775000175000017500000000000015110656141015474 5ustar memeopentimelineio-0.18.1/docs/_templates/autosummary/0000775000175000017500000000000015110656141020062 5ustar memeopentimelineio-0.18.1/docs/_templates/autosummary/module.rst0000664000175000017500000000040615110656141022101 0ustar meme{{ fullname }} {{ underline }} .. automodule:: {{ fullname }} :members: {% block modules %} {% if modules %} .. rubric:: Modules .. autosummary:: :toctree: :recursive: {% for item in modules %} {{ item }} {%- endfor %} {% endif %} {% endblock %} opentimelineio-0.18.1/docs/cxx/0000775000175000017500000000000015110656141014141 5ustar memeopentimelineio-0.18.1/docs/cxx/bridges.md0000664000175000017500000000416415110656141016107 0ustar meme# Language Bridges ## Python Since OTIO originated as Python (and has an extensive test suite, in Python), our starting position is that existing Python code (adapters, plugins, schemadefs) should continue to work, as currently written, with as few changes as possible. However, in anticipation of the rewrite of the core in C++, some changes are were made proactively made to ease this transition. For example, the Opentime types (e.g. ``RationalTime``) have value semantics in C++, but reference semantics in Python, which has actually been a source of bugs. Recent changes to the Python code have made the Opentime classes immutable, to ease the transition to them being entirely value types in C++. Python code in the `core` or `schema` directories were rewritten, but Python code outside those modules should required little (or in some cases no) changes. The bridge from C++ to Python (and back) is `pybind11`. Given that existing code needs to work, clearly, the bridge is implemented so as to make the reflection of the C++ datastructures, back to Python, utterly "Pythonic." (It has to be, since we didn't want to break existing code.) ## Swift The intention is to expose OTIO in Swift with the same care we take with Python: we want everything to feel utterly Swift-like. Because Swift can gain automatic API access to non-member functions written in Objective-C++, and Objective-C++ can directly use the proposed OTIO C++ API, we believe that a bridge to swift will not require writing an explicit `extern "C"` wrapper around OTIO C++. We believe that like Python, Swift should be capable of defining new schemas, and that access to existing and new schemas and their properties should be done in terms of Swift API's that conform Swift's sequence/collection protocols, just as Python interfaces do with respect to Python. ## Bridging to C (and other languages) Bridging to C (and by extension other languages) would presumably be accomplished by writing an `extern "C"` wrapper around the OTIO C++ API. This is of relatively low priority, given that we will have three languages (C++ itself, Python, and Swift) that do not need this. opentimelineio-0.18.1/docs/cxx/older.md0000664000175000017500000000542215110656141015573 0ustar meme# Writing OTIO in C, C++ or Python (June 2018) Here are some initial thoughts about the subject, from around June 2018, about providing languages other than Python. The actual current plan is [here](./cxx). The current python implementation of OTIO has been super helpful for defining the library and getting studio needs settled, but in order to integrate the library into vendor tools, a C/C++ implementation is required. We don't want to give up the Python API, however, so the plan is to port the library to C/C++ with a Python wrapper that implements an interface to the library as it currently stands; existing Python code shouldn't notice the switch. We can use the existing unit tests to vet the implementation and make sure that it matches the Python API. There are several options for how to wrap C/C++ in Python, the intent of this document is to discuss the options we see and their pros/cons. ## Python C-API link: [Python C-API](https://docs.python.org/2/c-api/index.html) Pros: * No extra dependencies Cons: * Extremely boilerplate heavy * Have to manually build every part of the binding * For users of boost, the bindings won't be directly compatible with boost bindings. * Error prone: less type-safe and the reference counting must be manually done ## Boost-Python link: [Boost-python](http://www.boost.org/doc/libs/1_64_0/libs/python/doc/html/index.html) Pros: * High level binding * Established, familiarity around the industry, reasonably popular Cons: * Heavy dependency to add to projects if they aren't already using boost ## PyBind11 link: [PyBind11 Github](https://github.com/pybind/pybind11) Pros: * High level binding * Takes advantage of C++11/17 features to make wrapping even more terse (if they're available) * Can be embedded in the project without requiring Boost Cons: * For users of boost, the bindings won't be directly compatible with boost bindings. * Newer and less established than other options. ## Conclusion After talking with several vendors, studios, and participants, we have concluded that we will make this: * C++ Implementation of OTIO (following VFX Platform CY2017 standard C++11) * Pybind11 Bindings * To support other languages will make a `extern "C"` wrapper around the C++ API * Support for Swift (with some bridging provided by NSObject derived classes written in Objective-C++) This will replace the current pure-Python implementation, attempting to match the current Python API as much as possible, so that existing Python programs that use OTIO should not need to be modified to make the switch. We will try to make this a smooth transition, by starting with `opentime` and working out to the rest of the API. Also, in the future, we will likely provide Boost Python bindings to the C++ API for applications that already use Boost Python. opentimelineio-0.18.1/docs/cxx/cxx.rst0000664000175000017500000010073315110656141015501 0ustar memeC++ Implementation Details ========================== Dependencies ++++++++++++ The C++ OpentimelineIO (OTIO) library implementation will have the following dependencies: * `rapidjson `_ * any (C++ class) * optional (C++ class) * The C++ Opentime library (see below) The need for an "optional" (i.e. a container class that holds either no value or some specific value, for a given type T) is currently small, but does occur in one key place (schemas which need to hold either a ``TimeRange`` or indicate their time range is undefined). In contrast, the need for an "any" (a C++ type-erased container) is pervasive, as it is the primary mechanism that serialization and deserialization rest upon. It is also the bridge to scripting systems like Python that are not strongly typed. The C++17 standard defines the types ``std::optional`` and ``std::any`` and these types are available in ``std::experimental`` in some other cases, and our implementation targets those. However, since many (probably most) sites are not yet compiling with C++17, our implementation makes available public domain C++11 compliant versions of these types: - `any (C++11 compliant) `_ - `optional (C++11 compliant) `_ Support for Python will require `pybind11 `_. The C++ Opentime library (i.e. ``RationalTime``, ``TimeRange`` and ``TimeTransform``) will have no outside dependencies. In fact, given the current Python specification, a C++ Opentime API (should) be fairly straightforward and uncontroversial. Reminder: these `sample header files `_ exist only to show the API; namespacing and other niceties are omitted. Starting Examples +++++++++++++++++ Defining a Schema ----------------- Before jumping into specifics, let's provide some simple examples of what we anticipate code for defining and using schemas will look like. Consider the ``Marker`` schema, which adds a ``TimeRange`` and a color to a schema which already defines properties ``name`` and ``metadata``: :: class Marker : public SerializableObjectWithMetadata { public: struct Schema { static std::string constexpr name = "Marker"; static int constexpr version = 1; }; using Parent = SerializableObjectWithMetadata; Marker(std::string const& name = std::string(), TimeRange const& marked_range = TimeRange(), std::string const& color = std::string("red"), AnyDictionary const& metadata = AnyDictionary()); TimeRange marked_range() const; void set_marked_range(TimeRange marked_range); std::string const& color() const; void set_color(std::string const& color); protected: virtual ~Marker(); virtual bool read_from(Reader&); virtual void write_to(Writer&) const; private: TimeRange _marked_range; std::color _color; }; The constructor takes four properties, two of which (``marked_range`` and ``color``) are stored directly in ``Marker``, with the remaining two (``name`` and ``metadata``) handled by the base class ``SerializableObjectWithMetadata``. For the OTIO API, we will write standard getters/setters to access properties; outside of OTIO, users could adopt this technique or provide other mechanisms (e.g. public access to member variables, if they like). The supplied Python binding code will allow users to define their own schemas in Python exactly as they do today, with no changes required. The ``Schema`` structure exists so that this type can be added to the OTIO type registry with a simple call: :: TypeRegistry::instance()::register_type(); The call to add a schema to the type registry would be done within the OTIO library itself for schemas known to OTIO; for schemas defined outside OTIO, the author of the schema would need to make the above call for their class early in a program's execution. Reading/Writing Properties -------------------------- Code must also be written to read/write the new properties. This is simple as well: :: bool Marker::read_from(Reader& reader) { return reader.read("color", &_color) && reader.read("marked_range", &_marked_range) && Parent::read_from(reader); } void Marker::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("color", _color); writer.write("marked_range", _marked_range); } Even when we define more complex properties, the reading/writing code is as simple as shown above, in almost all cases. When an error is encountered in reading, ``read_from`` should set the error on the ``Reader`` instance and return ``false``: :: bool Marker::read_from(Reader& reader) { if (!reader.read(“color”, &_color)) { return false; } if (_color == “invalid_value”) { reader.error( ErrorStatus(ErrorStatus::JSON_PARSE_ERROR, “invalid_value not allowed for color”)); return false; } return reader.read(“marked_range”, &_marked_range) && Parent::read_from(reader); } This is a contrived example but it describes the basic mechanics. Adjust the details above as appropriate for your case. .. Note:: Properties are written to the JSON file in the order they are written to from within ``write_to()``. But the reading code need not be in the same order, and changes in the ordering of either the reading or writing code will not break compatibility with previously written JSON files. However, it is vital to invoke ``Parent::read_from()`` *after* reading all of the derived class properties, while for writing ``Parent::write_to()`` must be invoked *before* writing the derived class properties. .. Note:: Also note that the order of properties within a JSON file for data that is essentially a ``std::map<>`` (see ``AnyDictionary`` below) is always alphabetical by key. This ensures deterministic JSON file writing which is important for comparison and testing. Using Schemas +++++++++++++ Creating and manipulating schema objects is also simple: :: Track* track = new Track(); Clip* clip1 = new Clip("clip1", new ExternalReference("/path/someFile.mov")); Clip* clip2 = new Clip("clip2"); track->append_child(clip1); track->append_child(clip2); ... for (Item* item: track->children()) { for (Effect* effect: item->effects()) { std::cout << effect->effect_name(); ... } } Serializable Data +++++++++++++++++ Data in OTIO schemas must be read and written as JSON. Data must also be available to C++, in some cases as strongly typed data, while in other cases as untyped data (i.e. presented as an ``any``). For discussion purposes, let us consider that all data that is read and written to JSON is transported as a C++ ``any``. What can that ``any`` hold? First, the ``any`` can be empty, which corresponds with a ``null`` JSON value. The ``any`` could also hold any of the following "atomic" types: ``bool``, ``int``, ``double``, ``std::string``, ``RationalTime``, ``TimeRange`` and ``TimeTransform``. All but the last three are immediately expressible in JSON, while the three Opentime types are read/written as compound structures with the same format that the current Python implementation delivers. The final "atomic" type that an ``any`` can hold is a ``SerializableObject*``, which represents the C++ base class for all schemas. (Note: it will not be valid for an ``any`` to hold a pointer to a derived class, for example, a ``Clip*`` value. The actual C++ static type in the ``any`` will always be a pointer to the base class ``SerializableObject``.) Next, for any of the above atomic types ``T``, excepting for ``SerializableObject*``, an ``any`` can store a type of ``optional``. (Supporting serialization of an ``optional`` would be ambiguous and unneeded; putting a null pointer of type ``SerializableObject*`` in an ``any``, is written as a ``null`` to the JSON file.) Finally, the ``any`` can hold two more types: an ``AnyDictionary`` and an ``AnyVector``. For this discussion, consider an ``AnyDictionary`` to be the type ``std::map`` and the type ``AnyVector`` to be the type ``std::vector``. The actual implementation is subtly different, but not to end-users: the API for both these types exactly mirrors the APIs of ``std::vector`` and ``std::map`` respectively. The ``AnyVector`` and ``AnyDictionary`` types are of course the JSON array and object types. C++ Properties ++++++++++++++ In most cases, we expect C++ schemas to hold data as strongly-typed properties. The notable exception is that low in the inheritance hierarchy, a C++ property named ``metadata`` which is of type ``AnyDictionary`` is made available, which allows clients to story data of any type they want. Manipulating such data will be as simple as always, from an untyped language like Python, while in C++/Swift, the typical and necessary querying and casting would need to be written. As we saw above, declaring and handling reading/writing for "atomic" property types (e.g. ``std::string``, ``TimeRange``) is straightforward and requires little effort. Additionally, reading/writing support is automatically provided for the (recursively defined) types ``std::vector

``, ``std::list

`` and ``std::map`` where ``P`` is itself a serializable property type. Accordingly, one is free to declare a property of type ``std::vector>>`` and it will serialize and deserialize properly. However, such a type might be hard to reflect/bind in a Python or Swift bridge. Our current implementation however bridges one-level deep types such as ``std::vector`` or ``std::map`` to Python (and later Swift) quite easily and idiomatically. Finally, one can declare lists and dictionaries for schema objects, in as strongly typed fashion as required. That is, a property might be a list of schema objects of any type, or the property might specify a particular derived class the schema object must satisfy. Again, this is taken care of automatically: :: class DerivedSchema : public SerializableObject { ... private: std::vector _extra_references; // (don't actually do this) }; In this case, the derived schema could choose to store extra media references. The reading/writing code would simply call: :: reader.read("extra_references", &_extra_references) To read the property, and: :: writer.write("extra_references", _extra_references) To write the property. .. Note:: The comment "don't actually do this" will be explained in the next section; the actual type of this property needs to be slightly different. The code for reading/writing the property however is correct. Object Graphs and Serialization +++++++++++++++++++++++++++++++ The current Python implementation assumes that no schema object is referenced more than once, when it comes to serialization and deserialization. Specifically, the object "graph" is assumed to implicitly be a tree, although this is not always enforced. For example, the current Python implementation has this bug: :: clip1 = otio.schema.Clip("clip1") clip2 = otio.schema.Clip("clip2") ext_ref = otio.schema.ExternalReference("/path/someFile.mov") clip1.media_reference = ext_ref clip2.media_reference = ext_ref As written, modifying ``ext_ref`` modifies the external media reference data for both ``clip1`` and ``clip2``. However, if one serializes and then deserializes this data, the serialized data replicates the external references. Thus, upon reading back this object graph, the new clips no longer share the same media reference. The C++ implementation for serialization will not have this limitation. That means that the object structure need no longer be a tree; it doesn't, strictly speaking, even need to be a DAG: :: Clip* clip1 = new Clip(); Clip* clip2 = new Clip(); clip1->metadata()["other_clip"] = clip2; clip2->metadata()["other_clip"] = clip1; This will work just fine: writing/reading or simply cloning ``clip1`` would yield a new ``clip1`` that pointed to a new ``clip2`` and vice versa. .. Note:: This really does work, except that it forms an unbreakable retain cycle in memory that is only broken by manually severing one of the links by removing, for example, the value under "other_clip" in one of the metadata dictionaries. The above example shows what one could (but shouldn't do). More practical examples are that clips could now share media references, or that metadata could contain references to arbitrary schemas for convenience. Most importantly, arbitrary serialization lets us separate the concepts of "I am responsible for reading/writing you" from the "I am your (one and only) parent" from "I am responsible to deleting you when no longer needed." In the current Python implementation, these concepts are not explicitly defined, mostly because of the automatic nature of memory management in Python. In C++, we must be far more explicit though. Memory Management +++++++++++++++++ The final topic we must deal with is memory management. Languages like Python and Swift naturally make use of reference counted class instances. We considered such a route in C++, by requiring that manipulations be done not in terms of ``SerializableObject*`` pointers, but rather using ``std::shared_ptr`` (and the corresponding ``std::weak_ptr``). While some end users would find this a comfortable route, there are others who would not. Additionally (and this is a topic that is very deep, but that we are happy to discuss further) the ``std::shared_ptr<>`` route, coupled with the ``pybind`` binding system (or even with the older ``boost`` Python binding system) wouldn't provide an adequate end-user experience in Python. (And we would expect similar difficulties in Swift.) Consider the following requirements from the perspective of an OTIO user in a Python framework. In Python, a user types: :: clip = otio.schema.Clip() Behind the scenes, in C++, an actual ``Clip`` instance has been created. From the user's perspective, they "own" this clip, and if they immediately type: :: del clip Then they would expect the Python clip object to be destroyed (and the actual C++ ``Clip`` instance to be deleted). Anything less than this is a memory leak. But what if before typing ``del clip`` the Python user puts that clip into a composition? Now neither the Python object corresponding to the clip *nor* the actual C++ ``Clip`` instance can be destroyed while the composition has that clip as a child. The same situation applies if the end user does not create the objects directly from Python. Reading back a JSON file from Python creates all objects in C++ and hands back only the top-most object to Python. Yet that object (and any other objects subsequently exposed and held by Python) must remain undeletable from C++ while the Python interpreter has a reference to those objects. It might seem like shared pointers would fix all this but in fact, they do not. The reason is that there are in reality two objects: the C++ instance, and the reflected object in Python. (While it might be feasible to "auto-create" the reflected Python object whenever it was needed, and really think of having one object, this choice makes it impossible to allow defining new schemas in Python. The same consequence applies to allowing for new schemas to be defined in Swift.) Ensuring a system that does not leak memory, and that also keeps both objects alive as long as either side (C++ or the bridged language) is, simply put, challenging. With all that as a preamble, here is our proposed solution for C++: - A new instance of a schema object is created by a call to ``new``. - All schema objects have protected destructors. Given a raw pointer to a schema object, client code may not directly invoke the ``delete`` operator, but may write: :: Clip* c = new Clip(); ... c->possibly_delete(); // returns true if c was deleted - The OTIO C++ API uses raw pointers exclusively in all its function signatures (e.g. property access functions, property modifier functions, constructors, etc.). - Schema objects prevent premature destruction of schema instances they are interested in by storing them in variables of type ``SerializableObject::Retainer`` where ``T`` is of type ``SerializableObject`` (or derived from it). For example: :: class ExtendedEffect : public Effect { public: ... MediaReference* best() const { return _best; } void set_best(MediaReference* best) { _best = best; } MediaReference* best_or_other() { return _best ? _best : some_other_reference(); } private: Retainer _best; }; In this example, the ``ExtendedEffect`` schema has a property named ``best`` that must be a ``MediaReference``. To indicate that it needs to retain its instance, the schema stores the property not as a raw pointer, but using the ``Retainer`` structure. Nothing special needs to be done for the reading/writing code, and there is automatic two-way conversion between ``Retainer`` and ``MediaReference*`` which keeps the code simple. Even testing if the property is set (as ``best_or_other()`` does) is done as if we were using raw pointers. The implementation of all this works as follow: - Creating a new schema instance starts the instance with an internal count of 0. - Putting a schema instance into a ``Retainer`` object increases the count by 1. - Destroying the retainer object or reassigning a new instance to it decreases the count by 1 of the object if any in the retainer object. If this causes the count to reach zero, the schema instance is destroyed. - The ``possibly_delete()`` member function of ``SerializableObject*`` checks that the count of the instance is zero, and if so deletes the object in question. - An ``any`` instance holding a ``SerializableObject*`` actually holds a ``Retainer``. That is, blind data safely retains schema instances. The only rules that a developer needs to know is: - A new instance of a schema object is created by a call to ``new``. - If your class wants to hold onto something, it needs to store it using a ``Retainer`` type. - If the caller created a schema object (by calling ``new``, or equivalently, by obtaining the instance via a ``deserialize`` call) they are responsible for calling ``possibly_delete()`` when they are done with the instance, or by giving the pointer to someone else to hold. In practice, these rules mean that only the "root" of the object graph needs to be held by a user in C++ to prevent destruction of the entire graph, and that calling ``possibly_delete()`` on the root of the graph will cause deletion of the entire structure (assuming no cyclic references) and/or assuming the root isn't currently sitting in the Python interpreter. We have extensively tested this scheme with Python and written code for all the defined schema instances that exist so far. The code has proven to be lightweight and simple to read and write, with few surprises encountered. The Python experience has been unchanged from the original implementation. Examples -------- Here are some examples that illustrate these rules: :: Track* t = new Track; Clip* c1 = new Clip; c1->possibly_delete(); // c1 is deleted Clip* c2 = new Clip; t->add_child(c2); c2->possibly_delete(); // no effect t->possibly_delete(); // deletes t and c2 Here is an example that would lead to a crash: :: Track* t = new Track; Clip* c1 = new Clip; t->add_child(c1); // t is now responsible for c1 t->remove_child(0); // t destroyed c1 when it was removed std::cout << c1->name(); // To illustrate the above point in a less contrived fashion, consider this incorrect code: :: void remove_at_index(Composition* c, int index) { #if DEBUG Item* item = c->children()[index]; #endif c->remove_child(index); #if DEBUG std::cout << "Debug: removed item named " << item->name(); #endif } This could crash, because the call to ``remove_child()`` might have destroyed ``item``. A correct version of this code would be: :: void remove_at_index(Composition* c, int index) { #if DEBUG SerializableObject::Retainer item = c->children()[index]; #endif c->remove_child(index); #if DEBUG std::cout << "Debug: removed item named " << item.value->name(); #endif } .. Note:: We do not expect the following scenario to arise, but it is certainly possible to write a function which returns a raw pointer back to the user *and* also gives them the responsibility for possibly deleting it: :: Item* remove_and_return_named_item(Composition* c, std::string const& name) { auto& children = c->children(); for (size_t i = 0; i < children.size(); ++i) { if (children[i].value->name() == name) { SerializableObject::Retainer r_item(children[i]); c->remove_child(i); return r_item.take_value(); } } return nullptr; } The raw pointer in a ``Retainer`` object is accessed via the ``value`` member. The call to ``take_value()`` decrements the reference count of the pointed to object but does not delete the instance if the count drops to zero. The pointer is returned to the caller, and the ``Retainer`` instance sets its internal pointer to null. Effectively, this delivers a raw pointer back to the caller, while also giving them the responsibility to try to delete the object if they were the only remaining owner of the object. Error Handling ++++++++++++++ The C++ implementation will not make use of C++ exceptions. A function which can "fail" will indicate this by taking an argument ``ErrorStatus* error_status``. The ``ErrorStatus`` structure has two members: an enum code and a "details" string. In some cases, the details string may give more information than the enum code (e.g. for a missing key the details string would be the missing string) while for other cases, the details string might simply be a translation of the error code string (e.g. "method not implemented"). Here are examples in the proposed API of some "failable" functions: :: class SerializableObject { ... static SerializableObject* from_json_string(std::string const& input, ErrorStatus* error_status); ... SerializableObject* clone(std::string* err_msg = nullptr) const; }; class Composition { ... bool set_children(std::vector const& children, ErrorStatus* error_status); bool insert_child(int index, Composable* child, ErrorStatus* error_status); bool set_child(int index, Composable* child, ErrorStatus* error_status); ... }; The ``Composition`` schema in particular offers multiple failure paths, ranging from invalid indices, to trying to add children which are already parented in another composition. Note that the proposed failure mechanism makes it awkward to allow constructors to "fail" gracefully. Accordingly, a class like ``Composition`` doesn't allow ``children`` to be passed into its constructor, but requires a call to ``set_children()`` after construction. Neither the Python API (nor the Swift API) would be subject to this limitation. The OpenTime and OpenTimelineIO libraries both have their own error definitions. The tables below outline the errors, which python exceptions they raise, and what their semantic meaning is. .. csv-table:: OpenTime Errors :header: "Value", "Python Exception Type", "Meaning" OK, n/a, No Error INVALID_TIMECODE_RATE, ``ValueError``, "Timecode rate isn't a valid SMPTE rate" INVALID_TIMECODE_STRING, ``ValueError``, "String is not properly formatted SMPTE timecode string" TIMECODE_RATE_MISMATCH, ``ValueError``, " Timecode string has a frame number higher than the frame rate" INVALID_TIME_STRING, ``ValueError``, NEGATIVE_VALUE, ``ValueError``, INVALID_RATE_FOR_DROP_FRAME_TIMECODE, ``ValueError``, "Timecode rate isn't valid for SMPTE Drop-Frame Timecode" .. csv-table:: OpenTimelineIO error codes :header: "Value", "Python Exception Type", "Meaning" OK, n/a, No Error NOT_IMPLEMENTED, ``NotImplementedError``, "A feature is known but deliberately unimplemented" UNRESOLVED_OBJECT_REFERENCE, ``ValueError``, "An object reference is unresolved while reading" DUPLICATE_OBJECT_REFERENCE, ``ValueError``, "An object reference is duplicated while reading" MALFORMED_SCHEMA, ``ValueError``, "The Schema string was invalid" JSON_PARSE_ERROR, ``ValueError``, "Malformed JSON encountered when parsing" CHILD_ALREADY_PARENTED, ``ValueError``, "Attempted to add a child to a collection when it's already a member of another collection instance" FILE_OPEN_FAILED, ``ValueError``, "failed to open file for reading" FILE_WRITE_FAILED, ``ValueError``, "failed to open file for writing" SCHEMA_ALREADY_REGISTERED, ``ValueError``, SCHEMA_NOT_REGISTERED, ``ValueError``, SCHEMA_VERSION_UNSUPPORTED, ``UnsupportedSchemaError``, KEY_NOT_FOUND, ``KeyError``, "The key used for a mapping doesn't exist in the collection" ILLEGAL_INDEX, ``IndexError``, "The collection index is out of bounds" TYPE_MISMATCH, ``ValueError``, INTERNAL_ERROR, ``ValueError``, "Internal error (aka this is a bug)" NOT_AN_ITEM, ``ValueError``, NOT_A_CHILD_OF, ``NotAChildError``, NOT_A_CHILD, ``NotAChildError``, NOT_DESCENDED_FROM, ``NotAChildError``, CANNOT_COMPUTE_AVAILABLE_RANGE, ``CannotComputeAvailableRangeError``, INVALID_TIME_RANGE, ``ValueError``, OBJECT_WITHOUT_DURATION, ``ValueError``, CANNOT_TRIM_TRANSITION, ``ValueError``, .. todo: Add a section discussing how to add additional error types. Thread Safety ++++++++++++++ Multiple threads should be able to examine or traverse the same graph of constructed objects safely. If a thread mutates or makes any modifications to objects, then only that single thread may do so safely. Moreover, additional threads could not safely read the objects while the mutation was underway. It is the responsibility of client code to ensure this however. Proposed OTIO C++ Header Files ++++++++++++++++++++++++++++++ `Proposed stripped down OTIO C++ header files `_. Extended Memory Management Discussion ++++++++++++++++++++++++++++++++++++++ There have been a number of questions about the proposed approach which embeds a reference count in ``SerializableObject`` and uses a templated wrapper, ``Retainer<>`` to manipulate the reference count. This raises the obvious question, why not simply used ``std::shared_ptr<>``? If we only had C++ to deal with, that would be an obvious choice; however, wrapping to other languages complicates things. Here is a deeper discussion of the issues involved. What makes this complicated is the following set of rules/constraints: #. If you access a given C++ object X in Python, this creates a Python wrapper object instance P which corresponds to X. As long as the C++ object X remains alive, P must persist. This is true even if it appears that the Python interpreter holds no references to P, because as long as X exists, it could always be given back to Python for manipulation. In particular, it is not acceptable to destroy P, and then regenerate a new instance P2, as if this was the first time X had been exposed to Python. This rule is imperative in a world where we can extend the schema hierarchy by deriving in Python. (It is also useful to allow Python code to add arbitrary dynamic data onto P, in a persistent fashion.) Note that using pybind11 with shared pointers in the standard way does *not* satisfy this rule: the pybind11/shared pointer approach will happily regenerate a new instance P2 for X if Python loses all references to the original P. #. As long as Python holds a reference to P, corresponding to some C++ object X, the C++ object X cannot be deleted, for obvious reasons. #. Say that C++ ``SerializableObject`` B is made a child of A. As long as A retains B, then B cannot be destroyed. The same holds if C++ code outside OTIO chooses to retain particular C++ objects. #. If a C++ object X exists, and (3) does not hold, then if X is deleted, and a Python wrapper instance P corresponding to X exists, then P must be destroyed when X is destroyed. Consider the implications of this rule in conjunction with rule (2). #. If a C++ object X wasn’t ever given out to Python, there will be no corresponding wrapper instance P for that C++ object. Note however that it may be that the C++ object X was created by virtue of a Python wrapper instance P being constructed from Python. Until that C++ object X is passed to C++ in some way, then X will exist only as long as P does. How can we satisfy all these constraints, while ensuring we don't create retain cycles (which might be fixable with Python garbage collection, but also might not)? Here is the solution we came up with; if you have an alternate suggestion, we would be happy to hear it. Our scheme works as follows: - When you create a Python wrapper instance P for a C++ object X, the Python instance P holds within itself a ``Retainer<>`` which holds X. The existence of that retainer bumps the reference count of the C++ object up by 1. - Whenever X's C++ reference count increases past 1, which means there is at least one C++ ``Retainer<>`` object in addition to the one in P, a "keep-alive" reference to P is created and held by X. This ensures that P won’t be destroyed even if the Python interpreter appears to lose all references to P, because we've hidden one away. (Remember, the C++ object X could always be passed back to Python, and we can’t/don’t want to regenerate a new P corresponding to X.) - However, when X's C++ count reference count drops back to one, then we know that P is now the only reason we are keeping X alive. At this point, the keep-alive reference to P is destroyed. That means that if/when Python loses the last reference to P, we can (and should) allow both P and X to be destroyed. Of course, if X's reference count bumps up above 1 before that happens, a new keep-alive reference to P would be created. The tricky part here is the interaction of watching the reference count of C++ objects oscillate from 1 to greater than one, and vice versa. (There is no way of watching the Python reference count change, and even if we could, the performance constraints this would be entail would be likely untenable.) Essentially, we are monitoring changes in whether or not there is a single unique ``Retainer<>`` instance pointing to a given C++ object, or multiple such retainers. We’ve verified with some extremely processor intensive multi-threading/multi-core tests that our coding of the mutation of the C++ reference count, coupled with creating/destroying the Python keep-alive references (when necessary) is: leak free, thread-safe, and deadlock free (the last being tricky, since there is both a mutex in the C++ object X protecting the reference count and Python keep-alive callback mechanism, as well as a GIL lock to contend with whenever we actually manipulate Python references). Our reasons for not considering ``std::shared_ptr`` as an implementation mechanism are two-fold. First, we wanted to keep the C++ API simple, and we have opted for raw C++ pointers in most API functions, with ``Retainer<>`` objects only as members of structures/classes where we need to indicate ownership of an object. However, even if the community opted to use a smart-pointer approach for the OTIO API, ``std::shared_ptr`` wouldn't work (as far as we know), because there is no facility in it that would let us catch/monitor transitions between reference count values of one, and greater than one. We hope this answers questions about why we have chosen our particular implementation. This is the only solution we have found that satisfies all the constraints we listed above, and should work with Swift as well. We are very happy though to hear ideas for different ways to do all of this. opentimelineio-0.18.1/docs/make.bat0000664000175000017500000001647415110656141014760 0ustar meme@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-n -j8 -d %BUILDDIR%/doctrees %SPHINXOPTS% source set I18NSPHINXOPTS=%SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. epub3 to make an epub3 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled echo. dummy to check syntax errors of document sources goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OpenTimelineIO.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OpenTimelineIO.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "epub3" ( %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) if "%1" == "dummy" ( %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy if errorlevel 1 exit /b 1 echo. echo.Build finished. Dummy builder generates no files. goto end ) :end opentimelineio-0.18.1/docs/index.rst0000664000175000017500000000464415110656141015210 0ustar memeWelcome to OpenTimelineIO's documentation! ================================================== Overview -------- OpenTimelineIO (OTIO) is an API and interchange format for editorial cut information. You can think of it as a modern Edit Decision List (EDL) that also includes an API for reading, writing, and manipulating editorial data. It also includes a plugin system for translating to/from existing editorial formats as well as a plugin system for linking to proprietary media storage schemas. OTIO supports clips, timing, tracks, transitions, markers, metadata, etc. but not embedded video or audio. Video and audio media are referenced externally. We encourage 3rd party vendors, animation studios and visual effects studios to work together as a community to provide adaptors for each video editing tool and pipeline. Links --------- `OpenTimelineIO Home Page `_ `OpenTimelineIO Discussion Group `_ Quick Start ------------ .. toctree:: :maxdepth: 2 :caption: Quick Start tutorials/quickstart tutorials/otio-env-variables tutorials/otiotool Tutorials ------------ .. toctree:: :maxdepth: 2 :caption: Tutorials tutorials/adapters tutorials/architecture tutorials/contributing tutorials/feature-matrix tutorials/otio-timeline-structure tutorials/time-ranges tutorials/otio-filebundles tutorials/write-an-adapter tutorials/write-a-media-linker tutorials/write-a-hookscript tutorials/write-a-schemadef tutorials/spatial-coordinates tutorials/developing-a-new-schema tutorials/versioning-schemas Use Cases ------------ .. toctree:: :maxdepth: 2 :caption: Use Cases use-cases/animation-shot-frame-ranges use-cases/conform-new-renders-into-cut use-cases/shots-added-removed-from-cut API References -------------- .. toctree:: :maxdepth: 3 :caption: API References python_reference cxx/bridges.md cxx/cxx.md cxx/older.md Schema Reference ---------------- .. toctree:: :maxdepth: 2 :caption: Schema Reference tutorials/otio-file-format-specification tutorials/otio-serialized-schema tutorials/otio-serialized-schema-only-fields Autogenerated Plugin Reference ------------------------------ .. toctree:: :maxdepth: 2 :caption: Plugins Reference tutorials/otio-plugins.md Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` opentimelineio-0.18.1/docs/_static/0000775000175000017500000000000015110656141014765 5ustar memeopentimelineio-0.18.1/docs/_static/spatial_coords_example4.svg0000664000175000017500000000452715110656141022323 0ustar meme (X=0.0, Y=0.0) X Y min = (4.0, 2.25) max = (8.0, 4.5) (Width=4.0) (Height=2.25) opentimelineio-0.18.1/docs/_static/spatial_coords_example2.svg0000664000175000017500000000416015110656141022312 0ustar meme (X=0.0, Y=0.0) X Y min = (-8.0, -4.5) max = (8.0, 4.5) (Width=16.0) (Height=9.0) opentimelineio-0.18.1/docs/_static/transitions.png0000664000175000017500000070713115110656141020061 0ustar memePNG  IHDR Y2 iCCPICC ProfileHTS@{鍖)7һ%Ƅ"**(*"(: X b>Ae,Pęyw=]<RP+.z0bb h L6#DfǿoMU<1()D/s Pو]wep L! "|jf'i0OlQ/IBpIfs.Dx^z)nA(/q3AN^ ثߒ&]CQr/ -uy ANOs/r9bϸY沽dsӂg9Ò`E̲hy,>O>l$uy,Y̬YGͲ85<໏.rN.˾,Frl1l# ۇĎp8U)c2py !G< F| ?AP  .a5pJN"LDgb1XJl ^">"%H:$R(O!HJd'y1YBF!R(%AF\<|ʙ˱rnʽ'˻/ϒ/?)]~T`VXPpF_a\hXXxUqX dUU:tQiR=&a% K3h)1Z/mLYIyr*rsR:n@gEs4:a9T檸TUU|VezPmV}V3Q U[_\\sO}Q?ޣ>!ثqQcT香KUEK 2Ý(et2ƴյ%{'t u"u64<%2uuwvi-֫{Og'``hmŠ`PŐeeXoȈbj¨1֘ij lbklRnr633훇0O0j^,ӬlnhѼE.oi-Z)Y[mjzcmbͱ.mCYobz|Rmnjgo'k׳gҘ!Bz3O8dT4po:lRK+۵׭{QW"<=zy|z#˼$mgi8Z֘Zr@x@Y@@Q`Bx  A0+xgÐ!bCCBCYeuS×ׅ(xi)숒ZU!+8Zc6;V-_hŶ]bdՒKՖ-=L~{xL|t|]v0=JHxrp^rݸ#=>@I*\|>0O(]b1QZ [2hH#1l ddg~\*UU=MVo]<'555ֺ=ZcC99G67nu6Eoj컹>O.O׿iK?nٺw|n˂/k?ZXmEvEc HbqV΅;v1vz{%K*HHKK[ݾKYrٝr qﶿR{}6UT 2393 1166 Q@IDATx\'@z)6MXMtIRZ :p1K)z-G3&6f+5-weUXJ Ʉ8_9Ap<78?z|x~}i<@@@@@@@@@W          ]$+^         |p @@@@@@@@ Ɋ         C$+8B@@@@@@@@H5        qS GZb L@@@@@@@@ tM .ث/Gg9 `$       ThU__bHj [ ,,̽       t:_sU\`n:-PUUMP?       @ !!VW) !        ]T$.:t@@@@@@@@ ?'J!        @ ɪ`BGGA@@@ d."    FGeRQF1#V;sB~p   hFj4     UV ֫g2+.6uO:Ảo_u7f?{X8c[f~cn1X;V8Gקe]a+] ڣ3z>ӹ_Ր7()>ֳ_{vJձw^5kqlw>׫K5o;5%yX V4zl_j #VBO,LjM䍛a@@@$x     @pڰ}9=<ƬSOlӶ`zRB;ժFe7؂!:KO87d+ӷQfCvݫOzOƇGG+W6m{urH>^*:.ݛ>Z!ח.Rj~e.ZO-EڲފjFﭻ&ߨ!ܭ_n!?A u{ ۹tr+zVϺs3z\%Ol։Hy*憻5k\BgeSURj ӝhDq J%k6o9B?[Ҹw^u+RiSҧVozaڸiĈ+0n fg.񹪃zm ݪfݫщAxb6S4W]Eيou{Iߺ/G8۽ˣ}yxptȎG F}By ɖƋ{4Cu{MEn%e@BVF%9Zųe;(2Y~=D+O{wb#Q&ɪGG*=>eh\L;+o Y yo5jC="(+۩+7/x~AT;Up_^HWfA5@?+Wz *)vtAo_**TX;י;08n,p-=A 2π?{Cg*{$ptc,!%m8Xq}ACѬUh =ʫL[k6f FO5^[\kn'o@Mzy&YyQh`7 _ /`1{Ƌ{3&Z5^{8\K=v|}yσ/ĕ @ Լ{Gt=!UUUЮ nz[g S㯨 *MsL}9"3jB^9ުmO&ǖzVp=*Iyv=+kpDJ^.1ݘeNjQG9=~E!:ؔ9ã}^_9kNkeR;V%!zuNAڻwu; w6#om׼4[㝃۴`Q,O/v,`=#jVloJ^m(-:h-5ڔg&XM}Z*TUyHrw:ω;no$˵g0fxyc֮U°i"qϣ HG&&rϣ Hw/ypP|4xqa@O3^\1??W#`CoSxlҗ,lt {eq}՛z,vs)8rmWkW%w:1-".=ee_>Q-݀G0Nyp#Fʌj-<k~G[~l -!~O͜Յ<]:j/I*y.YQOXa`F2Ai5O\_ @LszEQ wW##pwIodgNWk q5Vwx!u+7cxs~:T#FZsT/`.Kx͕#ȘԜ^M8P{NqFu P@vZ[meo6Y3V#3gglq[.1w6"iѼuN%-=O}=o3^ Ƌ{5>MgTqϣIUyp .EBE=#IDQ*!#/Ml:xz:xj>R 4Is/y c49nGf%1:t}Zjm٢2 6} 74žlŚ+>}^~wͼ\ lSnAe:F X=-8#_lL^=r8#PYK=a4{I {bzXo!̞mɞӬ!1=_irAmq WFZq#^ͮqhShgǫIkrG@h <"- <:`H|6xqç[GliQ#ݖƫUh*]둨@lYq@v|}>dO`'rsݰ/^s%X*٩7"k(;W4T-V/?۫Q8z~=*|g̶͕iL"NUM_OՏZ]up.=x<"qa++?󊍀p-A4@对lxUo?ENm%\xB^Z5:^ws[+^ByHy\$\/G/)y\^/G/=y\_^=oM]IVyPTӛdթGg8 IP f敨({3uܐrxzŖ!=l\$'UꎅF"5Zn[xD;馦2a݇h,Z=^iK~AK2uX43o5"1^}59Z3~+S_Nʳ<nۗ9hgdFbCOfBJZTYY)ͶI[i?ۦxՓW4vXFq*cq-Nq5p9\H{ h)y$ǹ=,yxw Quyd*QDP\}􏦭 /km_y ~DPmQaE#Hvm~Uָ YyFR2#ni'VW{lkYq# \n/RLϛ߃\}xJ9yx {^Y Bu{$Yy A@O4AX"wF[i.m6{ME|Wx)6ыͳ$}6nbz Wē[Th}_~&"tv *ci*cYGׇepbL2c =3'_&&-~s9 L`]aY?}]|W]s3NDŽ[DGp(0f *\׮6g a@(/?OdZu1"9AF$HȄ̥+V״O95:D2 yˀ{2RfGhQ= {J1q-O ?٣0zx; eZ52thz+w:.KGȼ2C{n GPT$$ *^Ċ@d~vDFNwd~HIt{kK~vH=A%ͼimq7 O+<|b#~/;"Z^\Ҟ.~G+6x\o6Zܧu$}TY IFVڹC2=E|/5ym+=mښoѦm^YZ~bc;G]weM6u}aBkh;xYϩ3#cc;] \gz>6^W=FNKz|f_,8uވn\=u?{~2_,Z_c|hcBu}M W=~3 ŗSkbfZex& {]a:]vtrBGWtϣKWw5u <\_iT!#l \@d'L*.E.&]qh{_moڮ5G*6eV+#zFB7v.WGQk['!@ 3Y!ac ht+"H         @' ɪ $@@@@@@@@@ ɪ}\@@@@@@@@:IVd         @ؗƣ}V@u@@@@@@@h&+qqh.@Us        Yu+$Y$ ` dgg+""@@@@@@@d| $        tuf        >HI@@@@@@@@$YuWG@@@@@@@@$Y$        tu+#        O|p@@@@@@@@IV]@@@@@@@@@@IV>y8        ]]$          S YN"@݉r=ҟtό*mֶ?ӷаm_z7??n=b"N%N5n %ń\T]U't/ԭ{% NVmz ="F@@@@@@d@'Ruڮի?IWvv^[EW]\wܱNךmmxAvtNuGUj}Cի@?^&M+BsוS=       *$YH't>HOq9"{UDEO{j 6n6+<TXUvl+%C9JqTM       /@U"^Hs-fE:{ 2ce=SpӖ]jٲUu-udZ<; (       ~;yJED @ XO??UK5C5!mZRc'h:\yҮ{@}+e%xOa&VW_WIX%&ԤF[Sfo>RtQZ5:UF?|KaFwU1} q{*k߾SFLrVG޶#Ho} c&iddu[ hUJwKF s6|ww1|O:'%8J6ڷǢnbhծgO} IvEI*i2G(im<2f6]v{nJ!<i $9f5"=9łTwQՅZ)Y6j&       @[+"Eܡ{f&+Y㟴%t'yjߺ56)Î#ex<2{HHk>G+yVC9z*%P‡}S7Vбh"ruF,Lidj1ڒwjF=Ƕil.U'Jm?~mz߾mԬ י7|9HU >bm;5ֈH1fmj +9Gkg}E99wZ6ѝt\oYn&W=ɹzcTW2ձmm6-cfa-YIQ+8[db?<}kW0l       X;5@=(*gFm^FcOEcE ViZT%klt$ݳ󜥅6RdlR gNJڒRm/-R{2ZJ|>CD.[;~*Fa<,zZ^99Hqt0%C9ZtQʪ VKתtv]*G5*H_JGi4;͕`d|{6\8*\cvԖ`ڈTΖGU6jH{kY+3*YkmM3 [g⏈ڮkp¸5ڨ՟i%[i_*YƜxF@@@@@@3YuQ .1푬ᄀI?'NըiVIZrl\|5շ,Ҭ*kNI۬h; QE~m1S}px{N@ WS}zqj?yϑTjcf3}djȣztU*OE-J-;lZ?:8|u3jmqmL4&ZJl6RF-' ?#N%c=;טxjk}V =8,t ~=GJS[W4mp&:8. cRe>GyVC w=Wgٯz3f0S֤OD@@@@@@3YuѦ ЎڷJ]Jr68fk}oϵs񸍿J2\c@sRkؓSNrT/uJ;ES&WFc6Õ`e1[:DZl3Q5qRuzb { ۧzfㄟk5o6nܮLs&XR~uZ~`bZKs7ۑ`e/OfSDJ2O s5_?'XYVJ}Cc{&iԉ!}QGW_N'@@@`8}l<@@@@ ɪ#i:3O(fwBQic5ױ%撗DOEoa k\ұ2- gE_jhXz߬|cYzD%ݪΥK5iϗIB3-+iT}lZsSСƛ\r^ԧIefl=K;tr{X)""^c]KP֜s^s>>f&ե KgN"  ̔    M~!& 9˫ ; 8f6mIxW;B.smXviF\0Yisnֱr%x\tQ;Ȯ3y/ J .KilE nPNjNǾ$됱"3I9XyIQkV, KY\`Vu~q@@@Bg?\ @@@@v`&v@JΙk`=ÞM8X׉#GG}Y /mg>秮rVclgU _j򰶮[:fr]q=5r\ںK whWK&-S~E֕`[(ˇ'uAwmպ¢_ Pb#|M~/:o8k>{Μ9eS}%y 0FL5E+=W5cڵۼ>U1@@@@@@@K dե"@ D(YUդ{UX݈˜K?T6=S6h<{\MD+9ۑu]ΤNK1Q24<W5bZ7;]GTBH͜JgZS44i!=딱|}OCz+*܌l.:Fm4iݶキER%T(E?b G/5Y/Vٔ[?ȼ"onvv8_eC@@@@@@IV]i+@֬ߒsQeujUk&_rBsr!_5;* J"*F9H^ibYIFUd*|G᱄*/[ \gJo`-S'i#rSڽm]Ω;VWUm3 nm;L!YαnφVyR.p-@pbEg/0wl=e߹N5ώ<3Hnj|L`U˜IJ__֩1Uu}㕓r^O       @ ɪK?GhR3mTC=t,9EF 1$"W1vG⋔=i"asd!y{ٿGI6UX-Dsْ"tc-K,GiС_eRo}GG˹0Ih+,Җk,\-CZVx䎶T7#ֻ6}:.֩icMbr٬JO{|cQ%9ږJiӴicUEYTIY+Rt>l       ]I$4Y J%Kjy1ҴtVg㮖XwX&jɷ|#Hq&h%j嚑#F om4>϶:|8y%"2)/MVX⎶ZV,5^W-*]^wsnye]ng݆5wXO96@@@@@@@,h3c)CiHB}c(cJ ־g4o䅓Wuժ>s>UtLq̀#`5էZk@IDATj,cXm,^ ΜoPL~94:eue4cxaUmuj"cԻ#̨3jQZBhI Ƥdӻg+}6Fgu{S   @nV{{ tӴ   $+^ @ Tnkӟ2jK53N;~6Y3Ν٪1αjڪZ=32Ko ħg=0ӿtZB@@E$vaR@@@Sgq!$0zSƒv}td9VKYL8Fc~Xթyu5GDKLAs<*~J֮ѣ TDcN2       ]Uoh#5/#ݕTո{?w+5>6   \@.i@@@hWfjW^*G&s ={AkNקO)2}]@@@@@@@@:H/@ ճ_#"@@@@݉r=ҟtόnP}C@@@mҶQ     q;iZpI2rh@@@.E$KZ@@@@**((j:>H3H@    p)$Y]"    @P \cGdDPE0    Uh#    !P]YO|>n8V_R7jdR*sWa˥/h(͟(%:ME+22V} c&idbO{?UWkwOf̊T$5ECI^z>qd*N5HWQ~ֈanV5_;*![9   @5{-JcYk`   pVi @@@@?N1STlZ_J3Kyh˴?n}Z=d·+ͫtKGgϫtkrȘJgNψ%%+#gSG&4J=@@@h    @l&ig&RSE4HnRֱ6LM^EcPǹ~{viR\g,**XUD㪲N    *4    )TwȜf*%PO|CڪuEjvTu.=3qDzI}廚85|*FT|9Y3s|B2ٶez1ϗ.Rr8'9hHQ攁f>#   @H 89 "    @{ 7W9)馌FĚ?EƘ-F6i8|m\WX,$g[Z}}n$M μ݆1Q,cF}x~wYogaZwc@~_-Kr#pa&jJYg@@@@ؗ#`     pz={\\bUmuj7(":F{:u:u"bԯ㘣j=oߋG=& VoވhE(G2    ]m~^7G@@@@@WLGpãԻ_&(pfx-ڪzm<@@@@ xX.0xƂH@@@@@@@@@ H A!$@@@@@@@@ u*|r9CI1D~JH&ݹ;Ul=8I:zyV#uejUsT'N>4(ZggST4jn;NTXTI{=bu63       3YD@_4|Q% *U;~^>)WVv&eUaFNr2lSDɳҋ{=anz&l>#mNrs2sz){.6@@@@@@@]HjV*E*pY_H"^X4Ξ9yÃum8/a_ꡳZAg;SJ3lYvlVVY׎7U˴]txrlT]kk}44mwť?%%ݳںnzV ʙjK tq@@@@@@UP @`,'ub;noȤU-]]K_Q}EI 7RbOڷǢnݢV*~VDWo_tb,G5죤qiDcoz\е:ovѤ"cM):kh|~RFtsMMΙ\߰n{oѰheP[Q7@&+=qD|HoTot>YNu3c _ъR!2=ƬKQ')6Yg-zȱJWk}9J~uBJƔRcV*<쪹5S:ԚZNjN/ĩ9*L[RiKeM~6k漎ffǽLJ6S?{7F˷hVr\^@@@@@@@oߐz;1@0GL{yKAˤA6g-i*}.嘙'E,T9[3zddOվYc'K+n2OVr\Y$n[&\ڪ{Nkm7ˑca[7M5f`260GH<ЊǶiᒶT(1R$+H|%ϲ{{dO4 <9#C٨fTWC\;IFQG1g_1~^Vͯ$ޭ$WMVǗʪ:5՟kUtgeNkFsZ[W)qӌyJd9Zn&F3>xW) 24f|m}׋ji?A"w$hFHB5      .u#@t*-QF#(%C9ZtݠS\ VRJZb\alO*^s+RHJzPsZɵZ[[c>dݨ7*7lԒ?sČk`5gyQTks݊ͅY­Oiv+JɺZٝ`%^ZN3h*] :+#im^^bQY*>RkуrV_+[AHGi2+FG㶧p]7,վ_%,YT=-ax*N~qc82b-{]>W$ IJRRR}CMS̊1qxmלk^䧎\W      sΌvJ@"GWTۢѲCw$82oךe,W>2гsG%&ޤ?{Wu}IDHjjɺȰ]ˮZZ`6ѮhbZ&V~-edf5KM+nbJk<6c3j!!cBk>|p@pysw5h~ޏkScFT}.%msٶ4t$:ƌ_w=1W6fM S+JO'gϚގ;sf*]VzP]3M߹"'.ڙtJdKh5+++8Y)g&[NhE{V25/M|R[6:[Vt_*ϳُU7Z꺉Ꝓ2,-ֆ'G+){@@@@@@@Fj @K ,zzb0-A|<μk<}^^2*_tR;֙}rS]>7thYսž^'?7%OѰ^NN   @{փ>.v͖Ɩ @@@hmG)ZdG:?dUjBo*n W0OmngjNjL_A7B|֒ 3VlLfLPw-~7I|Y954T}U=3<۴AURijƦxAA}5֜'湔$׳_Q;[dj5{T)ZiNgߧR%?T+ atz'vN~W>iAר]܇odckubM@b<+5bY9: JՠvRz=4ؽl߅s6|c.=ĉ{j2W:(Ln)K#Ώ   :vz7Tvv͖ @@@h B2@0KsR̀$[:ytĝ8sD+&3lεTν:%d ɹ|4fi-qi.ֻ\̴gZ̴S)?oY?urgG ?#QEy:{+gJ+EMʒ\iN{*܌RLMMnc==RgR> Zr0҇gln ]4fߚ_D>qBXҲ6ĖG1vozyמ;8uʶc̠js//9u`x  t$}{xb&ٮ!   m%f Hh)@ 0;o VLY]0FDwWn j,Wa,xBbTȚ3:kT:WŚNjΨȟqMk_*"eT3ٗL\Y;3VD ^Z]FLCF uiܿH'.>sIAD5U5rBFSTUly#e۟uiui*wnVFUէg=Y޷q  !,+!!Nm&2]{!:   $@U(uE:@MEE2t=`Ԕn~kzi߉- ћώ؅kgioU`әgk<]X-m-[>̥%)ݢ~FŲɅJ@j  m"0j(?i/.ӝwީݻwI   u'@DŏPު*wWO=jUh̗-a$c5Ʋx94覅X51egL-OY5zc/5ӥ971tl-ЅL`e@@hmYB׭`+ tq   m$LVmM1 +PqtfOMwUydΪ}ʙ1BbMI1:#O?m,?ӳ(@@@N)pyS̙34@@@#@Up)@ 8yBU RۂBTLF[쥆m0F@@x~6+RoZ.    !@Uk'    {ァoݞ߻ᆱn&#@@@@ Q"     U 11^~iiiPA    9Zp H@@@@@l!   `&`S.    ,^{CB@@@@j)IA@@@@@@@@)pYlB@@@@@@@@ZH !@C ? /C!       t颹s'?IMgzi"## rs       @Z&  s       Nŋ _"       @H $$$TJE"@@@@@@@@@ dI;f#        @`YD*@@@@@@@@YuҎ         AV9 @@@@@@@@:AVi6        &X2R!    m'P}T?T?VZG=o7w\dUJ֪jmiD@@@ d !    y>ڜAa4ӵϋwo҂"[Fk۱Wts(6}R+@@@ZE Va%S@@@@h@MKgJ@@@@@]D4     zgtc7\HY:JWU`5oGo֯l-镏JNR,oy,+X]M?}N ֹ%ކ)kmfޝ}uT(}:ߝIz'J $ZqsM"   @sj     @ DFs)y5l^}eIS/>rD`W&;`02a׌XvB!VzyC+W8ubOu:V1x!   \g    ^ &a^G}v^+;!MWB~TeRdRik>+@*|N}|}8lnK?.}AR]. OQ҂3X%RyJ]({    ЖoP     Wěf{ddR?:Rvz~pnpgx"Uzq}vʎ TRlQr>.*r`=T~uwWں;F{V-n=uVu8   @0ZR4    tJȘ8%$lK%:MCbkZܴD 5gpuu$U-eGy#,=E%3@@@heͿ;@@@@@KUޝ߹S;󵷤 #{滧coiGʢCGTqU{POUYY솳q\gU@M4lF{: =e8.   SO    tz-~\َ鎤$黋')wV3mU8SXv&MՐuJ5ɸQvWG;pHMX˦Gh%|ܑs/u)(]@@@3YǝR@@@@@.oLrT&HMOexi*ܞoN '7J WY)U{ouM79sUR'ht`e+a򲆹/X}   M S0    c=j*ZmY2Vx1շ={ԅ+啞Wy\)={Vidڐ)r5ǔJuyZXF>ƶ.دSVܬOr)sJK+UnJ}\ZN^!iG}\j.!   @k ]4.@P s5̵   @= (I+~ACzx/ X?ǘ'lY0fάxn# 3*rDeOvTܡT嘕wP3v\֦n9hm;nX1ў|ܓuMҎc47]}r@@@HHHpk(\L     @"53V]g @@@@ ɪ ) BWB9   @h T*-O˜w*O ZnLz0*USc,+SLWF9G,җlE^_!Qq[my9o\Pޞ݉TrO:uΞ(*+ʙg@@@hGdEU;4W 7 @@@@@@@ dr>@@@@@@@@Yun         \+}         )L#@@@@@@@@@Y5W@@@@@@@@@Sd)F"        @sj![\l!@F?9kI9%B ~r֒rJ3%g+4YK)WhS"4'g-/Dh<_OZ_NxBLF?5Y5U ^`۶m=BC ~r֒rJ3%g+4YK)WhS"4'g-/Dh<_OZ_NxBLF?9kI9%B ~jj- j@@@@@@@@@S dթ"        @Sj@@@@@@@@@S dթ"        @Sj@@@@@@@@@S wX@jMZeZw_WG*vۉTYʼ#Tjk֍ZPdo^WÍnV8,wj/"ݩ{OzGJ@A۔3@"][ XO(o׻bM#jB9 XOi/_BG5ix4 Ֆ)oն-Gѵj ^6E4.P^铆o0ʼnÿզ7v_RcχStTB#E5quA\}և7ݩ<ቌt4":/WsNk g)uڿ1SߧhM1qzF˫|<8rP1.iІv#c 16Cu1ߏM$- @ T*oNnn@QV,)Zy/[F- F#3M *{ ,KI4u^5(**кj;_P{ᴫr.AiY+vA8KTRY I߻JJTTz%c@+Gўvr{GrbSZeyWׯUzzx_(Hl H6?Fܨ;z3*Kt=sҫH[h S7N5_[I{mjN;F1J=h oh4_ޕa#8G c铆Jm{c q1Vo~ }>T`濜YÿvXMڷUVVW5֢|YGMu: 1 ۫0k[XU\~10գ|/.n5l kb{* X0bnJ׾}Զ4߅fiM]yx}+i6ﳨq=5H]Tsm,`=`5L/loRY@IDAT>0Qz[Uu8@`?Wl3jtZXfG:bmښ{h֬+y;>vnZU˳0x-ƥ <|@p1 "/[i@16 hM/_yi4^X;AVHt0ZFMOm~Fw'7#W^|N2X-v(;;Gş%)))ȕ*ǷmS$f*m&p0N?~u?~@>$imVȬgqG}97u{v/Û41{MlU;#UCndv<銏OLZx]ոsW&\Z,{yX)Dsfn q0?>WNXO޶:԰*x{?%(}MyJG +#ks4>fm.Cw/e>䨰@V@ӵE;}wl,VҰh@7y2Hy֖1OhWb@Ř? J[h@:w. U6Lq_l5Wl a @Ҕe*3dngA*CDaG_;?0K"ɺ<Uo׽lcels2fkM}|J?=Yn_TևG_TqI߹WC#P1{餇G*asy(M§[jrBK?jZL%0)箼6ެ{oA;R_%^{UHdbjSuuh+S?}Q=>&lleظo,['3h l4][Թӗ9@ˌj=vl1c9 }ØGp_><\ VH1@nk҆g|(u1HQ @{bSu 7 RO; #uG2ᓶt~mN*J0dxڲZVoݪ"1kx0E ?o_bžYDxbPՇf܀r.uU@+wP3vg'V}eea5Y`Lvm~tL+$M{UiĵILwzx:Yg!m6kJR E ۵rO~(gXOVGwbZGޟWusOtG\D+ _>6T167y$Y<% Yo1Ѡ[._yG.1_yY=:P/?eal+$#\BI;vT1RG> Z;.狢['x{ e.E\0W嫬R'JfTgz @y4 oc̣ /(<.oẹ/<<./73\I<jʋ @Tw&>@%Z5'A+19;O3`uI%߭lA kt9TP/X.T랹F Zn["t奈f @@ru/<&^i YJӻK2/25$1^5.gZ1~+R]i?'aO357sжU}̈́4MYTZZ*i8U xзnk}aFR{)?Nth/8e1W\Cj6V^=qPW(mJ23353#MI=ԛ+L-}Da )1+IS~} rEL^yxqcU0kcBS\7G}_ 5Vlm{U-멽Zz8_sg}Tq;5ѷ' +c`Dolڻj.u%q ɵQs==AU?\[ &V6 4S 2ͥKjS9iH\3y C1Py0*=e֓1/NKɳ4[]|6kGZ_A4X}뇋`mNַ&;Kرߢ4lUmꞛ5߿l?$'噣6]q ?Wm>-]g6H%r+yk1*wEp[=x[@-+͘Ghn =-\Qg3 ŗSSL5E+i;C& 1_W\ :C_?#ytj/GAϩ3WGHHHpf 1]c@";`P%pp+t20l Z޴Us T\Yժ`d@"cn>v43>qײ|OkYOrCg:*B|WALVzF\7uڒ]z}q}J[Xjo#}N^q#]eƖ<=O﮾1M?L{}~s,,Ni`v11wQFzW?k3kE%}4Ӷ^`ube\.(A _eCY,5X#ρ)yE VKvy'vk1QEY~0&_9      t@fꀝJ@ ъ5'Ҟӵ`Û*=SeѨy:'Tul+Hf+]Vf{jxdΖǝS%5M,/VU;MV9'I.ZDuQ1^2#R=mZ2J}iG7jf1m^2D 'kӦ]Dgd@ µܙg;s=b2^Wۗ`帿=9 yRHμK᮶['UYڸmcp%1cLj$lX|z'+ˑgTm/o.*] S+G =o'\xpjrl@@@@@@@#@UJҝKU('cꪈAzUW)!1ȹ]"ݖg >43?h~ށS>&w=ь4TjռgH)X|)Hc>sq5i}f|*1},oւ)u EDw-SY6L2^)G̛\Ͷ<^7{g#Ѩ^ s7,.t\vipP/2^ r여)sf=%:Y@ڰ      QC>t}Zhc%[-ڸl҆SD(R8:-t/u|o=XN J(_V#)b|+Q'*W2S9k6jŢ4Mjq{Xr/-ju_f8'3/uIk>GޞL-|TכKthAeUi˹5yO}-<       ЎjGAU@ bGh3/=ߵIsRoi93 ̓1îwD֙F*upε罓K)P6lЛ'靡Ks? uy?ٟϝ;gk~5vTs5?~SJzY>=.H6^1Scy6r}O>?4]fg:qmygQ7LlZ::?5ۉ')uDf~ R}1       jo=B}@ 45XF.,,UU[:f^vNSfUQOi)|U4g{r֔Xkd{|I΋/75}٣彣(FE)UTǚ۵who|c,ŏh`BO3h % ys!       j}B@ b-=J\gPgQ#V&k٭ӊ7K:VؠF-4ށfDs1UURaX+JⱩE 8Vݛ?1m9 +(#8'\){¬ =vq՜Ԗ2ߠ{Pm(+p̦%C@c(B3j#-g~TU55:S3ƺlme'}5g]?YEOd::sna g/1*V-ߕe+o[8LZaiɬUe-/g8jrI@@@@@@@ d!@P(9fܫ #Wr)c|#MKe0[]74C{.iߏULGЍwF0 Et̍fj7w$Wzr/FJN8G:J]2WQn E+=njJ2'jpu\={{qKf2E5 ܡo~4mܢ#ktꯙk< :d4hyc6|%DK>S{be O{#={>;λ%S݋!|=tNm9#~K}g<       `Js@ xI/qO"n;iYiR{>Ӈd37ݗ䞇(aB^>8-ئ.׾ҜǔVƬQ:N]I_TQߴldqN3f[h#]yhӧr%OUtvv6 O³-ʑ(m*2!` t>-2c9^f̌\\Z,zzd>Dz<=3 W3:~9_2;W=nl8F@@@@@@:@EcmY @j*TUu^VTlb\KudP ERӪJgViiȷNA>'lu hgJE[*puS%SllujXvZ5G>_*~1:F8Zhy        d)@TiĮ0Cs*9)||l-Ռ~2¨| )ݮqK,y_nmr܀       бK@V(}gv.W>%Вs-RU nѨrYS*Zl;[X՛'@@@@@@@:AVi' *J_ Is0z       .jB@ZKZUOR\=]V       YnQs@@@@@@@@hڠ @@@@@@@@@BV :*        m!@U[(S    \@yyl6@@@@ a-S&      Io-C@@@ZL r"#@@@@@Ν;J9-    и6nD @@@@@@@@YuΧ         иAV@@@@@@@@:AVi:        4.@UF@@@@@@@@@N,@U'|         dո)@@@@@@@@@ dՉ;#        @Y5nD @@@@@@@@YuΧ         иAV@@@@:@ErWPnaI-с-zT9;mЁ5Γ >ל7YӧԍdmK%hlMטu T!효Q+**_Wxd2 ïarF"   YEz@@@@h@\_bϘ?N'jKGd[2j<^7 ;o9~WǓ5Sj<ݨd3p   @y J:J7@@@@@?~wHէK: 󌥴_J0fcc7D=Rd g_o&k    *4Ԋ    %`ֻxK+) jSoJ e~V c IVU{UҮngV?e[mo^Yt-׹F;룓麬J륿Ԗ V8C{um)Ef(e *vrh;8(Y+7녗JOԭYW .VhA$    +pـ8=C@@@@ݾcTkSuMOuq@tiu1;6N<#klu}ExsVHl}ȗΛa~#7^žm3J/ջ 9>l_Fn*֬ǸV'O2Ow'\}P1N2E3>=z藕   D9Q:    [~<[MooҲyU22rx*^z:Rc+`5yVS֦MZoֿfYn]VRfvRb43*CeU~UQ_j}fU-+ۛ$+&h7lA&eEE]+5|$B@@@ X*F     Љ*-{ v85Zً-Ρ)p^]f\︓/MTse,/`==2{+zڵj{oZpͿ(@jb:d[g>|?}wpt)8uJ'O8cNif   AVm%I9     RvxKG|[r ϙɓhkM{DYũGoUU[qg}^7ua?ckAX\cGb?սXV<"   duG@@@@0H-3۱q98bӓf2iDzk efB\UufZ/N x4LwfLUHi饺wG>N@@@@j/YE@@@@yZkVenz%(+k{eq"R3ߣ>hs>Ϭ6+PUKiРAWWTxfu|YVl/FYF_bSSŞ,wòG|F@@@AV. N@@@@@ ޗiӒnlnǥOz_z_%;.?~ܒP t/A2C y*-vQg wev,g_%*ށĮI>Hi[lC@@@ 1#R%     #in\kjkT<.QzvSngĨѨ?.zv;%=y~NJk؆i_}V5^6   D@+D"mF@@@@MP}Z(t=6i6Gll&ڦӔ   @X ]`X A@@@@B'Pg9eҠAhF0V c+@@@Ml ),     Љl} ZQƕϖD}ЙI   @ Ĝ6o% D@@@@N-0b{nکڻֺ:$19YA^uureKTrBй=j@@@'Z    D 2WjE JЊdA@@@  &B@@@@@@@@@ tYΞ@@@@@@@@@ A         :BgO         YE D@@@@@@@@AVf@@@@@@@@"`h"        N 6tUS3     /p :y*^Kmٛi*nU}RJrһ[?wV˥t6feyu7['A:R6 % 7j5ڪhi;[|*r9JYRNwmնU2Z^Yt-׹+a'VmΏtipZa~YE [33f;O[ʿ}x˾nF==*t1Y+7녗_I'S⃭@IDATof֬2cեoV~o듔r _5eΫn߯}Ӟ;-3[*T*9evܒk%T7#c,6ZSeyl >nVgRi˕6?P+N5\B%z-ܔ5c5+EuY893  u]/Xӧ~ɖ@@@wD}ԁD_NH3R޿5|q=Ʃ#Wҷ⑓+d&k3[czn;^4vbZ6JF&`=Ъʱ7N=rro9/ջ +0G]ze_3977F NܨKR~X92mYIQ$zc>8,@:53rsUfkubDWw/##P*g?jϞgȿSW4=?CF]^qif-NkKdjkw=@#ȑ+}LUuJרb:ekdl=?^TwP7kq(F3BZ#Ȫ @@S}قl8@@@@`jw>y3g*7DZb9lJ 23bĘϤ=2g.QR-jD\َŎL-\[KWkڊڭʱViN/gUզMkmVZQ_gnpT͍֬1W%2_GJ]VеE\-+.զbsiMB`e -+ۛ>9}uE>=`5sZh/ %t_KpO&tTnKgٯo{[cU,#TOs'TjM{HGZ^\],V߫eoTc'ݢ,tY_.ߐ- >~ҿPmr@@L{m*v@@@(U Q)'Rwk͖ eL#Xqp-sg:׌Hkj=&'W?ٵ=Z7/{ck*7VYlʩV֦4Kw'o7V*՚O,=Zit`BcVps.Cgo6}Tzc> ]:|x }G(qiiG6p~̵2w5ѱehOCkϣ%^udwi9 K|yb>X2NL}B5zMe>  DE]Lc[WsF!3@@@@Xɪ:@c Mds9D)+{>5ɶ* W X~ٔS3%̜=jUَ5qS#if;і>?b[{9W1t`%cCkGMG#nwf:]Vv_?c5wZe sE.OJF׋ɖjlW]]qֽSBO&>\O-i3Yuղpu7ho?^ymy2ifjڭ@W   @X ضtXj;g2   @T dJ@ \=zm\vz73wW22ۊ?,'Qٖ[4QLEm;7gk,@Kb5`h`7q+cn͢qoS -rA1qz -:uUߣzyse]^Cɕ)R4\xY8Kq#3Ub۷Gꈟ֜>('ߴL(]k/Q\zQ:u_Mj_f+oSKa}zUme]dC@@۔jv@@@H:R@ $`ϷmV圡9_ԞrqA׹I _y_Nu+jM# (CSh t^_^z+HͷخTܜgڸx0׳2&kizX'ȶFUsy*ss?hW3a9 s7ݻ/]0>255>P~R!  @u]wɶMvnǁ   tAVM] 7e ?"JN3K?S:qN?N?0;.3g~niMH +Sjmy?s5+v=s?Wtl7X^ENGPӧOliuw+Zhy%sYQ}!sѮ5n;7%襶 GHm_ޠ{| +lfon6@@@ ^6m    @G dԇC >R?D]\*hq;MPrBJHvWcZuz6;Yj[y>I~iZw4_ެꙜ s#z5+Ԟ+է-th{+Hɬ%^*Z-*ޫHŹRJUn֓Ӳ55C.3Oi34r竿z/z>1i*G[f&|2uY;սby/5?3eLдf   i]w.b@@@h:Z@Sթх7H2{lt٧CZ?7O+O/e/3Vjܴ媪3o֋Ʃ;׿!΢9wqJG҉f׃ja yg^TUmTyƌ등%^FPPr?1)G:vs^E-eү իz$+FW+I2pv}~괞q,= 6]ceڋZFW7|Pso ^Ax#u>?oT1GƨVN@@@ Z>>8@@@@Pd uDhbqRvշ9WZ JԽ*'yjSUlJӠ,#p&1U9)wòq:i+VL%])k_M^i +hU,ԸW%Zג<#bd]bU.Ǚ+)yKacrm~}]D'-S݉dxcjzl &ʳ2}3(Kbk#I٫u`}'mJJ]{Ñ=OL{ 卹BFV֠]p\ty$D@@{d@@@@B!@U(ԩV53 HucxG܉s87@a+~ϺS9b%:2''$I;yrb3([K6Uh+L}u;m6#wJ&g_[Lua=:s9:`t?w=4fU.Q{&-1c\%^ҥ7_YJв%*|e\+   ]tl    @(bNG(*N@H-`&.Qzvk:Z#hK쮞Z4Ưئ1N{*Qe}3ܫ.SG{wA LKoب8%&$}ҴK[&.yf\bd"Z-<#jFvRu6?O58 z%8!SX)#oQTFi)wiVύ:UWO&q      AVgMH |QWL. ݉ *!5U钜F;fw c5fm)ٱ 7*5njߪ erjZ~{z_J$hX>n\ "@@@@@@4Yu @ Ui}Fm~> jHRS'^m΢Co1mfӰ }kjc̅۵Ϣ#jVY MNF`dҕϟ5=:Y@@@@@@@ Yx@ j׌WPgg.ۮigV_Yd5Ikc>7Y       ! *DT iVGuRWtWgrT_Sb-9!Ҁh/       YE1@@@@@@@@h sڢ@@@@@@@@@U hY        m"@U0R        DAV: @@@@@@@@D 6a@@@@@@@@Vud        @lB! @ ;Veee׿={       t..]hdcNGOy `8s.y1        @&;GU4<@111 @@@@@@@@ [|n@uuu{WA        iiiArNPH        tR:m@@@@@@@@N H        T N:t@@@@@@@@ *8'R!        @' ȪJqhoޮ=Q:RuD][ߺNkOm|ף)F]j#ymˀG[a+'r8C*η=@@@@  $     o8eX GUDG" Vnn[0Pnm߸N= Y=Z(t:E+7ln#7QG\W{|z`Spb]VN5a.hf_]NOtLe*))s&{)   4'̏e     p[!ڭ!569ei _B O!hJ-c֔dbP~334۹_.=>6MkJ5:rx2 3k6Vj@[:1>ԁ 5t:i3g7ۢ Z@@@@V U+Ȇ    AVCU`ed~\aH:WgI+P=+=5ݯ_ ru Ғ.Q+%M.ce~2͙!m~1Hk=yT5}1祦)3zɶ1ka/ga-e:"   ЩZ~     pJw_Xl|}5UztoU'n7rt\ u 5_loݔ3ɕre>S=|!R۷ ~e˦^|~VV6E}@@@L     %f7Gs阥&rǪK#}<@?(9~%v8k.gmq[6@@@@u.@@@@@ Nc `e Մi/`Be83yN(,%ԶV;iܹ~cڢ D@@@V ٽUE @@@@VAesy VC):iDY7euyW *_Uqy_?yatNu_zUЫuau+,iqWʠO.J+Zieш.ڢ Wa    @j5@@@@@^[ۧEV*'][pFZ`)ۣU9Qv{w˔כ^)#iEwmvǖlS J49&i܍ڸtg˨T^#kW$c~tǝ|:ækWSM(桥aMZEf?w@@@|1u#-qevs   m!pRU{O~a/,.|]l\=R+ꔑ".[U7U? #-j*R)+7G_7rS}Oc.]k-JLSMk2*   t>4W "  дAVM@@@@@@@H6Ȋ#ui7        tAVL%         YEn@@@@@@@@:J@@@@@@@@@ Rԑ         !Yu3 t@mml!xE89[x9%"3l%唈όWdS"2>3^1NV2^NxE89[x9%"3l%唈όWdS"2>3^1NV2^NxE81Ht6W]眄}go~l `8 !j WW//`~xyqC@ˋ#/"2^^axy5 +ȫG_0^a?D^ d8"--¨b]8A@ J(IYx1^%YxE@dYZ_Wd DVky1^%YxE@dYZ_Wd Dgk.0:Ǖ^!        @ dF        )@Ut+B@@@@@@@@6 Ȫ )@@@@@@@@S Wz        m$FP  @ ԮҗUh|1ݕ3L)חN`kӳtK;wCj'pwzTJ֨J;^V46fZ6'CGޮ㣱է4splP+ok]Cz75P7ԤҢam+ZrJ_L_I0j/r4Dqtݐ t׎uzg ԃ7YX0jVk_ݨ?3ڕl~{0P3Ngd."m=`C#tǤ4, զ`ӹ2pҞUZwvS)<ړee}ÜG8=˫~<8Brx1!i˖9f:aSŜGEK*lCu9o[bIZ@8]݁-P  ՞_V s-AV!f`֓ ׳iUVR Obq-OW>OÿʫeZ%nJ^8auqTʑKد2YX 5{5{ѢDU@ ReE*+ب&aSMMft/AVN|X;D ~ezx?*D-sH6?^ڵF7f5DCUms?,Vg{WyJVj Mw8xyl:<=QAAVos4g, sg͍wcUsŜGhƤZ/ŜGsbΣ[_Assy]`_ND"V_9&h囻U]]ݛ_[hݮ۷hja㯨W^41ٟM¾Z`5*_oxoUi獩I(yoǨܯ,*xi>4X.=?6RGKiE=Q܅UvMxh囪حݻNQUM:)8Peڵx=UYJU?D/P^$۽>vڥ Oٿ0~ emP}ǁ]5k@?(:XZ#jڰ>NU@dl|\VWjwUv|L xx1сLUsv`Ƌ93Wu΃ 3HD@~jO=,MHA?,η/,zWqXFj_-XBBնDE/d,=l+!ŪK !I{ ɏ\Յȼ]BuWR/hIv:V1r Sq0Yo3EExMi)pR5Qƚ%zc~  wަ!FG=t=[Ge;uDϿT#nICn0|F+PKjh.}.OiB .J57 //m/:?m2V15ש{ye)ן>=a=:!lleث}L5 )f::b/[;MmubcEsꤊ/[ Ghy0 Tk0ǜ"T'sz/<|Bwx9Rg` _T@{C.P%|En | %;x>^7- >iOVye(l fz<]%%* EYcoo[tCJXxf0$7E\M(//Sѳ/ޥCzsZio@01[]dy#PU5c nIN* Ye_!mi%;W!Hvޒ֏k.8+$ R?&6vLb$/ObsDq/zM瓍`ΣC sA ISe69 IU69^y4Msmj|s1+םu΃H@( qr @ Tm[Y߳ &3nح,?b]VBU4@e4ܙԛʶ-`u* 4{Qz~.U_u`Ni,"\ΦsO{ |Rڵceavw',*3Y4{ua0'pc|}`e[s)uBwv͍Ve5e^WM6=qZ\ZF-(9,GV 0JƜGbEUy^GgeΣϪ><Ί/KjsYEBZ@ &dU3NzRf)B{#&kM !iԛ-Z`=]=4 'Jul#`R-̱cM}wQF3L iVUVŁ4v@obR=zިUzcsK]ݾz|ywj=mr+='#PU~Y6|FƷ%V'7YJH)ZBUUUQ)/i4~F5fz sϛ_Wv=*bqTj׳, p&<$>ϙh%y4%Ӂ ,<D]<"almsYEkv"m(`n GFu/ {>fڶ:E/mP!Xڣmvt=4lRT`\[ډ3O:=>=L=g9fjJ+ i1leURJ-ܗ Ցiuڭ^vׅ+TY$H*{ud3+}n֢?ai)UJ}%s>ADH>.(ЬY_Tomlh;0p|{QWFZ~(@y1сmPsmءE0ѡgYsg yw9Zb! v+N >̴?$ Ɗ;϶=b}\ݐPil_{K,FPAKsۿhՖLee%*+XVLaimK'IsRl2q]\w?w!+^vo}Gv4!|%s^}͞2^n٥V =i4}r,=ϖ-Ew` Lړz{s}އg* %G^0Cayd ۛyns].s>B^sYy @:@mnkTZ-26r7Rm,p\ḋVܜ!XK^Գϗc_~&&tv0'=PJƶ ÍZ 3Pٛg{kǂpwophkLy^Vj6-to|u8A#8F]'b>cLk?UJ]7h۲%tKy}4G!CUPX{n dZkOTdkQ$CB%O5q"v؞?E k%/0󈔗s2Rf;b#Ƌ9/gkpJD:4Nd>i% ЦgBYnٯYh"''ᎭcڴV C N~bcU:)vMVފg46ΘJ72H 5zhP,*ǔ4*q w{cV|o8pV!_p.2X[2~ SS*r&<PwݱzxݿJihZ-zR`,ܲT7u, >Vvx;4ۄ$s 5n]$ +جa<5pj;H7߭/CEO3gQKc}k)dth* O!f:΂01<"f emgy69_f#";AU$Ni+ VUļ#ȠL !ChO؂nrko}ɔx/Tbbliu]`h:VlV^?(Pg9zt0~ֱ#ҊDҞ뱊VA*{QP,Gr@MsqiHEϚ+$-0y=7-1qJm?mIRʀIڵ\ #Do;&hOja2IѤgK 9FPN(=SDW3l]/3gVH0e:= ^~}`6!t3smCh[6-}ÜGya<x1QN2r2<_ 0^60#т ?es1r$E:@LL߶h8~\_Z=W))qw؇W_ھ0^56^ֆ:f))QmY6J0Hh/YtFr,:OuN?{~2-mem8n|hc݃#{3h{E˩%mfZa?EGgп+ڮaȟ MsbG!/3W4y^3ͅQEq pQTpdEN&ﯶ7mc#"h@|ͷ-)\)յ_֓@ b,bG$&@g8])@@@@@@@@V\@IDATd         AVJ         %YE@ @@@@@@@@h>ES* =111z       ~ͅQ7@M       @tҥٮd,@S //Oqqqp        @ fϞl.Y"        @g`%          ЬAV@@@@@@@@:AV@@@@@@@@@@Y!        tv:+#        @Y5C@@@@@@@@YuWG@@@@@@@@fj                  6  궿垫v'?|aiq@@@@@@@jwb*@@[`ُ4=ofokY W\lhб3-`*n"O^w|     ٻ I hnbhP=`5, nje\Z.??ݍ[=%xza5-î `JVH URYM0i}gwfs|>iJ< "qN 3Y4?}זoq?_]$R2\#LعsN:;vn_Y !}JݔxБqmGuߒ@w=+bb"_9nV'tgJSAA5G.0r+uKbcj> Zi/tW.3:L2 gg~D[9A.}:|ХYb7~BEc>Cixg_P9-v}!/SWdiҜ]k\N3]x>E:n=+k3C8 #Cwhφa}4%c]^[[r=6FQ{q@@@@@@#i1@`PDڮ'_= |!+-/ck~PfWϖTK=֥ FgԪfӁ {Y\U.GSEohmu!>xnoI՗}:ۖ;etGW7?sTr RahPuR,6~}*r>2ե>>{n^\|\$: oiYq׮Ng\ҜK~z^R9Xg2=}{ NoEV8\C|UhsN0ã@@@@@@; %eB 0=NJpPQUMN5l-Һ/7tpʡڝFfWfU*i/]ؒy|wlI+gݮe\OV%դ&geQkmz8nvaU;Z|9lۀv32 JV5k6`})*joԯx|?rThIΆ޶+OXݻ;*wmoڅ踯qM~w.7D!N7֪ª1dm@kHQY^**8jAc3FtIiG@@@@@@Dd!L[=o=6ZA7(s&#OzսAy=ڃ(ڟۨ,WKޛFYV_ڣ4"cWǍ]FZTb[Ԡ,m6_F?> K ն1gɡ*YX%oX1m)厼R;v=Bx;#<^ɮvկɲ)ԝ-mZW >Q{8j/Ϫ_ק(sqNkqJ~Pl/{.XiPoYwf.w c,:<FIm.WiͼFFmzcgdP<^=]\THjr6iӎUFۑh}f۵r2ܭ\fleGtI4@@@@@@@@8`'8\T!TsC)5o*;(#oη{)#~ 9 #s$m49չ0V_E=jR]V.>c0OUdUp7168FG0DL}CN٤r=|%z22rovr!&c'_}Vyzη Yv=gwnSJ[*s`픕y*,4y aY= ųUTiY\g ˭/(K_2q/(.yN       @d" 0ZQKū4?3ME[RVNÑ;{nɉ[l7>-Vݕ?L7\K6-T醍ڰ@Q%,WBBlƑx;A s YW *1Y\{Uhl)iZU*bΘz̛>|35iY+3mJݸ6,mfseD 4Ȍz(~", ÷.>1uxܪ77)w`F\!      /@*׈"@-بӇTWUaK5X6_AQӠ&Y%~{aнQ\7T߱XCI*rPWOS) j]7=ߡ"sƑ{~_9e>QdVlztRb}k.ז}m^>d+))Begtu)Ng:@@@@@@@( dEP@ RsTy|Я7ui*]sgZtLNC;]:wPxA{?7_^8t*Bݭc;.U8O^~RWW]8/>ZY+79W6~^:q`]FjsF I%rs4m~5zwG1h?emЮvͪoS{7Ϛǃ62X9p> >#      &@*V @l NnWYZZxQm9-ޓr_QO{۠vxRf_i ֜=;%AMD,ptsxJkն#RݣjvV_bR}*9񔾙6Gs|.1Qɣ땛ac&_ޣ!d'`Vd~srGe~Y&s=?19Z>FBÇdBVѷ&bQ -r(Z:aΖf2i6%ܡkwV+3Gw趕[LVݕk&j|U5ی tF>JνC[S6-_[|A>}L+UUF8'Q_}Gc~d:8Q_vo)YzpRVf=k%Cѧ-1sV UZmr٪UPgFsn=Pl~ٍb}-*5Bv%qRUWXghdP`.U߸]8_"]#B!       oML\\a${f-"#xiw:Oŷ/4ꤪ&YZ([Mˊb׮|MTYslFgls櫼Ѭmv<ҟsSk ~Sdwl+VJ3Pfo"%j35VmԹoW.urUU̳<&ɩesJB?Ke+-%Ei U=0w FNk7+vc<52BT󖬵SZ`vckq2jn~C](F)R EkާUwOcb~?eƚK.UJ/qC5C9 kݭzq=}u#;T_[ю8zESY 3~'5_ww:::yÿ}ȝN[2/@@@@@&\Մ TkQ.VxʓwJjw )qn;L[K|QGfA`-_[uy<ƫhSvY,vW.uI֖VƮQ1UT{oLG*+ld}nK.5=ڋ$kZZ&fQZ_~?{v9:zy6<c &f@K%UV%GEu3_Xbj}󐪊,+Cbf̌VA- 9T!@O$`s:+~WpH *wnYm)JNMUj?4h}SR3:t]61 :tGת5:`XYFaNGϺtb,.su&D̎6@ VX'dz$GzEO#5ߧMѺFi.m\֎;Tljui$۹>??%}uo5??glPw Զ<6n\t^vQklX<æ@@@@@&K`ɟ.iT#bGJLNW55^z(8<;1ʈTg> GH}չzT;aÞNL6>7pK =jƑ2^K#ј%YrLRͳ.<G'bvDP6(Y4Ǎ0kT}7`V%d﫻C:}lrsrcO"     CJ<*F @\ Uyإڂw~_/^OJcb\ije"UUhKM5|Kh@t 0e:~: ƩI3*?0|iٙ ;׷WڎJY7ݬJס 't87W+h!zJRX)mj> Zޭj~5kϩ 74rXYF;KO>ߧ(%i~:__,·sXy#L=     IJc @䭑1TUAJ٠[PQF*ʯj @ ݧWq%:r^K3n:)m9gv\^=[\FqJ_r,\knJ<4uȹKYz._wWm0_GmT>6{S/> g TO=Cf"|~Աj3o_\1vgR     @ \d| L@l~J=]j?}RǎёCG:}^ ˷CF+ ,עUIU7v-rÇiEUմ_*w7;=Wu6ht׽A jjr쿱qpZl)?`ee6= XUYhk*fPU;Zc X嫪n72[U-ŀ8aYLisd2-[bysp:90v     @ U#F@bjlG\)?h:Cߢ@OQ-2)z-t`A-Oo`jݱ3<ˢMzFضT*5Va׸/h&ƻRnHfH?R) =7#+6˭~vԶAOl4V9z s.Udp̖롻S5|2[kHŠF{~C{%u?P]JUkMS@@@@@h d+@@@'}&fp}6nܠ7+;+5ja;IJ3K"Dx!oܮ |m5oYl6띠9;x»}!øuo+3pT?7Q}i^SWZIO46     .@*W!  >BE=ci]uU+]!e՜}Ex+b ^H]BnCqnzzpTnY[a~0fs+     @d   ťr_;})PeW_\ĖzXr04kyZWwkww^=:k6z^*tf۽mzG@      L!鳧g@@$qΟ<>:kb[oNFʦ6;un (]T EWup;[Ұ7].ݸ[.V9Ye!.Vaf|D@@@@W|LY   LXbwtݥ~-E܃†eiooKl95?7euS)IqtNJs HjWF9jiφŃZ@@@@@X[Ɖ  q$BS4G y%N\"3Mm3iZA@@@@@`8.pڗ   ' whѢ-8{U`<Ŋ      ?J1@@@1 ؔ}ҥf{n_ok?yoS@@@@@HxE   A`Ŋ҃FKD j,%5Utv-Eɣ~:W.@@@@@~(@@@H4BUnVcVbRӓ      E         c        D!(]        !@*:ցQ         @ ҅aX         c        D!(]        !`   `}g?Yȍ7x4UXXR@@@@@@`">0^m   -koTBB.܈Ϟּ'?9C=@@@@@@@&@@@XxN8!_*|բEt[|F@@@@@&DO')A@@&^2`     L;YM,"  tuuꫯ@okΜ9S     W+   0XsU @@@@@@`"+DN[   G<2sT/@@@@@@`28.p2ui@@&Dw i#z-}C )      0d5  " Q_^ =מ2V~.@@@@@@`j`i@@&V^Ӎ7諯O~!e|@@@@@@&Z&Z@@@`R}-JYT6Ӂ N,46|fu_Q|<ώ ! BTM@j@@A-tS?    /?ACLvbtuϪ/٘IaRy.#žXB"'Db egg#B   0Yg    lI!0 Jg.j I^Tl1Kuxjgr L@@BUT,@@@@@ :WEDayr9[_>K5*=8s~zCng]y}Q2>Kƽ!_b&  q w8S@@@@@$J"$@l|gfoSTHl\Ƕ!^?F  xf?.@@@@@1 D*X_g~(iμJ5~zXϾ+$uV}VWFSfg|\Xr> <%Κ+OVrN\7R dnUj"_[xGn-3/ءcPZ[=D[Zڠߎcx/|>̏;ޣ횳,תzJٟ|u]ղi]ޣ#=:Ooђ"[:je֌ĸ_ Qg   %k`@@@@@ H :ك D:XBm:d=۬MkIm!%]g|>ԒWp+{ ~a`6=<{2yn^wfnw,$_uI6\ [~֌\ըڰ48%e-BT@@BVJ     &`7>{BJuU)/6%jduWꡢMfx摽zYƉMas]Z<.ZvS~c:TYg6$`Uո__4'9Q]-+ŗھVKaqS{.R]nTqU] AY [w<Whd7ulߓZ@,=ZaX6_/%@*#  Q"@*Ja     vٍK%:t{Ʊvs_O^Ա:7UlS'֙\/7n)#sWv٨󇒕w_^=m<$j?_l;Ss g=ʹ7Mv{a_$9/dX/P}18͟m\Nj>ޠEVSOמ [ 4G!^  ą!XF&    @4 xVvm?է߾"JZWX(ybؑ-fJ+~ƭŪsW:1>BVTzN&y?型26UȪjDƴ{ `[jzoj;+R o~t^N}F@@D62     G6?WoxJNN4Ч<:\%⑯w| .\';~No5S__["ҷ׭_/-՞A-Le'Ac,3d%х^^]erQM/#  _ i@@@@FXsK5lT뉗S͹j{uiqHwdωT<,_JY/iܴV<URY?]~nY)e wF^tДTj_%gۃv Br.!!q0   @E`    Ļ@VOO+k5\\"e4LYґ}m{XfmbDaY~`c@Ls]Bkzn-MX\52D!!`(F@@BVqL@@@@OZUi*v-ZV9'qZZZ޻sծvy\Zk+avȑԘ-B>mAU/%`Q@@@ .Y2)@@@@bC`ЀDl&M5H LX=q|ayy=YQ~RoRn'p,:6> zS8XL_~w{4egg{+V_@@@1KvM"    @ >B*t6-\8jNe__=^qP+/NS3`q~CZ~Eֹr=Sif|/n4aF㗪j"ςBVC~vSxr\=9BT>"    d7    L@퐾"t7ڟJGdMY:HTR_J >oPHi #%eFj?U͏ʋ9T~@#_mg.Tu<(E+Bwӏ_3ozY?@]C\    0Yg    L-!sƎJa1cZXTT/ULζaS'Am#gPݯR^γ2^&8JoeQоm?NT'N jAwq~#KN?> nz[;<u`As   @| ev    D@vޟĴ]KfJ͟׏_T34\-*e' 5Q[uA[9]7ǦVu|4)&-2jI9Ym}ZYӿgzۻ U_xtäx^W\~f_Y *ύyTnX+|3G[o    03.d     0n >/iȘ'26T$)YkkWȯ c+u+wK@IDATJ1]9Ks=|_AZ٭ABlIxUu۶\C|Y{.oE?rc'cN.\'^ӯЏ*G/ VvE   DL8sF@@@! ~ .BwG^9 nJO M2pySٚ|3PmW0vr:.}E\222e GyRwб}FNT6e*kt͵ /uɰ ;t쥗t%#5kև5O>.'?1 FH,o.#qq@@ dƠ@@@@`F+E=f@wwwHh*8@1D SY_Ӄٴ!@jF,3D@f7G@@@ Y 3?F'@jtNB O!  Kbi+   \!K&bRUL.F nYR2@@ d5$ 7@@@@ Y*2$BT| @4 al  L!q@@@RBVQ0 0BTa |D dS`@@!1   Ċ!XY)}3[^f  03Y͌uf   XBV3v bpC dU`@@BVJ    -e%G d 0&S@@BVѱ@@@&I$l %eB 0@@&BVO    05ƙ^_U#D dk@@!JQ@@@bRUL.!1 0A&f@@bBVQ8 @@@/@jc IU$@@/BV@@@Y1jQE00@`DBV#Q@@ dK@@@@`8BVpo*QM6}!S+@jj @@ d5   S&@jʨg|Gf 0Yg  3FՌYj&    d53}2fMj2TiBV@@Y =@@@yBV1S6BTSFMG @ %eB    d5@@@'BV !4   "@*^Vy    @DBVYⲐU\.+BbBUL,D@@`\    }F?>BT& ZozC@@`:YM:}"   2qwDj܄4 0M n@@BBVSMW    0| Q %C9 @ d  ,@jd#j    @ #D5u ]k=   !@j2Ti@@@F-!%@ d_l@@$@* e    7FFoEM@Ykp  @| ueV    `  |+ Xp D HMB@@ :YE0*@@@ "D5A44 % D0#  bp2   ^ BVFD@`*YM6}!  0=ǝ^@@@@`b)dEj)@  d54  @ EaH    0q"D5qJK D!hZ Ƃ  WZE@@@(ʐ!(Yt  bpC@@`YM:]"   LdȊԭ=! Kbi+  c d567B@@@!YT @ a8  L!I@I@@@U;]joo xZj| LBV3sݙ5   d5֛"   0CV3nL@\>>@@&_@@@@Ї>4}3 (Yfͨ2Y@@f!@@@(i&3p2 S,7WC@@@@@@L?wF͌    B=    XbG@@@@\7F        S$@j@@@@@@@@bSUlF@@@@@@@@) d5Et        )@*6׍Q#         "hA@@@@@@@@ dƨ@@@@@@@@@`YM4         @l uc         0En@@@@@@@@@ 6Y1j@@@@@@@@")n@@@@8fx)ȉqt3}{/E_~x q{ *Dр:NzY"3C UFj!    0y@@@_*./ZubҧHA#pLk |Yy . MU_~V]lP%nBcg`J@@@@OWM==#   @.H$0ܧ*ک|XW\"uB;UESw|}]qQ>Y jS]9@D58*3T[kɚ "    p.@@@@ ܣޞ;/ߘڸZ}-gefTZZ &{Qlwv?o3L_m7z掽D@@@@`BYM('!   @, t_͋sGusn)X))JI r5-j>N;vn_Ya:zUz[G/;_0KﶵzKe}a]X,βs@j׹$c~F~R:;uQWIv43xa;k,_ y+kTX.֧_    LߪMM   D@g|edU׉ J[*^T$WcౖTO=cj@J<{6 ~96 JV.vkV٪x|9:>`Γ/ Ԁ ]'Z3oq؍N!7Tr1 st~z_3?{L    Q$pY    #`K2*I,2VۥMT鰆}n1[W+jw:U"ِs*]\J] [UQQ$떊**UYiT+4z+*lۀT+ɩ%(]Zc)uvNgvUZ5kt#Ba㨨h8;0jO\Ԁ:T}#G^5_ٻǺZS\     0 i@@@@QR4{YNLn3MNϠtS C,-.WcSwc}-*M˻UV?QY};Uoʼf9tkzNhѽ6qRUul ݪ/2c[Gt`R:?Q|FꐱU^JJm cɯjs V+~QI=~0njxEn[o(.=xk}SeVX4E@@@Fƙ^@@@@ FLbV7gs&~ Xᆠ G;9uK@Չ[ZƗ~_)JgiTwS[ۻTFFo?ӧSinsX۔808-IZWB=@v?r?`%{O@@@@W;   DKR8\t{K߱F薜ޢ_k}s}|^d(jUqxgɖH>^~Kɒ2HTVfڔ`g Z٪깠>g?ј.:?;mZŻemVڏm iB@@@@`BYM('!   @ \)%)mV}ѧS0DlTCߖK5X6_tR{\Jg^cyKy$aRSFG%@@@@ d5   Zt&|{(JΝ1y oRsTy|Я7ui*ԼIyUBzxx{եUN☇AvΑڽ^Ѡ='quq /@@@@SUt B@@@8]=ϘYA3<|;2q+pםQ ݮ*QsmӣZkk+SU5od;ոn75g-}xmd5;hvs;]:mr|,f1"    unI   ī@rjvY;_kL:{tn[Ŝ}C(-&(Z:~Ζfh6?ٔޖݎ@ZvcvSgzCn{_J`עE *x$B ߪ[ޚcNmrww;편WHK|@@@@@`*YM6}!   @t Λᥪ&YJ|BiQMˊvo׮|M20svssiJXi?Av;*}qeif>˨k-Y+3UZriQ7-͡c~m|qon~CX?ucKQ[gǬۍ8/@@@@GU#A@@@i6xI% T J[@@ 30b,U+,mZmuM|V?Ŷ]ݍڔEwa>n>nb%n`I TL(;3 30((g|?{{fνswTT%,WcvcnQa6}r\K7Mj #I R7!9Y$9PƁ"T,XiLυ) ] ː)Yu,(4m+G:Cl+nnqKY P(@ P(@ ]ae(@ P(@ P(@ P(@ P```y%Y P(@ P(@ P(@ P(@ POd'̔(@ P(@ P(@ P(@ P,"(@ P(@ P;?ODrt`m񿻾q薻m?gM8yr"0abD(@ P(@ P@_ 0Ȫd/(@ P(@ PPX1\sdEXy`1Xn/\ T/ָJiڇs<" P(@ P(@ ox́(@ P(@ t-qEYk6:Nkk- 9*%E&:6h@`kB^*գn+cj2SR4cݞVu(@ P(@ PMd՛̋(@ P(@ x@< S>Xӈ'V܇y֪zH1V)HK>I-p6uuw\擏iѭ^&'&(@ P(@ PduX P(@ PtT,*tB,nE֢lw=F߈Jvdbz/N mpN2q3"B]NZPT69 rI9đaCpZJ'JcK ^)!EԿ`Qg^y-~0! ,˶RTiq݉rC9\G8&F݈(7GӗIC5ʱ?b*`|\!K((@ P(@ Pg-йw쬳d(@ P(@ P` wa\k`DQkU.QGVG(E*:su,FGYDYJ0{,t ًDR̋MB *S.ԇ͊YRU]7B SSq(@ P(@ P"g^ɉP(@ P(@*P1 DrZ9msޘSKRL=X<0jAY*, 0e)0wZ`%LFݶ'ьu #!'FlTG$}ڪ7 ~H/K+%cV*)%OLud|4\Ŵebj;˒Ym+fɷl,#X!i"}x8p@U]98,'Ʃ8wcpVYIT{}ϑm P(@ P(p~.:?^)@ P(@ P@`_+Q]SPapִcXΤضy+ <˧Rش/KtV"&n(%re F4J [Qlq.jPS׉Q,mrYŷfMJFFT;$+/|!.qf= r(J"JM<(@ P(@ P(Ћ ELfE P(@ P ʝ&)h]{x@ig 9 nCF)' m ܶ([ΌE1>2~pjNmgFO1@ôҭvT:g c-ueHGIsϲbj P(@ P(@ " +_(@ P(@ B 8,c=8 J1ck3^9SϿ:i/ @[Mr3ӑn`F0Nը+(=&GͭѵNĚ"e$/3lVs(@ P(@ P$ ^d6(@ P(@ \z?c%ZI7JpNr2O*{̥p}x(g-8x@+xG!H[%P^Tty,qA q;>5,vkm6ݾ g\ mx84xCV,Grݐs(@ P(@ PY#s(@ P(@ P`  D_P۩xiy44.7;[};ܖ͞@WCȮ|V6.}Gm^f-{p՟_28#DEEcfsr䡭,#xe֣BĤ!bOnuuJ ,n\](RuN%(@ P(@ P8SY(@ P(@ P`OuY Gu4\R fL JsJZ&yu5T9 ͠"OQjx~p̕ֆlܛR.rխ SS0.}UZXsU)JJ!N2, ?ŖF5=Тiu +[&&MC\DP}L[$P/!P1uny歐La N~4arnEwŋ)Q< FQ?GYDԘ) c{Խy{G)̂tԍbf,9^5?KG$2ʕs(@ P(@ P" ^ad&(@ P(@ l$؆l5q> +CQ}BԆTʋጆҍ(#CLrd:w󏚅mMNUBrp`0fn7)^?u R]l~R!M=ʐ84ܔ宅ꮎ%6 tʑ?*4}וI((Úq(@ P(@ P$g^ʋP(@ P(@A `Cks+HKFxDx!O6.i]oi_f9UpH$BzwVKaA!! Mmr('-w yʁzKnsf}ʒP(@ P(@~) ~P(@ P(@ n 6E}j i a݂ (pndun P(@ Pο!(@ P(@ P` WI>x L \K P(@ P(@ YR(@ P(@bbIbw(@ P(@ PB (@ P(@ P(@ P(@ P+rQ(@ P(@ P(@ P(@ PBAVe`!(@ P(@ P(@ P(@ P(@* ʰ\(@ P(@ P(@ P(@ P@`UxX P(@ P(@ P(@ P(@ P 0Ȫ2,(@ P(@ P(@ P(@ P(/d/^(@ P(@ P(@ P(@ P`,(@ IڸmC%[{k+poJ2"x$ҽlZfļKo)ǫKI+?EmcN(LZO֢I  Y&L=H% $R GhŬ/.M3~J-P*ffiƾOzLa3x=ͨ L=|.̷ kM P/هJsžk߃+°qӑ>cCǬ9Y;LǙ.%t/A"2&k{kqH=,q ͵UoOmj8A Pe;(л5܌t{zFݷ*ryÐcom͞$*S1kۓ8SzYN#Tѓ,Iڎ2{/Ӵ2trS=c}_0Y)ʵsj|,c=Gn۠ov׿E}y毿ڈtv{EA6,)^{m[jHɱC4(@ P)p뭷=y\k-+z(}}c]ܐ] )@^1kP ck鮎3u ~ Re n!ZINcIjn=mf{SS\Z QX 5fG PGQkJp7|K8HRj5fJgOiYb oހWsjX/gcL@FzJcy݇6~zG^}5Ojn#֝yސ{B6u>2U6_ԟ#w^Z ȫhujͫh*Mu]<)žV«%(ϕs1r5d^;4Pj C}lŀ嵣 K\-ܖ˥_%0:cm(ب^kÌ٨(5kDfGEfᕲƮUxJDJ%-\umamV.*,]gõ(@(sE_5+ȹb}m,ɂq|ZI Pxј,9f[bYZ&OBaJu*(0l]'O~ge86dfLEe?Lj19:LJ>HjcD4Oqn s0ܜ<Z.A0F @rB$m<Ddln}'bu3|Xm<&c㎻ջ;P$J(>Vܙ% Mgp+F8][ko( #?-A T|qc1}5ش0QD<'?伲KEdxHj/"y$#_|@nE[x2#?i F{ɀ)@ PPn/:WD_|m݉EiS)/$}8K>1ˍ,2ȎYFX^ۀk>k?"9J:yec J6IWGtS!Vݬ >F@DZ]ܛ=Br!ppxx;yf}EF7$W_?n|3E j &=nqlgI)bnoB)7ՙWjf)%ev薁4T&w v{BΰԸ3l}Y.%3=\n +r\RٮPntigľ ES(=;g3r{ǶױOhWk\˟";%lm}Jc+sQt>RRTk<5@sg)]21d*-:N${f^AB06s\${jdnϓ)OY8S:2ʤצw=nȅ(@~&p뭷񤣮PmaHEB{,Z8tLHBQ'.]#}z&{.or둗(KO -QʚB9hS9P65STu s]NBuFgbϕ3 ıJw W_Š4(@ P$Ȋg}W /p˕Oxa\D YįT P^?]nDFʗWڻ±jN/.مe31p-Tӂ]YK<'z͵#E{O;/.a]iӔ\HXʣm`*چ"5)TF2ڋDZ{<Ѿ;FhS?עyn5i#tR5.Ǔ@ER0vAFz9Z16J#2iy.ej x۵c)o7mrjD>>74%ི}7'֙jd/smR`'e2,(@ P_$J)0{+vn=ckitǨ{vN|L Y(@ P7lrnoqE̊ miz0U4A=aDƢDL@IDAT3C{)&y;'7o q%߼_s~](݃Su)p%LYxTWM>f vfG/~%~^[t&>l&܏'ѳTPflCkc|nVOQ׷͕ZB!`R kp_KK%+aZ-afǽY=hԵmAqu58/֗]g6LJM'x _ ǎBI;F]v)?.`\p?,s1f[$ޓI~3 Yr2Ei#"])qsӴ@V5uU)Ž_l- _ LӇbQ'y=1s"dV)W 1ܼ;]y#o!tjk'Aa[9euX1˃[Y#M+09JzODM SPmF}ZRNQ.+\Qyz+~ݹ!8Zh\JwCH 5`5)x1k>\H2n7v6mVxs'#GsW\6*@]9r P U(@ n4]g|,:q:[tY,1-(¹&(za!BJV߃x^YQj6ÐdDR.6%lm٤KNP$63-7 fiEss[NĚ"9Œ[Bt47 @ҹ#tKl`@ t wQ|_{[¹f?GZOKGzLz+I/p<՗`O˳o*`SS@%OrPZq=eqˎE Rs?t4|h %jcGܬ4>h-a>h{yyK*` dIUl:$ѷ+cX)!$ (0xsE={e\:4߇=;i=bj~kV+|1,7YJ}|Y?-on;.tR~ErO7m3R/*L%YN&*(@ P}% e vF/(0]ksNUwZC4tXg9 l]tU/c s];:PY{e,`:5|VZx!a4ԁnK|Yu.sAŔ6Sin)Ӷj,xZy9EsFӶ#22x}U,]W4YaGF"to}@T5ҭRw6o]zh9[%Jm^; ivjm4횥 ^x-azfOp 7 WFJBnY*_} #q;mkhi!nXc!7.(@ >+ksEsu[w՗/%/^KşxBU+rlv~zr8ei @`VMQCxx8>~-ĘH0wm}&byg@n@Bt/^6:"{˜uzZ4//G7L )r\u[Z)=n`mDH a"E(,'6cɬ85uTEntOPP_ir.k*PoH(cVdř 8HBh\[V h0D71~'46ՀXi[DR/J`Sfx 9!Ag[hdz;-(C6AJR=t+;:TrO1^z;5V`uZ2Bh䒞S}Mؼf ¤gF<3ʐs[ |J/ߚ-]z!Egc8m眢(@ +\svw/o Qh}"rRN b]!>, Y#-wNQ< 0ʳ ReG'a,2[U=Тi"ONeu;kPsm{uB 1=J>woďgTwՃ/FԏR,V+,X6+"#('^nT5Vw)OXpP0暸1+{4oeݴz`7@-5m,1OVxD{m[kKlQݗOgB&$'`= ֡"R٬ع. JIBΥGue%p34vv{UJ_J㏤?$%_>JD⦕K; F_<+5gaN}ndž9裖Pڧ? e9'cVuf6XԖZ;+m/hB<>ZIq~X[x[hEMX13|%DvAXd."AMj_JXOy{8JS! ~~="Ojb#-Sn -x~98X}?[(0xsEt{n=nsE($ϝs`o1]7yX7&G v@1Ǭvg?3ND߰Vl]Wr]9N ) 3C-Xm´#gi> _ :#42" ®Eju ܩT.Vm@(҈[07dH?|XuX:Ssc^G$*QbtoEfQΑjD1>4@&! 83Ka*@TJDT@m< -W8 3XպԲ IErǎi⽛,gD,iG':uiu,ҷ=Xip| :p-rY.=AdQk^C3zDKf8wݽ.>o.㉝ 1~FBr Sn CC,%7=iRr(xW\Q!usn=sE/Ήv$yɏ)@x1AûSO}ҙ1>Ycri+p<2UX"ʖ,cŏpE?Z.rs+ǎ3ĕ쏖5T^DyO(@`Us&d@F^Dhgr[6 t 0Rj gn(Cf6 q̘ sSkgں2dui50 F:$-@GE]q.zW W&q\\ۿf= %-f14\x9ElyNtfB&'ZuoJbIbzc̫3{[Ŀ̼b4ٱuf?J; } AVEKJEAy̍ r)nj d_͎(,YU=*+Y< ,IJ Fl3 >=Qzs8T]Āu>Aj,EBSdF LwS2 Q!HЦoӳRiE ̦lO(n*BΞP 쉗LKZ-vxᐣ[%07uE`3DzKI[Ox $ajƫi8E PDGϬ)@ P` ,h;k0VK3Z,r@"=i/I`mwEB&J٬hnjAG@")_w\_ ĩ yI~cbrR勤#_Zw?iKEF.6_p,E X5q{֖vǂCKx2`<2бj[ ͭh#^LG{W(Е$޻"xFFu:ڟV8]Ymr6qVqƎG@xZ[E, .nϻ6cOt"C --DkŭA;oSނ%SN2)3_v/B!:ԶhٴVA -c,AQCfE |?SXW>-%3(@ \nsn}oN. o+y>;xDMV ZEC1nyN)@ PRAVeB\q5(@ Y(@ P.դ(@ P(@ P@o 0ȪE(@ P(@ PUZ0(@ P(@ P(@ P(@ P@`UxX P(@ P(@ P(@ P(@ P 0Ȫ߾4,(@ P(@ P(@ P(@ P(d^(@ P(@ P(@ P(@ P / F P(@ P(@ P(@ P(@ P@g}'OEe(@ P(@ P\;(@ P(@ P%0daWL@Po8^|w 0^1 a\| 뢡W"2v۞_k馠C蓱HKRBSH+~*v_H+<;:mFÁw bЈv|0FvQ&Ue;0"4t\yD\d>MNC"q]B(A P4cgx給n[3;Fx8P.Q&eq3񽫥ڃXiŔk/͞~im÷"8! `P7N?8,7;1jB("/`.lh8;A!Z¯ CaL(O_VwS1L0~ca?HYѡkx`oW< #=8\CY"(dE^!]w8emāmpEPDE^+F{.cN7ц לy^>(@ Wa)@ P` ߄w 0K@7{YV[뷈Q'xzeL3S_790'ѣ<0gEc?`nf/62 PLG??>U8[U/G1/y %Qߦ9a<~lvbHt_̟@Ѧ:/%WŸ쫼Ir暁4w{4>|B|).(@ g_+]xTV v,7gb1/:87J@^_ iNľ^hԖG+PC;?½68M PǾ} O(4/s?zQnSkm7m/^)Z=}9~)DC-?`͎-VEslbp.]"ט.)s(0d5_?}~p1w A◝:4WQ֪ (s s+:#ǡ{squoV+GF X/b P4a/k;02"߻㹷t\}|3^0ZE.,6;1eY}JTdhuW .~C߀E`[Y8VU[Y傉u4j P'kUJpL0r(@~-`m~d22MDOX]">Q|n??7yiiu'S.ޜlHgEַOa{j? g-Xܟf_{UۗX9JZoU^!.B s n4H_o,(X6gA峗a(@"p nVWW`$^vVb|Fx>s݇Da΋yNKOnVW &O▉V=J>y?r_"qǪ+0F]mmVcLcd-J j?]rf|x[_-(@ P`/lp k<7g _wkߍnoQgQwvB5 ̍7=`,z'`uôqqZ]OX_oZ<6}W[I\3Bp?થ1ޗ,kr^:K͘zI@R̆(pWb&6g_0.߃S&yr0q:.j*h,F'LyEm(Q؇ʅqA˳v_7nAr=W_zC+|ih^bgƊ_i@e/0s 6b~l? 6@뒌3(p |e$b4 Gۥa#/g,N qu|Ы5ꂗg z`v/uYmkF庮L{-nu}pP\J-_VDu ō#ij9q7XZV(@ ;7):磼4d$il\ݩ`Lsl>/MAV6tRxG0h\ؼ,g8x0V9i;~ߨ}J<)Zk5"0{z8V/4+0eMzhvX#eu`#<䭪g* PH5ƿu`M^.߆gP++ĭ]kEv~&eeG00q}JLV8[QEƟ/TG4Lw# {?AlûE 9s (@oe(@( FtNc]Xi a£jx{cVDu6v1TogESMhz:wxY q&]%wzq؉kHnkX)9_>Q2o(1uN8u;=`&Y P忴i2/t`% YcYBE/#8n)iXwLuf9v5!Fyo‡_וSV~ Rrb0]E=ج L3+%ѰqPx6ŮAY)j͍`L{t)Uk-$(@ P vƏ*27RE#M$;:oؤbIzҎ W8XwSQHE.J)?MC2T /Zި L ϞM^S[ԿЄJ1ju9emޚ7Q⯨-Thi5۞L۷iRWL.Jhn%U񯯴soӮF]ZCPSY|DukMi@_t#d(@>h؀:O|J78Lj_ .eeش?ci/WL;/\s;.{cN# g,J%>=TSu+&&ެM|ӁowI˪+?׍̙wrv<󌫏( 4.WYt0|,Wo_0&3E$R/6tŗV/O,yAH=^ڴEw˨xRR@8nlʁy]ʑ^8v\{{#xS8YI)ibB7.\;ϐ|̏-/O_5N5FQ @i\IHHwg]Z۞sW`KRkDAWJY6)VucΒ$[.7'o)7t$VsWhSTRc@ E] vbҢ{ ~ibF @!sLU׶CouxB;ZjA{Q"3R2-"qs n.UӀ.-aA`LF9 Xܬ۟GG1 yXx%EI=L\[{VV~K*ip\U":[SvOɂ.  Wײ:g/ rfwm_a@U.U>D;$\G1R2/k ,x ӶF!uŞ[z6R(+ژ Zk)^I7zc- EфgO#X a#+aJʵK+J]$@$@$` iz{/.xD#룩OZ%LD0RRǒܩssqSeiϜ?7[Kw1+BۍG"m'WrmR型w$nuA2Gu V|98! [z\N$@$@uD +yGۇr.\g=6|g#,qq1ŹpRE\ΡTZ(z98arn^BJ'sqnIL'8lG2C/ @ PS{VD{"JULhZC<a(*&?2pg^HoQISsY;%Z[shU?Q[ We+Y3q&#1..vݬhCB"%  &PK#VZP02we% 3|Y;w#`G>1WU;ZJ}WQ" U*ʠhʪ;B%+Ez)W xwc9, w,M$@$@Migd9Bʒvr]TD+0sES£ر)NGX~Q[TrT)ޮkAzRi* B]L]N$t2(A¯+ֵb~0g1$@$PdU< @pj 1GTE\teӦhNπ#Ŏc}cǠPulEdUz/*;RץI5Q2#!  KZ@2\n8zBc]Ҝ~Ap. 燑Z{b(diLԩ.aOM:o=!7F(~.qӒEL90HH@ 876}"aBwtp0uQ}ʒeder& idQ^z H{C06[T!ci 2va"i4T5@ p;&9E|<1aRw{'BI A@ok}>)bYvX&ء8? YCXKՠwz쳟`>w.Tl!>IH.E̓HH}Mhe7M !qNfaԡOeLk4[iDHHnQsp[+ V0e_Œ?T" yW&|DUGWIff=P0 EW;HH$fg;gW ; zZ " BcE$CeQo} Dm]8AA~DzaG2e>$@$@$PJ O`e~A_$֟VT VFJO5.-\X0i|[W~ûf!hp SS̜HHTGX /_n}=q0V&'пhQ+v|ZtG,Sltl>"D},Q*=npo"X2 '0&hO)ݽ%M˴G-V q|ޤ}ݪh(Q$6^(WGJc 3O[e>   k :xbLlmT'1YY#OuG] HtWKXu Rj g, Iͽ %^Q"XࢊA' h @l:P$++y¬;5H>SW4pwB{uky;[]m+I[ԝ>l}E'p"U:ZEl]*-<G|sƄJ,lK.&i%  ;!~=PVVg e\*G1⋶x޾]?9KHzV | 1 bQm#a| FQ XoK+hi%HH쒀t}'ee%뽶HOۅepOXb ڍ{S)E+nIgJ]g8U1>ҥ1hB vQ%0WBQrU'G! 3~|Rq]5#wK>7*1x @ έv&L@$@$@C%LjtJm{GU sN_0frjyɭE0z#Hu5.xpӭV.8ʳuN;$^0^0L[u v"by r/WD2'@IDAT  -GG锕đvE~?<pFM펍*;0orrqJI|wqzY:oJxXeܳ'F6FrvGt wNM <-ʦĥHH와Bt Yԍ/P^KnQVWE7chCEJ#WV#'[#i֦4_N t!H Ͻ,HHG{E(,۽MBUd1(XI ¢mJWʯ\Vi/+IyOUzGrxgq(]H0W. R}@"K{C hIe a^ҔqBtgLMV=5g;'yBH.͝ǯZ#vVꯞ>9D3!K ݌Ȩ`uq>G 4]Z 1+IGF`X>R= VR7;Yʧ&e* Z+bTu-ث%Ԭ_yɪGU܋B驝f%pA>V05 gh  *){:%+.vj/ @!eqQ!mJBmHU;QJ*G DwSե)KO4aavǕ† HXjp@(|X`V~t)v:FYXWR$кtJe 0~HH섀[U&8V~%O6qqˇۃ&@1ttKu9Vnkɢ%*C$eHw~-r,m_?4m׻ȭ=Bzz{QPBc[\PF_X[tta*phNղ\Ƒ˖z U1jPKHKpwЁ~Uْ.=Bv3M1x?|D*XI$@$xt 2髲4=V58U ?tfS:۹d'3P!HO T/"2-7qayCȡw9\~w-]х*m\p6m!opE%)C$#jpgbidSӉ )vi\Bѳ!{0KÅC E=N;aз0& DLgYLH0~1$@$@$Ţb۷[yF}&o:$,mUb:,(?i.o]JVN/~7D?{ >C.ufvuWަ2;#]ߖF‡Ot T&'N7ϜEPF$|pUwWnV.)7jv[P~*J:Voճ&_Œf;UaMߩ: E; 3O4ص.Zr؁tGJ^o|}$Ft @&ፁϨ+N3TZw+S#̷Q츀s_Əwя0k.pt7D(Ğf =; 1VoaC J)?* D. gp1ÒH*9ڵ=yE8HHH `~J>ܭdNJ`eW71?Au]wǕW?$i NXP~(Tb,^aT7^p$>dy$%gp];3HM'TS=Bؑoo!h x > /N\h*\a1g;y ^>G*T"%T%̆HH {ȚUަ7wŌy/Ruo0F zTk >Ljrj";wEDZq )Y!g^{_*Y-,m۷4mRÖo4!c3ltv HHY5.ȚUb*\6%,ؾ4Xx>Sf T"}}0c5\<0>etײc?!Q_WCh }"p&hO)*-@QSCe%ci_A:HHH k5ὧOB _ O)SL-hEs)(4( U S5\;Uvn?fbǢ0>h8HΑʸ& R`yDm_ 0\ڨ> pPFWO̐  h@zdd*5E<$]3 pϗ“8(^֕`O_"Ib.Te'&8N.`sF/;??E%{:v!wB4om mVgM&V-(ӪHnFuc05 aH"W J/¹W:KU*yܨ+uzl<=P^pèNO|9*׍1[5}0EpU V@Wz)|F&aiki/m;n7@qW!{%$@$@́f5Vp&Ncbl9@NF}~*2!4u^_ F7_\-§sRʿV;DUϐ,"0cPvcDZxsN`;MauSL>ޞvOܙTˠ=M*AKKWv(:-S`=jF,LZ;$@$@$`G5|WYH8cϠw1޸^cQ)h0Xצ+=ab\z' mQS?f0*C^vEe{ yt+OAtW1X((įT)Xwh# eK7I1h%ų~=-UD_^J<>A/Ft!Ca gbb2t mme$@$@$P#šմ^ľoS~W}'0 3QwlMٓq;EJ5o?bQH4'|{*+)hHr0ؾG[0n<6u H5~ 'D(j5AlaIj! de 4bn]o`d*~TСrD>n}w#azdZw.U5_U*e)^@t c:^qPoV p'nu&U3RǦHH)E.x#^ ujΕM4X@Ū9&0Iv?š\>p~{(^v5`jW<]7M W:1T)'k UeFJ\[U;MnU1HHH>x TJ%ҹB\',Ýz"6RKkqiVJ"sB,Fr/&|( GUzgi7_L:Nň]۰%A3)g镦oakvLׅFa>fwةx]w)>a @#"1,~5|AH!Te4=q| VoUK`ٚ-\MbÃLmz3_ēŲw$%ƴ\ǨびC7wui-XL_C5mUʧjV0Hl&mFň$@$@[1b dO5`q@K+8>GGEOjţ§mOX#<: =z:&-4:*Ѵ1O[ pbkt}0cLèV GaܴR1rRj 4 >6UVhcY+^OYsV)↰`0#+gY㩙189#@uۿ{`J4MԬVtzc]'( Ū诪YQRQc%+ eA$@$@Gd<}&=^ݧ٭7UxwS>9i@_s,1~?߫R`3FOhmМ rbGCqV,F 錿p֧\QJU ~5fZIQT^IHH xū̡SA疙U<# y8;(6E,c-1at<5%|â1:* 1ar<9p=[dxLRX4$@$P-4IB! DE eͩMey(H? (ZH60\U$t[U _B_1Xhr)\-Ҟ ]zg*ߪ(.\Sa]bc9瑓rEv: Ohx5_M1y@ 8r$SKqE޶)6 @NwW_7^6uqLJZKPGMD8H5\,suGHW6pb\DžWpl)rD==;zH)2qT-۶DN^p\IBgA9rEPVjmD\8c5Ih#=x+3#Vw3&k/D$Ќ i@<]w邈v0KxiT?>1(N٬d\.*w@D.K}(egJ zv O)3\'[wr DhP,¼o~U3cUI ZSΐ$@$@$x 8i* E@th vħ8z"< $  $cc9J}qrE^6|E-$$@$|Pɪ5kJ$@$Z8ߧJf$@$@$@\6\'  =hڹ2dN$@$@$@$`B& XY @Hھ ҇W jD)'oʄ8G;d{I4oWh'l/= ~@ؿ<A8I/%KO$PZëq`{5vKғhlNz)^zMJVM=Y  A eJC۫qlVoɆ8G;d{I4oWh'l/= ~@8s:A8I/%KO$P~?#A8I/%KOq|G;d{I4o*Y5dmHHHHHHHHHHHHHHHHHjj(#                hZdմړ!                eTe̎HHHHHHHHHHHHHHHHHiUjOֆHHHHHHHHHHHHHHHHH 8r~̎HH %EҖ-z7z$‡?7;p>NSͷ%{#Pqg~W`^)=$<2lߜU;yU*c L$dW.58].rBXLnFO]Lat 0cmI,F+ e+-G˨ >-Z쩝ʒ53l6 k8,baWk;р±h1ymo2 =&{_O9XO{B'ߐ[*p FnM8lW5iy_?ݽwEThq^~ٿ/e|֞ϻR\9'bhu ʳu=NA.uu[B*[쌟yZrEC`p hi43bAL˨ xpݢ F{]Oݫ8posFFC蝦ւ DʾYXpQH\Cی+O6N~2)L{S(Y PJ)^5l}&Y2 XQwp`t37.XZğ%'lg}H:)19/98===<Пb~e-?1x⻯Fa%{r,&ʎxtJ`yj.[̨c[~7 񦢕F{ ?>ߥø_(YqĘЍj* `Ώ̊6t8̒a (>U eq~U}67u4Zo dٿ :.5h % NDw느nIа Vc sꓧU2ӻ;ۏUkEx[VsXZ_œrطHk#zXuyX.CyHj?f, e)4/O}׀:⽇p$ ?M ӑ<3::ڃc gYb@?w3f!=W YV¨{ a'YXe6f |Y5d#Y.ŠDlJt`;YF`w/e?zd,Nw9xK>5H^|@VjowVX4pdo_s] G~FVQwJ V˴ 2a@@?<ߓ^ִpg;?O_m# -=l)(Um[ V}~i}Ѿab c" -?Z~U"޽Ay8"7s |P. >۞c`wX};`5՗2/,KL[UT,1aZ1@jѥ"qMd4V $@$P3#M*g|D +i¹5†=Yb*o^vH4y)6}>m+;"W 1.pR>.\@r[ B4,nxsn[Y]Im}^ M(-$!߄ID3QRxZ1K6=? 1`I8b "=AV^Q>\~oe"+ζ  VR@Aޞ7=ܬepqF7Opy$m7>Ѭi5xK_II]qPF )5pd(y,}?'jDD@| aݼĻ <|+vOR N1aF[`og?1/Y$gW7Ua_Mo#AEZe y[wdїON? P˶ 44oV9 p:,(? \zrJĶgLV=G V|oǣw?!t<3fј-σWq.쓲F +)z  (6.jR]l]ڶRߤj._:&dDtHxCx:5uS0! 4w⶗z?ZU+iNRuN{ ,{!IOc~m/{2\Awq> aNC1x2hJN(1NU_ҴV]ԁt7~X9ꆪ.@')4t,"ag8C~X.S^FBC\?Oቈс)S .K-V@$ |$y\G2\;ݹ<9{ -$ʿ ^im/ #{ùuK\8dI!Z9q d??*MwƸAعF@ %P򓄓q21n~gBђ=J<ڑ1pDhǞo{{9xF.W"$Er}|qCdπ^bK pJ`kkUzi9J鮗ȳ[VDFt-Z&)Y–tՈ@>[ 064H}`+[62-σϗsQxcr7zwV,BBHBB:98kCȖ~-NǓĪC˵Uƈ@W9R/n'3.JCcpb{ӱdS#zHHhY[pt dZL9d ː[F[+8խH>0s]S;nR_tT^Oz&ʵeGnov# YzHjH VR&qduE W[h?AbL>B*`a  W1q-}cr &MG#dS11L~eO5jP$Σ$"e>BqgXNjjatګ@is~X6@ҏ.β _vf(#{V7ĢEO^M^q/;$Pz~G'Q9[H$8mPneFqixYNGAN7J4+|_n1  ܵlfń{זE.FғwR|ΘP-~ zhu/G27(XI RAJ'`K{}-bX'+/3XRO~lBFx8ۊgv 2 ]h?c$%pԇe.fJf+LyXK`wKW-ggWW\g@ n=1Ӹ{Q<ڷG P߷P6,Ō+v!*L<*>.*AqS i`N()of̍RA7QEٸcP$nשVvia(= G_C?Fbtgw섧d2{*Km w0Wc̸``WRq"aE0_)g0MR>S^E2Jf)XxG,2Vwȷk (q=5$G+Le8^ёD+yی$˚)6iȱn iwDh;Z{"0AzrrRխ$̽fUde6#[M$[w`>L^lf*?Eߓ1<|~=5Wu/GxWRy2j,a"9멵ګ8g^ڰu뢕Yw⋳:c %-D w%2 o@# $@$` Ҍ}8L-|\ U7dْ {?=MCzi,Qi"J'"t9 N^]>Nw͢؞m?7ᝉ\V^X~lߛB!3 JRP^e l:iƴOcG2"ĚTifR^h1qŘZʳgMGh-;BNqt@$]JD6j@~ݐ!G-KG)yH}٪񆃫 'F IdS^.O3X90NJ8MُK{-HprN^/k#s`i`gn`ا%|:-6R[(XToB@KeBrbЍlg=A@6J*kpT7EdoyT)X8E-¦"?x>TF*9Cgw#X+::[,<+gUVCXqvXxoKxoLBf/NZ  bUFsVx!7j,g1/L%c $@$`m3&3hI MK7s%RvGځHKԞ .v~=Jd(Ȼo8tD{ zK](w<,|/ChʮIs> BH3{9YWޜMdcA"̙pnE.MC]xw5 GXMCqT, )ڛN.+"GqwC&:#F: #)|;c/3=JJe? 981 'wd,\xn,w2v*#sWIAt%#z"]A P/$=8e~0vdVJh4/s9G~| %žTyk64{~{Gỳc"s\ȦqIH֯:uq7eo=4z6űǿ@>[Gi*mp{#6];A`0˾k8-ԛ,a_H3lS{JV\;-ڛ5 uu)?G8Y$v!TflVRJw5'q8Z:ʌ^.W/YܴaQ$D6li]1 -oϾDbX;m3b&ނ#qV G?줹b5wBiGǷ[{~.a6>ᣆ(tW/h,J@|C 6P)/GE'& EF|Mn\C&sto7f5-xo32쌑짽t?Fy @cOI,z <2lp/"[saf\l){h E^m0)~? _|u3ZW:JPIOW=pĠ?3glGʄoUk#])V8пϰE(<15g}pkxl*I CNu˱8g:"lu[F%l76=3ǰWV V'#Wp -}kgiol |E>1\7L{^X>7?q0Mtr=[QuPllvc2GXenҧ?\%besh"&,HH 8F;V锫jHw!P oXm ƶgz# ؏8qhܿеgzFjP.1Pif&"i''͠ G7*A bPdNJݣ3VTܩAi(zI"VD3NhGoĽ'pų^ڡf9wc׺ Z I"]T꬛V}1Q9AhN`PعX\%Ԅ9*L]5\x V#ӛÈ0Xե:Y2nh58qN38+ִRm݌SvƲCGI{;;UZ>q}1,H7- q~iʮXjΞ>Mnи_c&uM -75_JGo_*-?ƾQ _orAK/&75fmњZ{)ϗZy969Ŧ^ Hy8ó 4W~ett[cXo8s=IBC5j7R/ayLږ^x#'41݀o4HG5 ,iYn8[>(}qn\%(*.Biyí 6ҵxczfO竢EBM簱V7=m<_b,h֡eFeF͐  &JsK/ '2GpwHVPib+1G%UNm5-o X':+ne5@Ҏ--gkLS-A,:sZL#i2/G'Ox&`c{5,^Ojg&                 3de HHHHHHHHHHHHHHHHHH@OJVz&                 3de HHHHHHHHHHHHHHHHHH@OJVz&                 3Zhe&A$@$@*-ZP$              hjQђUSkmևHN8::I̔HHHHHHHHHHHHHHH XJVV& h ˨HHHHHHHHHHHHHHHI_*8 7IHHHHHHHHHHHHHHHH;Zj? UT7IHHHHHHHHHHHHHHHH;*Y5֟HHHHHHHHHHHHHHHHH**YYÛ$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@͝O$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$`M                 NJV?'                JJVV& @s'@% X%d.o s8~4YEpqEH.j[q.8ӳPZߠt_7۳|DI$@HNOą"{Q>%_‘C|ukOKffHg3ltVHHc4xF cqG$@J 7q+VlOȩw=k WH"{e~#rzLK$@$B#.b   O1+:~NUDyUeRn;fr5'($fƍ׮]k>IH. f<0r␰|zT@IDATڶ/$7}k1e.eS)=H0HHn@/W<O8G=ơUbocP& L`GmsЫ+G!x= tTwLXm_ HHTx\ $@$@$PR9q2LWgȭJdT)XS ^W҇ 7W)X Ì3ms'5+Zjawuצ;sm#lȦT`I$@$@DVޗ+R3'6(E9WaokP. 0&pHvE{d~y4 hjdZ! oB9{>8m)r4Ϋ[˰i5`% m.=4! ?{|_e.|fAH9syH)ۊ>@jTK6_HΩfYYwU|Z]) 7ͻ$@$@$@$@GޗMN:?*_,Bq˰^m({v\z ]Ʉpn9]#D1W{N'z]]l[g0}\Gزwd+:Fa]ʠ%6lu%RBw( R)fں..d]l;'NG|VTN$8@WUPz~U\ۯy//?Sypww1α>rFb]~i;A쀿` ޺y1_G]DrmچC絬B`XZuy.8"QR`M8=C6=g0E!5BQ-C\>ý wyT"GE2 z C{XӠ,r F~_XbHW[srmY%ݟ< /"]_w-»$@$@$@uMC9>D-/q O =0^X^VF|!/3}4WxZi^|Yb^Z[{ F kꥅ΋4m2 %  P"  Fƚğ(PS43i۠f]YC !w$,&3lAT@_F2N{Oi<5͖y-;lY53te֫GF -uY%^#7u 2+8l!43V0d'C3e>磊ռi61_zh@(^:a,حeاֹdL94^hz'4T]8li m2͆8]}T>H=,8HwMT>^׿YeC&''G| į%/;?-s&h8>Q9>uaL9M'T5Tn7X~'ˆ_ßN8zL[Y7ǃr1gl+7k6披?/^ynZ)DaZu  #+% @S"P٢Wl/CS|Yaf<"J'I7U\YFf姚)*jaRМL?I,Z^zĩ&rM|rfr֩%i /v" 5qu 팋}ߕSiL: I 4Ct#z* [ -O̕goao[PEmK⤟IQ6iV*;a`5>neWREWw^Z*YkSn]֍H1K hEaAe혧S!)\;4 I12'h+YM\tJSr,UYl2fl[,i^AL1RL0vLlINmRIiؕerJc堲 ber]Y[Ty֝QrtS֠ U6g|م-SHiN kg HÍtJVG-VQ _*M{{h@Zt-I3G8 PL IbQvgOɈA$Pm? H-mkzq ߗłzT5ߗR{h6\N<de$c I-T֞6!ρ6*ƓE_7opnph/Ю;t^ܶynI}1kH$@uIAtHHn;j34r#/\~ Y)IHJZ^@mX+/h>Ow/UUw@ hPA.rDE*mXN;iK;:Xg4ΌOjXc/GcUTaP#T γ5|6k}KezK2S<²g5ypw皍_^7i/+Ua|\Yuѻڥ̞&B644~4ʸPm<2ه&ƓOWhѢR- 7ըl͇r3/& Eh[7-w>eNC`ʘ_6TS/}~z׍Jrޠ>?>C 4|dMl~& 7KO{ K[mV+SK%>[{ȳrڮh$jyӂbhRྸYƘN>%w/{+ + D * @R4q|ez| ].\0xܓN5ON ?JkuZɚ>\aϜ5ND']`Ȝ>{gNU..{:tdOuufБ:;a=0584M~}^tJNf<=ʘS790UDxXɸϑkZjT}5m01o0@}.cgq>2Mc=&z(_9oTƟ )huЈ5KܢYc{*XQM6GK~Q}/(d&L{w" i0QXmSe}l7=Y뿦vӺc.@&/žs]UYφH gk{xfUoFs_܁1 @ Ȫm_jgPϤT} dL ~X9 Q'.1m{m{=RasFohPRXW:bW]C Q%Oܢ#4e]zZZV\;ǜFg/P2_,=h_B NZjPUewLJIטO9L*[\{6T[Z`[LS=r )abvlzJUYU^N/ dߥ\ӆj7joC&@8\&~r͇ʲ_?g1?_f-f[_&h_~EqJ_b4JN蛚1wF"#ZyJ]3 @; Ȫl_ mSZb(TQե4BCZ~k Sڳ^jش&8Ȩn w#4;HeU[_K_-}'<,]ز>8jLzۯCzA[ٹ6ջz^_5e==l;6R޽f\1Ҝ|A4 ~n|?LUޫUhzZk=߶>7sX+=R{;@@ p}>lˆٹp>w1'KY7xk"' Nh9%1^)~|[Xr} f8P^=NB\qxuf@hWYMe@ oJbao|uӣP3=j{ZaE\[{kCvoOZDmSoBVRw?CnS7h֬Y{fT]xtJ%(-%E)ޟ4$mӃ&ȧWb'RF{f?6S2qUu3"l䯾N:3D/vs/]|mr(AS^[!=Ieyḯ篧 q&Rz+~{{qE 'c E&:mW߹ʚ%gU]y}rkChqNm%N;/E1 .aIas|^N@?-.Vq)-QA}s&+oVҩ|nڰWOnibJheDO?Md,(i>#ty?cM e٥n p.Uިʹߵ2 Ua>;uOVUۆ虷6ikx\Yuk ? iڡg~wQJ͚~W7' ֚7za-IpnkoWOνOvK4|;OeK7-]k^ҷ󙘃G<^ST t{|4zթr~={f`e4 [Ѭw/k_ݳ_s@ה m.&"vՐl_@̚kϺ*lMyڎʞL,tvr},h>m&O7^節Mоf?%z% h9_Q{b(uo9(];/}xNyHCN"U@΢=Y0L @s +̗$כ%ag޿N}]՛!ihRLMaL֔;D+}߂ww%Lwo_i3u`Sd2FG˝H嵏i1U&?Fbԟ:|LE33gZNq};% α\^B5eOt-(kEzaҀZr ʗ(gNJy'VoVӴbs䫾O"eڣ9{23tbn1%I~?Vwh6,# @@++l-+@$PWSZ)9U)fPЌ0qUjeΤVzZQ;m/6F ]x*RͫAUUkԩwFz#DlOWjpQ5`ɩiH} lSONSxRC}eFTߜ$zi0Cf{ay7~Α^-*j`ktS;z~ YҊu'8QP/7Wډ'zW-[,t4YæbJu}J6駵̤lmR+8\6Ÿ=]6h}h g2 ` ~5/=_Mq i}>-m+kK;#h]YEi@&Pt׬UXVٙHŗ{T ;4;yLr V{'8Abu;^MCrl˞ZK#nǫJr9x?&w/WQ">T6"y 8g~sIM@#q ε_r|YB3!@u&9}=h]aP~@3, `WHwǎg|% we*:%6W32}z/Q~Ö`-\a5{ @x?l2a<~i@@Ep}xFNׇg @{jOg" @d{ J*{u\PsqMМ/o|s @ ~d.h?i @X7Zꙡ\ 2hZyh}}m}#-I t6(  iƝ:tHGs ҈-|l'"!px?<M ~K@v)a;\osB@ ~sKDCZC@Y )-]fΗ@&ak;c@8\^_rG8=_Ǖ\@@ @ȫY         ` d         C 8lB@@@@@@@@o@@@@@@@@!@U 6!        Y7         *@@@@@@@@@@@@@@@@@@b$&@@ iͫ/uk$e}ke7I[pكVy~WG~vZ*:{G5u}RVWgM;H5b+1AV.f@Sj+Tm4kiO3+hzY5ca@fLU9@Ϊg#YSK@U dժNE@@%)Wm+eJgz+""R.v/*Ymeu٦W|Vi׺k?ZVv). @[z-] p~94  Ȋ?@hBJ2VlH6=s)]ܢ[G)% @W!EwgLJZT;~v2U@Z-|P@V/V I   }V4J wsR4:Gʷ蘺qMzQ +ju|2#Be*7;˗k~k>z4aB7TzQ8ujGecإK}0EcʰH ڱfH5}Z>EWSJĮcՎ5hq^_Y/*ouq f-}{9duQc>cXSSFsiwu05UklݤL9UC-~w6hq'a}Fv<Ϧ긽3'?ߓ؝ϟ)(=tyI=+'߳}Qarg7*FYS'3س؜aa?ٴ`Oaq0_lEZ@^4<yB3熜C9Ёx} hNdڳܠR{nE@m|-+ zzcVhՎN D}=m]Suԗ5r"68jYLN5ʅ!秼${XIE}B-V@*u 3F$O|ϕÃWp?%,v曣L  @s՘@+ ycϚ`F\7^EIѻ>]Ξ&HRCC'I~@fZ6֨f*3gMY*e\4b֘%ZT|'iC]|,';s0Sg$khQmc]S|R]7*qjPRI7Oִ]SPYN=ܢ҄ W{t385[=_#TUpyg}yo@ G}czfgo 5zsNf i='-$*GSWɢE*]c v4j:}YӔ²g5y]ho3kt_`J[n瓧__ed^+]-nr~J 5~L_^@ڳgsapW2 иCA'Y1P-S8eR! @K>wpR*ͷ&4XC-Kҹש\N^zvmFC_$?L+_ VL4|4 ܬeZ|V.Ҳy@lbS[ 1hOkoߔR;T՟ȼVrвU_'OL!bs~Ȝǚj6le_}OxAZ@yE y/?s#6ZoPH멜kb7YE^Ns{muƩ-(jהJ,6@!Fy:6`GYXD {G,mIȽGf >a>[rF@9%M}UcMdךS;ӧfL w֦ /8VE*=UMWJjؠ  _X(',:FNy*;PKRfxA3kT[۠tS3`JgspY|cdLI֨Udn] ]O9 X 6m4)hWϷRU@SvRL?XG5bh;Ƿ>!pBu4iM +,f)/D#|MJ+_-ю^P9B2b@jͰ@ܹhhπG= ^p?<$@ t<  HՅ[]OIOT]RRRI3O`^&Aɾad(Y˻׮j;ͲT ;lz`z9 eK=u{3,4N4摷=gf\~{dw?toί|͚5KSzO>aa {/Oֹ(%}UZOz_pT7-fN4vTמjbU&VxW5OJD[.͐iJ]w,͚1E*:$t $$X[}'[".Eμ@b)GDDJT  pI g_:_WuG(kDCORUc'MSgoZW͵XWAOkMpNc9" W _J5Eq݆- @[H~x[?@]h&@h_=U{L\y NZc m9u (cU)ϲy'+;x9ɤcx\BT.p<(Ů}MڬQϱ)YrVf|Oy] bi?`H}ծlzb!YmxЏi2S6+&?wωP͵G>9A:#VY< 1q3ptSR9%>?i@Z\oX\oZ@9*_{ez2q/KHQ} ,eԁM+16s-Rycd?r |풻]α#Yn={ۡENWf?kʝb_OJNR5`dw]M+خ<{b3N񔯲α9Z{%]kMf˒X>:DSvktq6/_ EԴmɎ@*-z#!k*\oUxEړ@vSH;3YV4֙=$GC@$0qDoq-[vՠ&wFz̡j*u$NNV w+tjTctΆ:U?4p?/S%Vh5TiMwsjZo%Ty'5Sgx_^1N,H"Ul Z\i3i|ѹq4ը쟬^J^WedӦSlb :Fͩy`3bB+Qi9?Uw-tlCh\op?D7LH VKs?F y PyE> ;h-5$ ˪5;tew|R!}On)Rg3>.R! ` p@_2~xe#SPO@h'i@ &}.!;vL#Me@څ@J *.,i=1K,ZF، a\o@f~x3 S鉧  ,0xt;!xK  CNM}olB@(F@Z@1%`;         Оjg#        @Y5JD@@@@@@@@hYO@@@@@@@@@Q%"        g٧         ШAV@@@@@@@@ڳAVSw@@@@@@@@hT FH        Y |;        4*@UD$@@@@@@@@@,@U{>@@@@@@@@ ȪQ"         @{ Ȫ=}         d( @@@@@@@@@= d՞>uG@@@@@@@@FMA@h3:rHP};]ςwUݻwZ     A,QF@u dպE@Irss#ѧO .Ԍ3ֱ     @k~xk8Kh] غE@Iz4VZ&@@@@@Z[Y @ ȪeJ @ :v~ `mXi@@@@@hog2#-[ ֖]nJ (0sL{hyYsB@8xŽc1!     "u@ZAV-

l@IDAT      @; Ȫp         d@@@@@@@@@ dN8E@@@@@@@@J̋         jg'"        @bY%Ej@@@@@@@@hgYNu@@@@@@@@@ 1"5        3          AVy@@@@@@@@ڙ@R?|I?~T @@@@@@@@.@U @@@@@@@@ڰAVmR5@@@@@@@@h@Rӳ @-pN'ֻzC9M:%Pu?^oO6;R|5:^w[N)=qƔͪg:%JR@@@@@h9'yyM1՚0:SN(EIgy:|~ҹsz?~X֖NSlvJ4Yzo @'Z7^nkw{]:݅Zܝ&M~vzם+қ/=X;˽{TZ,%wa*> Brcځ@X퀂*"U!LXB c}j{|?>ԃ!#'3LZ=Jp\[E gQK@SĻoO2p?Exo}Ou i=D<\l(?ި#/:Z\yz/şjկw^5G'lվ{폧A@@@@@ 5qSf͇1*0dߖhٹ.lhw|{ 8߷r]6;eX4H yrһ_H@ FK@.$EhVPϥ}'ڿ*'ڽy.oPEn7Td:9iړGvk5:WqK]y/UNAI#.ު/U]CORIJ0Ji]Wa{xaoOVM)]ӑ=][t׹lrwu`9F9FZXq8S;^|Isj+ʿ:Mo}LۗVi}/@90:QD+W@@@@@K [aVޡљâ}`bPv@0AOUh*LWC5<1 Ǐ~{>=t^zx^ 9GS:vUhirv|]=֔MӇc 7f;'wk;;{Wu7<Ј!(緻V0$ @UX$EZ@6AVv P_(d9 SN_:YiF3-yHͺK5PqQƭ3$(֠ԈyYW.z\qWP5~ z}Tfܧ" _{bZmqyxגk2V^eGYY#o}(rY@@@@@(paMjJiGLi ewPwΚH3uOK_N5T9[Þu鎙%'F;E~R[{`cv6w G}c%/!?YUU2-jגk+5i׭T 5AV#בCNͺ盙zO0 >∏m0h(4_!Q#Lf] &6tb?i/6GOʂGU9s`0di@V$)R];7tW#0TY{-&`_'i.׺\9P6M ȵmy[޵B:Az]74ux+>q|O>:ޥe B2KtP?ҕ>cwsRD^|kn#;$%~S|Nظ ,     nC_@_=E(0TY7dHӱ搹cZ zak{;s{O ׶>v?;*6\O K(-ԿM7hΆ -{InCoAݷVE|O߹@wdLH82SbD%d\xU aUs؜eske=o  7!1b܏ -Tҕ16tKΒ&d 7#>e?Zܿ47t{C3bt=O=G4/鐯&O~FEk곯kƩ{ts*ӵ 8j+T\_iؕ+ B2KxF,8WՁqdNRMo:+^24Fk&$np#      Р6|ڏ+<(d@3]g4+xWoɲjԻu=޹Xw YUI [! )2o)Fo rm?Ss?J?jʐKսs7]p5^=@_|!ڵ{vtAQD% XHn<"ғNڷ e? d-;1[@ZI7j{!G>vg7BMj6賥5诏SXҩFz;)_=KuPRM\*iJP+?*{ /\]=9%w*'z˟KK\Kv.a65Csx>:p>?WvbZ'v~7ܯ%kk4ObStfȆhyl[sǷsƽg9u- _ + @2Q[ ^ "٠O@(kF:5-Ep+81,t 9ZZ҉/ǿ-IasǤ>VuU sPR#MҁoO1"     @ |2sMMfpyazp Fu"\S:)'-OLj=G^? NYĝ:~ _'I_q 84?)pkyXC=K0箨ӞǔCU7osYԌ؀mY̼[eAX$weFTMBþ6&ū$lkv~ia룯 tA뚶P r" =Nݺ*}[z0?Mݭ-:fv` oT7#1s      4$ 2]Zcl\Xck˖l 5HI2kucFmWRA뚶pL aaqNuv}{w8ʟhl Q՗g;u=}f^]eR>߱^u^"]'LW_^^+ jzg\+K&͑G!     @SR3iJX&4F[k##u[CyQ*wЧڴE=o7WM9ߚ\Y׎ dyZnmARǞK#IV-=nU|V<۟^rJbU7gs:_4NZJgsc 9򈧬4Ze^@PS; ͂e@*`.jc uXLӖ? s췕XϹ1C4'tNzDzDYZXizO{N_+3|+vkI,ޟ鬩xUn=l/|Q-y_fH~fӸ??Q'E~BY坱>/xy&`     /.V;&kFNӀ΁E;أ^v| ]~n~"M#]oiĸ'/w 7`P3uѨqOK8;]ֺ0p{FfH>v[t1_{~I'٪>BY坱 2#C\ XLFhvL@@V.`sK%w OAuġTdz ^ߨqSo ^4^WݻJ7NN ̅ޝo^L?JYǤ.zwQ:ѭ(c+tݼڕl8WHsFާ[~_яgv#:5G%se      H+.d)@Lj)@#$3%]ft.t(@ L7|FɊ( `/&Y9p(@ Pq&YO^a P(@ P(@ P(@ P(@ PdN(@ P(@ P(@ P(@ P(@/$ (@ P(@ P(@ P(@ Pp'$+w:G P(@ P(@ P(@ P(@ x&YK(@ P(@ P(@ P(@ P;&Yf?KxuqZ)יu[4V/?$q04^ Jm"?otbK)@ P(@ Pp.TKxuqZ$+ǫ{J&XL LIIť{p8ik嫟 HcAj J`&F+(@ P(@-\KxuqZ$+ǫ{J&X՝ݛ z^&WK{5uսL~.eN P(@ P\ 49jM8^nppM8^np.dՍM(@ P(@ P(@ P(@ P:_IVo3P(@ P(@ P(@ P(@ PXIVxt P(@ P(@ P(@ P(@ Pd<(@ P(@ P(@ P(@ P(ЍdՍM(@ P(@ P(@ P(@ P:_ OoghĮź׷A-r0N9 oMe{S慌O `yb EYTmy9R,[BQLnwl۩ʭ8}~o@QbYiXf]n.r]7k|htHl[Nװd3 t.j嗑 Ǐ&ᅋ!݈u3?|s 7Q(@ P@ T_(_@I<_B64F܇1r6‹{߷]9ᨦOާٱcq2vסĭ :_Ͼx %1F܃Spoڸ~)ꁀL?IFm0wX]|A_jc{!ĺ9pqWXo=,~TGJxtKc-M+}N"LpS%MF20â\m{˚ O~ [W|_*|<>`ǯpHm*-8d˯ƴ{\dT 7:e4ذ[˰e.lOm-и ddXorX?$+n(@ P(@8oR~4Q6<oOD~?6^ghi9zsƿb(>G$ e2ʎbwsåucl+v :50Z/y]u9{*~+US?Z6\חV68x=} &cbf$+$b|着/܉r|鉯o,ƬG-e S(Pw)Iyng+t͢0h?oiEaZpLؖq+>-Ɉ.VzY7EP`b{؟#8mB:t;p|LJrNZ (@ P(@8=N+e`uYwWYuonǴ3(09l?sá"m`u S[x1WbY}'o||ox`5 y: =(8YpL=fleJ&*6kJK׽fyߺRgXh)Aʖܞ mŢ/. VLH4~:^(L(TY,9w_kp'Ht8y @?+6(@ P|+p| l3\1d+ Vқq3 y},<-'o^< +zO.r8[E?M}{Ky~> _ 9\*2D4R0Lģmo~'W?<'f I$XI^<͂j4pvߗeݖ5ay[_+\oĨAWOXD^b8qIN /Mb+ߵ7@qu?N>,Z¯KZ"\77gB #㓡b/'/պwomKfGiˈQ|߬>߈c;+7 dC;gp>4Eaq *3Yӕx3$/!UXo؉' 4zӠ?c[1T&MރEh-(@ P('+ɓuQgJ'OK&j1q-6o<%<-у+=ė} nY=r<[e__ d'YZ|zo'W܅+BbODr:l֮Vȃ<[4W۞A"YX);CࢋϢڜ`%|wpguv@˱G$ -9bN>߁zyh'4^[Y5䭟\.?vB{X[û_nȅ3Qr@A^޴R?u[wv@s9g:qb~.hOI P(@ PWpI<-I],' f]_{J37OwSsSoztѓĝ L! ;mUrN䋙8mc&[7e| d8Յx6 <QIK>f椐x7XЀ$!%;̑{ٟT7L6fz1fiRo@=IgF~ q|\s9DcTg +_4 U)r`PL,/ :AT֒}E},u9n$Qͅ(@ P(@U.ǙxZA"G$=gΝ7xNnԄ< vah~ -/A՟3\SV)RCw~۔Tѫ(]7%kT|#m?)c6|Fc2ܩ!s)+s_6c!?UWW]JQ^Y-oJK帅(@ P(г:C MJ0ZȤ?<-׳.Poձ9}Pd7LC?0/zl5aR̝p.Xzx<}JM8?8r$:up;R4cf{{{I]'NsnG}ۃp=@e,A9VTޞ6&E|k->p>p{^_:NX6Wn{7Rk\ r*?A5}=J܁/z~k؈IAb)^)O ޚj!}i[&_.!T\řg` P(@ PbPJM _C:P#˒/<- 7^y\RUuUZøf@v |X`=Ș <|_ z ߙsî,d,)[/q>OkLxM<_0Zj?%=i eBԋ ޗ܇ 2^.1`6xY0pɋx<yԌFHCED!"0>fne#yy (@ x-^ x,`/Dm>pE'uRHngmt d) {lA:" :g:Sgϊ8aw`7״?ezwXnlq宛 ~8HOlp 0Ņ/ߛvja`U )@ P(@ Pg^& xZΟmvQ(\:+{ }c3%h,=/?jqnWFk(@ P(@ P(@ P(@ P|/$+ߛF P(@ P(@ P(@ P(@ P d@ɮP(@ P(@ P(@ P(@ P`MY#(@ P(@ P(@ P(@ P(@Abq՟ W(@ P:I?:錬[)SȇرCY(@ x$1%Wx(@ P~?v&+~;I P(@ @^5l (@ P(@`+Ɣ=(@ Phn-Z_uP(@ P@?x6Jq7(@ P(@ tDX P(~(@ P(@ tL(@ Pm~hۈ%(@ P(@ \H3Y]Ȇ\(@ P(@ P(@ P(@ PQIV8*l(@ P(@ P(@ P(@ P(7L`C(@ P(@ P(@ P(@ P(@`? D P(@ P(@ P(@ P(@ f( P(@ P(@ P(@ P(@ Pd口6Q(@ P(@ P(@ P(@ P~#$+ 6(@ P(@ P(@ P(@ PG&Y㨰M(@ P(@ P(@ P(@ PMK P(@ P 444ԩS6-;so޻woDFFl P(@ P(@ Pd\S6^ފQiFq`]31qP#Uk3$nQm3mx3`8̜>ޟ&I?GͱS8^ѱ@Ee Nӏȋ(@ Pz-$'';4h<̞=fP(@ Ph_Q cLGs"kuE|G54ע|_?J1`pmtx(@ t#Vt:mj5enBC#&Db~1K&sz׵so`T1.%A 7VFaz/.L:a˱&n(@ P@SSf4Umm-w=(@ PZ`ʔ)1;vX@ c;F\ڢ=5=طdɚ+fXgtؗ72ę*6?Q32ΩK܅>C)n(@ t_o--2u-T}:CX.6x}|HRP+hh`x:LJS[c**>iPkAޢXJ(@ Pp)ij֬Y"Ie`G P(@ (SՖo"JZ9 x6c+8׺*s|[jD%*陙H+1-Ќmh3DgnW(@ PKoQ?bDSCvY ;fc{u 6ZUpȐn_%n)+ lgR!)hVXj׽S̋ƅ(@ Ps]2H2\(@ P(@ J/_Ivzayx(a,u3 r1ռyn}vK2V/%ZG5 ɹ8Z˗cIV2e 7(@-kQ ř3۷/.9:Q, |Z#.&ck+aY zg 7|(Ԉ' 4/F_qqI8ܒNeǟLKcpngj)fj,[D3hpD$~X|w )CF݀AaZ o^M,-S⦄}aʅ̭|D=a(‹_h>.H<Ώ+!4wXܮ:q2b`<0J<9$ ǍA;!Z4`XҐ@x>Bz4CHYzAx7ųs(@ Pa7tJ>|6 °a B P(@ @[q%ƿr*%nF^Σjj4o,x2;~i}[Wuzya- #y~'pPK@rË1?M MMrι=]=>>U`lMsY'Z h(Ve[ZsJ}ei'T*_GDԥ!>GiuO+ւZ}_ZuR"]uo#U籛VZujZ]kjU_*l<9)kթ۳X[[=Yv^gׇG],c1 m@zN]kfЮ)|C P(@ @6ߊ>.(@ PrKŅ:OJǍyzKCu:t Sn=Yzߨ[b^)ywQlm]zr–ֺ:&-(@ P :@SffV2t)0PTuJƪV",NdLiy폕oTޤ/]bd'tHeÍ90:c-**ZcO۩MEa9T0`tThm?@ښI)J/tt 3F-vL9L:ӄ7;7Ğ'%̜l/,@TvaCqGz,.s絧y GW$B"˅(;%oZ!ZZ Ru>-@UYj,PHn-&&j9КfWPz>2sk>f&/wp(@ P(3Z/" ui P(kdkQG,qƿXU,ʓq+3++ZR5G}>SؚNPcY,Wr(0*&i1 bV'1\Ukʰ65YVmcVm$gc41Q,.1 ?;\c^Z+޴Ӧ4M8+M&ԯA HYLҒem}%l[rS LvF^WK_c#lZ06[^?_,%_cU ^G| 0׈ k̰*m )bƽ= S(@ P <8Y@Zq(@ PK_J /7/dk4|qC}[>S|&ʪ h.CZ,^p>+k*S@f҇iEE}ypblKE6l$7S%Q,j`M x}-߹{QlMڲ7^Ӫkiaёu# 6WvSݤ4@86mɮ.o3,r glkݻvZj7k.~ ۘi}{'LKj tᴅllOs֬әN)a eV# KZŌ`fqXe( Pe5k̓^(@ P(5k^yҺ (@ P(XŕrJzl Ne9- RvX& &GPfbk6).}>&"F"Af%@ciU P@7HKGv#54u]Mb'%ƻG˭_\(gO6GRs V09+ڂy;޷Shamߛ;MQ2vH MMyuuuhj]Nu=]4hRt lNq"?^ZPQ-f.-R6 ')aƟ4'Xk$Wy3W(@ P(@ ̙3G~d@i (@ P(hǕj_R,?O\D {-ϚesTad'iQq&σ fD\'v(@ P].$+z5j =Feo%mJpk 215FIwhBbWKq2t#!y;sYmśv34o=5`T+̞sYľx,VȑZ? e\äk:Vy9J[+1MxT7Ek42&Zy +֪6΂#)S7*⥓HKǶg CsҜr رAHxZKjw'ϵE׆kfꠌp+;(@ P(@UGŅ(@ PloJ1e*%ZfkQ&=WnÂ$5{[8k1O2F kNCm^)@ PDCKS!/Ng^ޞk:SukrLj!}-G luYlS[M e9ʾR>Nl }-@Ԭ,makUdjrwn>*Fz5cNl.Sw4f;q1*# M˲溝GmXIh>x[k}n4P*ݔٖ-ZӵUW P(@ PfUB P:K[niP]!m\Ik#_J\/)~)-^ǿu]JIm..#IcC\ݘ}l-9G-ڶ(@ P 0-SCt¼싩VGDP*{74bᆵrh(YV 'Z}`S[ݙougfIJX^hT}}ĥ&_kĵۜo(@ P(@ P(@ tD`ʔ);vH5<@{JO8N{ƿ_Cv{\昪]1G5Qjb  #g"VB/ck0_)@ P `|dBޱF;)N$LJycK,f*7ÈD!=J7#A۞Iʾ-s̱r_ymyᐚf8Ub:<(@ P(@ P(@0ɪ#z<zƿ~ :ւ1CTC-+0>Ǫch P@.=^u1է3g`tdxxH:H>&>-7(@0Jbj@j< zd(@ P(@ P(@ Dƿ]HŞcLP8${@=囱0y9ʺi9%X9/S"wq(@ P(@ PLV^8(@v 0N8F PLꁃޱ.P_Slj-@%Qhs(@ P(@ P,LXp@ 0<(@ P 03A0 .y: P(@ P(@ P(@ t_*)@ P'pQ(@ P(@ P(@ P(@ P(C&YUQ(@ P(@ P(@ P(@ P'$S(@ P(@ P(@ P(@ P|ɪ(@ P(@ P(@ P(@ P(@`U){D P(@ P(@ P(@ P(@ PIV>hU5Xv-o.EsG+SظAAAXRbŮkxb̛ EUޟv6tz]_Z:檚 ,P\r֬"U}buSwQ(@ P(@ P(@$qƿrzް=(@WvxrZ<ǰ0q7v}J|x,c!u]Z+EE.Ԡ kܡ,^1%,}HL޷]gijvFQix WkS":iB-rq-x_wQ^ RC=b)ԎvFIoVJGIj͐K.slku[(@ ) sQ(@ P=] 44_~n&L^VVc +Dž $MM ccm崃XlFc@v|s IȫC螫GW4qƿb>},ЫX\ճ(@ P/|˃KB#DEjDx!ݰjBiize&s_XAq}y1 mO~2[k3,A45܅Xf@.}? P(@ P9)5IJOrw ˻Ӂl^E}b#3'_mT {)o}&<} BNU[t$`cg d,_G(@t(ɪ֜6vբW01V{$\#w}O8z BCoeõ2&TKqSxW(y+B Zv~\ t!cq n0?bɈg(hiw c)^z|.%J]8իb'ODuU.+m:{)~7^If*)< Mq$Lo%q騛0>&\a(Fؤǐb;CU;?).*z*dH.&U`2JDi.~m~kcM9w|}32 7OƩV⽝;QyLhp nc)ݙ-fyr嵈dD}Ք|*}|p\j}]7b׻o}G=ᱸ5 /$"Cqoa2꺛1u~MPuclW㥥ToQ/UJ;A6P Pag(@ P/o@pX(X›XX7e,[ P=BRў~#SZ6ɫEMY{;Q)HMΖX5=Vj۩SȨaX=u613s!a$H?E?uUg/Bm): r!tv/:u|RD̂$U= 2)ȮBsہ:,ott$-*O+Ch:6&->ۀVcJY9OL:UAC<Fm,̓e`|붯&Z<5}V!^[(?~jj#.Cf>'>y0N"wf g2UI18;)9y"1٨=0ڦ3J?I)ήx0`zz5vAVk#6'Oب5c]$-;_uQݭH)(YP]bQx5$ʊkbdȤN5)I>Gx|X7(@ P(@ P(@`K-v?ſJN:J%RZLֵqtEg:&&12⦌)SYuX̴4$jⓑ{6?aIJ́9" FYVXJ)dޜ`%e{sҲQXh@VT`%=TeZ9R|6W`}xeh{zZHRt}m(mw^*$t#Rۭ>wڰHoNNIW^}04{PdYJbx:KVnN#;CAU$\}Ȓ`\C! J%˦OI:ڒ%q(WQ|htmZĀPZښaNӑ/^KŘ1j6J7he9J Jvň^y%Q~^Z$XŧfDBRHDNҧg{Ҁd$; w>l/ބkmVMe.w1(@ P(@ P=HaƴI=*(@ 1ƿk//k."T-!Kg&={r 0*:ڎQ,M)ci P=JC3Y$`TyFX("=uԂ#X;=FMݷBȈa 'Y̜7*7C|?p1YHZTny+zٌgٍ KyKfnUDL9.X 鐈%ֈٯw:ßJ 1_z^`o=[߅ G] Ӧߋ|1( lN*fjZ5_*9u樏MLĴc[;Λq$ښ) y5+6e-wצM}Mpׂ+e}W蕫,>.OznSŬkyXS0g Q3mX"fxqypDO^*^ tOu=p^%ز0N?!I+E"b___e*k,IPIі{oѓc -G^8[I16Dtьx?Of N P(@ P@u@:](@@`kaK)0ۚ)ĿĎ/GVu%Cjq 3D"?^Pg>.'XL&(_H:P>ܯΘ67HKs#6N|(J_qj2I;.Ē<VI_?忸М`y"MʀFrƃ͉pNV‚ڎqRtjVSUrWpzmڵ։yJ],+ט m)&N G2bmmg$[cGСQӂ?$E, zmz Vj[GMSYu|I7\h Vj٨PDiAJ2ܺmڏIѻFI[P(@ P(@ PҬ~gy1ƿj&qj4%=Z4`IR+c ss>MvDZzS>h;u8"z7e,jpJ Pg$9H̐sP__eBر Ą;Ύ|NٚID-qaWaabgLFPPB ,fRh+ _ڎxexZcrE7;1Sb6UxM{:&,Pv/m$d]0xvp@Ʌͯ~Oo>Ύ0SCdĺ&eWY 4yd2% ##4-~} 杝R{u6k恃G*e<}Nn@)X\jq6 @}d`ηVb/@ +gw P(@ P(@ PR1wnv6[ƿ܊=ƓBյI׫q 9dB$cZȣ96y,кY-q-c^MKs6F P p:1ɪE܍QH=+׉둚̘㌵ooD°oOX8HRHJ+B94Jߜpg,&,#]mvləg0z;ObwN- %,Aá,4(BIg0"oR'@H3cWm8aAQA EnPY> uۡLeěU(̊%Ds(@ P(@ P(@ P/!vq{g;rt&V}.[cN 9hc)-&8nX(@ @%Y5 SC|z.G`mnFc$-MdKaKmt Sh[i%iChmiBCSuuMX1,Oʼ\f4{l7iمyŦ_]rvKQ*} W:SM' 6G:V6^ހ䥘rfU27Fq-)ח\o CS Z0o*CeINjϞɳ^VDI7]>vUQ@k%;GQ_Ҡ1%_]=EtrcXqj~c̱1cNv? *nXٍ+zoB-s֊711Q V?Gv!0PTf) 25%pd2]hCDXqx"?[MdiFOV3U2°Γ|S=,-uX5gɸ<\6Sv-z^|ZG *,Ϯed2Sos?3յi>݊k!|O\eP&KΤkOk?&.S^-!^s7(Ī- VjgV A:Ly_Hc<#m,cװϯ~EyT`꣘ ;G(@ P=M)R\;5vڿ[5'hu*WZ,obi@{㦌dn(x>M:\o46YdBk2x{o*gBUF79,gaq%[F477h-ZQ #j^ f>R[3Ay #* _NaTAhm5ʜOͷ+Oyvšö/Ļ#mn)nNsWeX ^,6b´=HphHG0Јg2>ؙCx# _O|K'H\YF6(@ P% /W bKD,;Tm,br<@ `$)ÝO.V},*741B{㦌5O P `|duFI1yM P2a0Ǝؠ 4[ GkP.~kAFt?u)?1u>@$MEӕy zD`lHZ +kBm=f#2R2K}s@tea)F} ?[PMsߋFQUqpٴ͐ǣ?n]u˩"B! (iBD2V)~mIܲ-6;1K:lўq(x بϞi~,}862}Ptz-8zqHQR" 3;+*S.OS*ׇ.:Rӫ3|#QJ9r"{ yĵ\CZV g۵k\>gMg(@ Pp'p[x+&x2~UзjLru[%&d|2Ѫu؀_/| Ҟ_#񽕟C(@ PHwֲ 810CWTcMV"*&jhiO?,"T2UL:6Y,n*w4m|J P |d5jV:& lXLǑj(4]2 !Gi>zLE0E2eLm(DZXR4X&H;g0sefr٩BF)#J,Y X掩+cEPUs2kV|LT-; "<(+CVj(ӧH)[=~Y;cH ̓աCf~y3x TX}_qvmJe.geV 3Y(EL\Qb?+,s"^P| HR/g^.UP@BְK<M5ktan?V9[YyXiJ[R<Ͽ"ȂJ#X,|hnM3*|Z||Ɨ @IDAT4,%3 P7ۥM?UPpp uQ*UYiΨ ioxŵkj_*Ԥ pml_p榣ף:6:(ӴYD7XQ"MRᡣQL, s{U0@h 3Tclڱ˼~"% 8l:^*flUּBCza ^˖yyoX1[u+>|FYfյ]osxF2Y2-]E&a'L-S~5i9(^Z*UsAF$딼7BӵeSg:`lӬ#9y㦳Z63,wuǏ[GC}kwռKu 8gC/AKg++4ڙ u|m6Q Qy Rk֬:A}]>57h |ZYqw>KjsS PASN($ڄ RhDW9@[>ѡdB}H{ukJ6`V*J-k0H+Yy }I"3>V݊ҫB@PCJ6JUc8D(6,wű; ڂL9Qeު@@zBԫc6.O 4Mr! oOfkSĕ:g 1?#5Ux _x6e?) P:i+;+U}F -9} e,S޾6UmCLMMqWMgu1vXl+l# Ao,BC39bko3kvF/JjYn%9wd @@7>ꟅUg>geY9,|\K"@-$Y]Mw春ԷMR&RIZ\-aگէVBK45)6WgN>kc/@@@-Ɩ&1ԲIt_9+O<S3LY;g៻ˎJ';j܏ʙWQ{1}j\VYQQ  @u#Yjm>;Z2ख;d-XoWٳ5[{gNE /: Ygu,{9=RkL/{;O>dGY+h"x:{WFْ>qJغ0ґ=.RM_ka*v@?vgkwgQ. u/_u?D TU瓬E ՂEzi>8rD j !tu .Tu\(  @qdSLfz.t'Kޑo>zDUb;RqW4Ryzů}BS5`h}S"*LLj>utq͝T T^y:/ђ<B !XMΞӑ na+9;e,GTKfz_UX˺;T(¢.Fz:>+kĶqc#)Dy8  [2v;>;{ZF@#GJf-Gu:=   @m 10̢OGGit$; R~E*伧p\^\ٗFvYvGf׃goҙ #scy:򃢃mzyJ[pZ[\ ^&h,N,4\c&\+RV;MSOs .gIDg Xtxw mܡc<^K'c@@UOI- W-b  pQ孧pQr3   t50v\GzýT`ƙuΞWSRΣZ+]; V hN'j7f%OqsZ!urWӐk J?7T+9;)z{=rfhg;OחWr+͟nҘ.-u0TuW8y܀,3q VV@&Yd!   Y$< @@|B k=rX8N lRq'8es+U%b@{E%ܱDWWgƞz^q7qWIWWuxFx|OlUV-Suͳ.@@K/$F_-  TZ+MEA@@@C4dXolZ2*LLSܫI*b@K٤E˾)@@pft@@@jPn%^ƹ:߹x0*ܜixri*^g䏅_z-7lX0PG_j)%Jnc<.xN )77.GRήΙVuXun{I     'P_@@Ikk;\ږ5# -hk<҅wҔzy}mfzQ}.W9/e_T0M[S: T%'ΜzwGSo{ZsK+vexY?h^|Ϯ#8\gmAw77S&PA}&o61ܥohw+>aWiVgOk>Fw(H#Jyzos}±SpRSJ4PSb8rY^ҸQ7}5ikk疟cv}]M_'b#3Vs|I@z!0$ܳ^N     p @@AfQSWY^n+J[7B}A"yLs']I;:rka5grSz++:@I<*8X'eK.b4v@BOѶGJU76Ĝb)ӎXO^ܝ,i]**5@/ZE   \IVǽ   qFAh^u%8Y[KHɂx1/)L-kngemV$yl?zj=sr yĮck~_#uBmC΀G{I͟nҘ.U1yfpz<]v/>+Y?qƹ{u򊼒JW^UPK  /`uJ   Pm 67"  %ry-=N5kRzkonQBz|`hK/H%Bi꿺kưx`Jn7iHh~45N}ek*,) Gɖ U|E Ԗ9h;]9I.qt% >l}cmS~{斱k^[, U:JiڴkR7R@@'F铱  Ԍ@f@hРCKƘEYY2+~ o I5X ̊}uU u*uVss&{G)."˘]j6gꐙlA)D[zfRS7t\hpfӷߜЎ]uf~5CtKx :.(@z'zקڡ uwmm6Q QhR(@@@ xvu   @P yT55R.QQ վS]j)<.\5QGs/?ufpaC@@@곀 @@@@jZ`ZΦ@@@@ 熄@@@@@@^:͝;ןB&V@@IVU8      )`h"S#  @= ɪ (A@@@@@@@@ ɪf= @@@@@@@@@@=A@?8r=H? E<XYx8d\x8d\\d;Eg#XTAA]!$$ ?8q1^~*!"  3Y]L'@]#Fz5VO}/y!Xcx1^#_bK-(<+8eid=C`ނl   !LV1D e+w;nWa3؝`eq2^~5\b/hyx7 P@"˨>MBga!OE   PHcL@@@@@@ ;Bn-P5  ԵIVu=     ~-}<1#  @ +U@@@@@@@@@`&{=    8x܈   IV2Rĉ    >V |,*A@@@j^$7F@.@/Y_!#4~QE ز4:ڦ_de~Iiz:P VQo35 SyU8Eik6w^UZ/,O)C?WHJլ֛ =% ijFiMAgg69<5Ps`ՑgjK>qN]Eh̘)кwid?=J횿} 'X^?vF6} OW驾LN٩WՊJWv^﫾1!ÉN!Q4rH@@@jh܂ @ j-4bwjY:l4V4u~4D|4*l6g<}h~?FpKי@κݺuw^o^y{mQj/:(Ҽ\ˬkI}S$ہze;I*'J]hs7)u54%=}㽯 ʅ34; fw@Wl3o΅j}:t~6u'ZaHj,|sϮN|9ϫ6eyKbKq%Z-ygsHU@@W|Q G VJ8t%[Mn]o~G/Ficqa~׋+Of;zK{hktŰ"[e]^ S'=*DLA_襯﮿>D\~\Vl)B2ˎQ}CmYZxZ;K^?xz'{eXȗq8McZioz|Fwl?bG{MJ[}7R{7>7SչmgUt}m5eiM&!c c_@\pB)Тg VwL/rkD&=>q   @ dUc@ T P_E keo;P/fߟ8#VPjGȧK4~DmiV.F> kG+ H=;_N~{\Zu 7ۗ3 hX~ZhP'g68a9V>!YA!%Dᡁl^o$_?EN4QjlOk깔 "wFqGy=ZN*f h3jw%@gUn{U=9YT{ ۑ=;Zj6{}Wi(=֕/Dzypt'ͳKɷmuK;LhL̑]Kq*q=Wn>^Jq@@Z?}@*(ґVLr9+ޗ/k D n7k+̜1uVV:umؐ&j[bC:fu)֕r5)ZBSZ>3O٬ĐljF@YkqG_;ZjLv|i>wտv._бKJ7E+N~Ҟt:dpselw'ΩIDnq] 0_*NY3VYZWꈶ7nƃ+*P9zMWeܝE:mʈt=A'K;/PHbk\am_Օw:~N˖HX3KpwҍfBm/=oc@@s~cZC@ T,~̢ n*|;|Cذ-^֭%n=爞#`@o9 tc,N??m+kzRje Y-e枙o6OKj3Zhok{ o[ӻkt\ةe[\6#l~ i7^|tU (]tUJkifkX?- GGg[}[KOhx*,=7F;J5seۿSoڳ}!Jb]KH{4s {   ԯ+  P u5kؔd{+{Nb[5:U++38xG=iww٧OSiC}/98*':6.~Sݮ;bxSW]g5bxnve~'}^war5`@@C@@r陬*3%@۱F%K=ASpnZG+ jqJk{SSI*(Փ{k &՚cgjU2+ЙKۖVwgͬ{4{Mk;{ u7@-G\nFjfjg%udƮ ih`55)-:c;_uNՅ@+'v^86Tt#_ָӺ6g&;njbX˗_E_[-3T}߫G_)h]\3%n\&]3}Gzodw]6=fq[//1O)foAJ]WcH @@+OՕ>@jE PZVkͶC^-~Vi֙6_Md͌;ǞN_ VLݒS[Y%gg1IW:4~Y<-[qTn8?uceo뛣:JNMW}|7OJ~yV;sr%hT<RƎDӎH`Dկ nY 6-^ U{{**!Q{wjܕ8KS=CFIzG2䔯:g}Z칍v6Qh]YA@@' A@, ;~a҄YqB?Gsh?r,TPw^bYB:H#}f!wlWcMW1:izjYx;NR;i9?jfBà P_٧ts 󠞴'Mm:"3ka;rL;uxA a-1[pV}O[G*/Dnf!L{]C|K_trV_[ߨkr砫:q+499~zƾ!Q'@@e /}0Tɘݷhڲh2ؓ펿!>$p>'H/[_ hӞ4]A$5ZM9W>ѭwj:^YHOh@kƩɫ}v4pdel$RcW/;uq iBbkȓt}=M qt,g⧿Ӌ[6jӋpTjV9ko߯nVcEj}ۮtwzDFk "Yl>)`_O}Z~Y-ҎaJ}F2_#|JMzzb^m|oc$ᐇF$*(Dzs!!=V ע@nՎAzwsMb&Mu&W)P 843H\MRyVrݨזXC6.5'뎧?řbx Mi7a~wuN#Gh @@#@UuԸ@Zkjnm6*"ZJ77ZϏty ־@@dsn-cuz:^`LIBD_`M17Q(/ kDuzpPᡍH^ű@k,ڗ3 T2>.\f,jl#p =4y@' Ϛ<|nDiR>Qc'óf5ǨP1R5R~prkϔ{=:v|Ok5O,I9$Y3F@@>,>  P5~i\52J#@=hH5/M/iL2\sf[T~ 5 QP}]n@$޿GJdd1DG@@7rXS@@@@@@@@IV@@@@@@@@@|X.kz    @ la5Z'!   (@/ 1!    Q"    pq$Y]w#@ |U2]faڭ-c/;xyA8@@@z%TjH      vm.Y߻B   @hX/zA'@@@@@@@@@K$@%Z@@@@@@@@$YՏq         pHDT        C$1@@@@@@@@.IVj@@@@@@@@@~dU?Ƒ^         % R-         z      ɓ^>}~wyoڴ¼q   p7ۥo@@@Oz_\KOH7~*y/m't˰duku\j{ ~*EޠP[`H(K0WY_|O+CM2h]sm"IJ9  E\.\ÇW,@@@jN$&@$%u/|1iUOոt UlUY:$q/ŏҺOt%rViD(Y4sn׾   SԪURY ؚСC .yc@@@K,O  P@`HRW8g ,uR'|`GMtԞJ@  {%X%*%9qGm3Iٶ *.F*5'v Te@@@_  u*C/   $POl}E@bЩ<{Ŋ شf4g7r_L VSxݡ (lzgd&;3[ӲS4e['WՈ5O!  #ٖcn^b*IBq 33agoֻ+*iCLJ佮c{ͩ&jץn\%lSuynM什ۃ4z8묜8CU:f7tW8 vHW|dh(]WݞteԡG_ŖLs񶜓R`+'Nؾ3Z,`Kiw7*r^nis44ŀ>J O^|@}\/Ɋ   O|ԩvڥ{ԠAuQV6@@@(QqA ԅ@ >Stu8I#efnӸnI- ӻ((aV=LMW.١Ccڵ\}Ǜ fqfU8IJjo!G{fPҐ< ڴy9^$ٓҼ{(裏w]Ȭ+   @ \`2 @ r'X'k֒4-Q Ψfh{ S^YwĶ,k%X-MRMK(NJ|z3lI mhҤj[8fһK6W-Y.p/o>wmIfj ;5:gkюzG!;VU+[hN2 `EyVzrP;+YqүfO*3/@gu   ڵSBB֬YsٻаaCvm!  ԝ3Y՝=-#ԡ@AjIw+bsZ7J ҴhQR[smgߩJ^'X9/D%3PYQg|(GS~7l6ٜ~JFiᲵeCyIS+L5ݒ;Tn3u&_t&>u;QV3n\{U8xתH(Mߖ < U£)aȳZ[5J]@@@?t%XY![,'G   P*4^w!\Vr:(k~MH[mMTzku&lfJWnӸnL>V9icC{8 Plp(Kkޜ?6m2k-Ng) yvt}/ӑ9#ys%]KEOQqY]`;'UmJ<=ʔ׳8}SJ4K .ԑ#W^ΚȪI7jjfIkl   G{QӦMuI{}   @ dU PGUZsWҨ5, yڤ 1dLR7=X\*0UI" Xb $l6yD9Um[x5tIr&%Lˏ]0   C=$   $Y8 @ i̯5s+Ťd8Rn'/і3B֐ kIJ.g^w^k|lڢY[SZtajZGLM짪4-mf8XLd)V*oP,Vc1XdWOL?U$Ry+k,W7ynrPU\wuORFL0Y"I359&=JRw3ewS+3ktRiGBPhqefFy;:358!e}K   ڵ`C@@  /`3K竨(P-Z)ԙx d-h]>sl We[z@@@@@@*Q@.kPwDM.NӧumX`h}Y$qYP/M}*)ez_)e.u,S:S5o^ N^?4      K\<# @\q<;2k#*G@@@@@@$Y)A@+`S}:p䈎I!tu eR; @@@@@@ ɪ, @@@@@@@@@z'        ԘIV5FIE         PHJ@@@@@@@@@H1J*B@@@@@@@@(@U}U        5&@UQR @͚?{/ݬ"Z}yƬNոn5׌ Snpwfxx >N5u03!\wq>?2V    7O<{߻BE~}&h@s5cF:>h ҧPtj&Y)/j,򷾫IVQL 5T_MgUEqS35m\FMªL׻ohE'zUv\@@J~ yW*ݻ{߲e22!)Q^§uA7rrrQC@@dgΜG.0#P@WgF\EYT 4+CFW/mz00^&zm9Lݪ6S5?sǘY[5ZUML$3SJnM^keHU=|1i-n&+[; dR_V"*4R0j@kk##<β5!@UM(R   P5}ZA=h64Zޤ8lb֬z_6Z ױ}ߟk6h{A{ 薄Dk.hZe:Jn}:$8*27xy-ojo$i0ҪmqShƳaS֍>fRǨO7T\m]M[>q'L۫Wb3+Pc:![*  Vy]waW ZUw)ac^?7q)jl֙V|[nm^R-1oР6ei?[_d7M/ЊϾWppsEu~rW?E顬ZF{݌uGwq`ޟbA-JkC͌:yW=a~5g޴r@1@)l1z Po?"cCGu&# Psĉ˿d3L6|` ?Se*kKYCӇƚK|Tg$)m}8\7$TyYүxe&*3fMSħS+W~jdMyժDJj<]=KӮ4~YoVS+qx%LS3tTgO=Xv351QJPԕR5[kKHVsL|f /И_C2e{ Le*]Ҫ=I_Ccd;Ɍ뤙-OyVwQ5 ox[@@@@@@_hA RڟM%';IHVjjfƶ46-}8*y,kɬ&ı͸wms V&w")Ag35#*e"i(GugPlm+*^򋔚1cjϔ[1{jJIrl)JIyLᮺ/䈽RM/.6淚Or'X)>^=;Jx?}F2~5͑2Rzvշ3*I3)m,%;a&V'X%Ҽt-#͑UAeɺf-q$$\ͤFA1i3;6-w-Wbѯ7u\1l=*;A5֎MD+ӗח9õ83B]w㊩uUʼVJq5y3+5yk;*J]sJJHiii3 V f^?+i?cD;$y&X%:58AVC{9xe'P;wrJ}vu8B.@| %o@@@@@> 0U}U P@P~zzz?eP Wg(gOi8=& #T5u Ԑ]^sKOI1(̜ˆe/-|AZz Qx3D}y,EfFK5[C[iZ8y=u>$eej&]+YO0Eq=󒙵1W~3ɧ;&$^X1'ɦf=kf~(c 35b?5EM_,LiSri{t˃Qc mdmo\y5U8J q.8P3YUe:x5m渔/MN59V|,ILi\3|(c2.FG+ڔpHũRVMֺ J2[ܵ&Y743gu3UVQ[%ͫDEfm:1s~?Nm'WfV- 4ߖ5]Z]%2L氮f=l4YQ֣l6d2vk=H[_+{X6ަGY}TOgcS֛4G V3ɾW&uB'ezϧV+GCh,ǻmv[Z@he$KmWWWYSO!܍*0I\ܯF}c>]bkKԫWj_=nk=<#rOOui]+5N1UCg8ծ$Jz;I}& ruY3!jjorLL?O@@ Pz Y' @@@IV  @P'(,MK)+'Gɩ浼Gމ>!*ԋ%gD֮iTfkf=YiprBT=I޺s:Y*רk[yʫ}5OL+5ubN:Ϟ!j}Y% %߬np[5)S4{/p\Wg+-OR%Qxd%mWWms>?;T+[&cڈ泄{uҶ\Hc+3wYg)uuH@@@@@@@ *fX@.@οjLnJT9 U^Yxj+N^qd:{R.w;iz>],Wiym_b5j'![nU/ҊnxfGu]{Z:;:߭ؒa _{Εkr͓߰'ܤm3`5g{lY?J;Ĭari}i#{Q6. S^ŋ›8ᝎmǝϏ)vQWaOv8}C>->o<\9Op}DsKYZ[ԅbaq@-[@"py=}^j9mf7Zt셦Ӵ&JWqXfk訍  $@U3A  @ סq嗘mV<dO)xu ޣV׫w^\%GHmtuojF VnREPCCj 7O#M; ՙ8_X%èb2ԫf)TfZ̶zwor߭yE/k#AsUƜ%ɬUuUյ:FM'WCZdj;| [K֨leb񷍵]7TS9Vz-mG]jdO>JƋkl"z?**`]ͬ\#XᎷ۲Jc^J4"= Je\X^YmOKKrwk\!\:U/]W /pLn}Wό~W3^{=k}SZz:r 4k·s1l_IWg}i-\$*"'ub g|\u2>ݐ$&a2ъi˛_ھv+}{F&_@@+V$+vj @}~2U9rfk ;f}׵P9el\JTQQ7ݎP#t[*b=* j4sWz_{ Tfo]Lf9a)ӳ?Lg2?*vjJ64RZWklɿNswӂ{uf[=[+ֹzI;M$8l\x17uos%Ou$θj4e\HEi2t=0,iP='F 8L^O&QGɦ6ܥ=rU^JLanĔg>?ew<,.,r=7v7Tw?<Ⱦ>k>/,ezi8WBߜrΰ5`ؖ#[qU?õ3@@,?P"DfU[zHsv@K^suru}%N~i깴z9z}a^_~G_Ju5cВoivil_of^~x~/%@@ ɪ9i@ 0o]ZGDuKғz͠A&XNmfQSY8WrTr:droT"wTylOO0dNULX Rg5;CJ[=erDI=-Ur$\SgٟdT(ӟkpT_M[7iw rLSYY{^*ZUKg[ǗuwWJ{~+hڸ n㜫tqV?-weG󦍅+l;̓w οm(ԅO-67e2V?9E#-EkGՄ6Nu_5ܥ6nT3WU_*]շ~~zE?lJ<ؑ@W[my<OxDIo˦KK Is c4c?lȁ \YH** tJ "W.dH;rAW=ۿk׾vٗ5&Z5h{TXs)$ZC@@@ *g@Ewgiz;w`Mԁ 7%dKHL[(^1,!PΧf%Er2{wT-Xvκ2Wr$oks5k,WYk`ڰpkF9ehJGJk`WqIX׫qx> $'mDj%bYӧj\HeuŎ;[4o31WDb,m<3(nl,$9HԜWWjc,u^=O>,yzn2?15_'c[x$D g)cv|پ{;ю8>A j26o,VؠE<$j *?ѾoMG|!gvJc5Mm@K&lc5^/4 JV'CoH?F շ~]dNb̟7յ~z}fUպ1zd`]CKߌaζs3>kL)ݦ_QvnVυ޹EzQS(|s1raI  g*WWr\+]M~$_Ԑq@~y+@^ @RI :cJg)Widl#B-~U;6Xp'ԧRMIp$$2_KqB#|Yֿh5Y5OɆޯUMU* [S o~> 5@@@&tR\Sʷ-NuJm9~ߪ e9 A䬺 ^sУL:_fzRrJ`E]TEp{]UZ:?m۵SLjʞ׉}'u$JJ,ڛ~#QkrF `"1%UI )mʷ^Xu( Mnâ#g'o`e5oϯO[JUnD[p   M} @qÿm6[bШJ>Gͪ;_e]D#-&\#R S+Ϟ=޿ixi5h@hy羵-   p D%]m+AzHw$+ RW?[2s*ҶZA,ڿ"W?TCNVT +F7wX$ j;y쾃cZ߾ɲ^?#qw1ciSRKVk9tBx>"覱R:{O>:MuM&aVv?Dt_Ug!^Ikmm{ @@ *p@X (vY%Ue,mҭE5wHf83rk&!*@gKu*^ (T3  @SNtӊ#d}{^kȇddR͝-dz#zS7]cgힿYopU[̶5x\*tOM] ђug5LM'_%\GbN)f ud7.QO)PRsڬ:VE={f/;@@M H@?LWvTق:zN;+: "2Ju]!,%&\A@Z٭\#@Ppp_'ms$3\\ ƽUy4I j7k$XձA*Xٿۮmm Mf=:^~U4龺_{X}*g$W`}Th<:i :A ʔz2W;{ww3u\KZԹutGe3$|Cݑ<+~UMUIzY  @-1( ~,8ۗIh@Iy `@@V/pPP@ XwJ+IG{(RIvՙ*[mޯRj8WqZM[tdjwPl[Igo Gi~\]Nn4cޯ9@ճ:LR'}gpƽUHDW PEhV;|ndeW*toIr]8Ms/sxW:xԬUYM΄2o_wE"  @@@@@ZN=$>Z!ۧCY6VQzYNś_8[N+׃0 -s8}f%qsycNG|HuٸDž SLݾרG N5s?ߙWTMiu4mT¢}K_nz'XߙW]:w@@4mƈ@@@,e˖ס Wٖ-gzjdn~L·#cuUǫ:>5 Qv+n~㌹8/׻&-e΢tG^jh{uw=^>㳔pFŇ|[< 4sHM}=<~V}^ҠުRK\r{4  IV~?E     Y5xBO @ ]o2p}>떁zk`/Qir\P֭k\Ʌ6G 1քk+H2Dl֛ךf j_$9hun˨^Ξ'gW~KcF1߮9A@@Ot     rHYr3@HG |ȽeMeNl?*p5m&}c G/`7J'\qO'iVCkۡvAUW=W”wцtrUSv}ˏs  @@ { 5E@@@@@h^εEoV4:`e"%%Ҥ5v%mH-Tg͞ m^[4& IR`*T&ZZwZk'W]G;%vR`q"}{p7uhljř99@?{Z{ R}.w!  W:A@@@@@@IatBW *ZHyt;iռ䂎vo<=E*.\2I5PUOֈ獳5&_CwwE{'XT׾gw]|W:RT}|^U߭ܤm X#kSN׌i>Lz  WIVWT2@@@@@ZX  <ݮ]ΐvU*cG$}B՘sTknoOlQnyS1H=n.8V% v:,uڣPerSΞсt_;,Wތkۈk,b_T\5StOqIP[yU~eįa>z}y}#~$A5)Oe:_Z;q}/c{&rgtzՅzgka[s@@Wv`@@@@jz}@hfti+Ȥ<ݐ"R흆Zݧ}r~؉1xAi}SȮ(B}zӺ^{)<<4nk!tlk:wdF|3J!:Q'wꗳ; :6 w(O\Ap 0_HR|:^Fp js[CZ:ϼI:H*T־pȫ׸~эH,j @@G     @@=$4EwAo>q&8YU\=^??L%c>6Gչz|:~xnf tS۪Yw}p&<ϫQE~|#TIόֽyuj;z~ʽp.\eۙq^]]3ݨ~UNeGk*Ϫc__N|=tO{^IC=y]-X_]@@6$    6mWG4 @mYg @XJΨ`gJJ-jVBg?]j֣2CͲZW_bԢcNV]X{E WhϕΫxԶc\3~ݟ׉}'UWJX:W3CRϣm6["W_V(jWZs'B};4al =5dm3D3 \7iN ^$Y]^ozC@@@,`&ߺ[III"2] ɪUL3D8)e@ /@? 0<ֺ h @@@h@ΜFԢ      ` Ɋw    4J`,m]}Qu   IV4[Ċ     #gED(     Ҹ*        \!$Y]!0@@@@@@@@@dui\i@@@H~ &pL/Ty˅(u9!|<9djǏFDi++pֹb R@aA +Ni@@@@ *U^Э4g~mˏ[@` Gdp6nѾNCR+V3_3WHz뭷+VmzzmW`'g̗SW@~$YωR    a} tYypY%ŋ>Ӗ3+`W`M"xUڮ4nі2W4W ͖||-IV-?D    @@ Dv3~G    Ea@@@@p DtQsl@@@\W         tnH     Ny    ,@?!   @:MU@    T a#   +`)fmw!rK>}3SIw׍wުq죰+`Ņ'tr t_i*{RPnOpI2e+xP}]>VrkJ۷5,\B\C;x/ΟU[uqUb(^JzG7_̐=JV @}(Q@@@#+M<7~q$+?jXŻ~=ևofOV/pGx^r,(՟85-^]=LVWɓiiI$+ jtIdϠb!ʧL <FYۨyİLjϏ{#<pڒe;Pٵ0'Qjjz6eyL3_^غA%M]{VV^X~B/VKL{WK*"ӽ#Ҳ3hK &+e@@@@8m5mtlv~ў`5~y_˗?k7_OKpR%Xk̂zd}gr LtLZ-n(2D(0+=5eB u$UO.iy*Z;/TR_hz@@@Z@gOzMQ8Okou ݼԻk9ooִ l@u4mglʚf:g};I@IDAT.P܄ *PIg i? ֿk:?mƞ&j93FU9C 4Z}Q/MNϐgiߪN1UQ=aL#j )([dOбJq|JM6S'*1wy s[qEkjNLɭ] @-cX @@@@=i6/q.7S,Јç@{>BׅGN(`%2o J{.B='ʢ=}BkP;\KMNmȿM5}}jógO>3OqUׁ]pwz^cM@seӄtUNhȎ3lx#/s;|ՇfjQdM nPYJxpt_XGU0t-A'K(%]˄);+,ɞWբ_61{\@u,iӟ th]magGiLXu:}CZ+%ΟU:{[uEЬ[%k؍!;m}<0O(%pY]$ \wx#Z;k]Q=(19Q9Y8sڠ) slQ]{_f=@Y~X7UthttU9qqGDWy uj)ID@@@tNWVUGk[)j'?}]^W5B9oax 6; Okvfg/CB3Rd=yȼU0uJI;;nմ#u>.4 r@NvX~ !Jvkו;zk.bONN˝ni2GFRg,W^QwrsC2ױfGH݇    4IZ1A?oI~Z5U٭:F:[#DѶ{&[@OQ̞6L')]܁#ZA[UB蜭bob{:wSC?h!L'h&*7[I=5g0w߿Q&X?o:lP6 ofA$X5׺RtmWJب鶌UqI DhY*Y &oLz%Vj?Na\AK L    _'mwR_5l~ oG DtQsgBW8pB:_Rp*9چWoׄjb)T{|ȭ:OǿΝ1>]ϭw7Ҏj¯57J3cBY,#TP0BR'uű:ҁ3u_s ` i_غvX'fu䨵T2zF)qk*82RlݍQ O|Z凿“ G{Ͽl`qi;,=Y}Sʳ,4\j#кۿu?G@@@D}J%f!p=.ֈJU` nifQ'2?ev~+vН?|Ytnkh֛mMƐpZ_K=(T+2[* _"6*FM9:JU׳y5@E&-YM=(mv{HSjٴQZ:MsӞu̖)'-ýcU2[8.Jf,QbfM"ԒeD|[,6Ұ.Ы5wK[Qh*ViKoOoXF    FvWnO*SxI0~+;gnvUq 5OՇwuP?23^~H)ilH`8[a|Huӿjk@46{Wty~0'Xoޫ_ȹGuM'$q[7mStxsfNdi#Jizu<=08(#5}ҬhIY wC  V y"J@@@@% _ߦ[vW%_5W`%Nrkv5Wh,*\ڨ}x0r3ҰC:fo6/@xG[ϗYy,<~ڨ>lV(4.@Ëu|q@ʎBY*, Ud$I~7}4ej=2D%eUFFVx[i@ StFS#sURRf Rhd?C8@F WH騈    W@ې u JRG|h2J; پ>%) 4IL&hlޗ#k"#xIhi(J׭q3        [$zSQ@@@@@@@@ZIVq3   LSNl @@@@S$Ԥ-@@@KN$Y    "9$VD@@@,ЦM)ץXR[oL?S:    ͣpTCAMOu@@@@IV!!!ڲerv51!QIII^(渜>ֹw֕$+'   eh߾e.@+YR   \13fП'UVV^1cb      V?O #$Y$        @[ @@@@@@@@@ ɪ@@@@@@@@@H!4@@@@@@@@hyZ~@@@@@@@@X$+?BC@@@@@@@@ ɪ@@@@@@@@@H!4@@@@@@@@hyZ~@@@@@@@@X$+?BC@@@@@@@@j@@@@ [ڮo)} HWǥ_CbCil;o|7M6-K]o=68D*x XKid|n{aDRAiS{׀XAF@@@$JVi+   @WJ6UfQ-Jm>]S午LyMK/UԩJ*[^PF4*5UEe~PEG)5I|Vm¢?iFqF+ M    Hjΐ@@@@;tTHVhpG!vu)Ps=Vwc'ܬJ|m*riҦdu1[yY|8A@@@|)+    ЂnUԘ/@E:zMۻY˲rq2~2T{+xTm8J)o>zSjMϚg)߮    $Y   \Nzorٳ!ܹQw>D(\mV`BꢛF VdT-+ACI*eڹCmٖ'*$:tӍ#iHH,%yt[i:Nu%t580Ke+db8D̸j]絭wIs+D=ӘoTlDC^-ިhD`# eՔ)f vnRNQ{Yr3u",Lafo$ZfŧOd4Թ Iz'k(WKRpnPg\-X!p>ʖk״W+}zs= k??]KMUz5+e|Ap @@@|++C@@@jع|'-t.^G7kt[[J`5>_KUvjFDjlun͟ok|n㖸P;;;iORrKO=j=YkJQТ~F341r Z3y*cýZ0zl%uǤiU(ӎaKKSβeJ?Jx[%r$*ѬeMAXC3P_3KGQC]9MRڶGg~ղ5_kJ6c9M?JYVY:nqS*Õ4!MXGTn6tV4%    pE F`@@@@FXWӴhefԜɎh%oS,zۚ`eY1 *;*m"e]=gQpkH+7NG^&Yk3hIkr1T!5ە`:Kfիf$둥{H[|TVDok LnZj:7ގ Vɚh6]YL+v Vjgg{VUr pH**kAu U|BէOnj Ś >pjjlg4]ףzb]oZ    XɪN:CF@@@ouܘ};櫏j $ݳ$GG N}4~dL_o+oV8R}NJUix[[0W:pOs Wp{~3KAѕˢͅ2ı=ӔXū5+j9GQɌT{Xzc `cuݷkb&Mj_za\}6oXօrKW41Ĕ6f~WS+oϙhG^p5x 3AfVYߪꩶz\WIn?*kn̕Kf:X]eJU)ظܱAK%8 fƎ*h͋Cr*[8V)%    Hjΐ@@@@U#[ROܧ]JiSÿ;`dgO=pf diVgEQmEc;WYICəqS^R~cpZ>#]s .rng}xTu~ЁIirBuErj+)SEE2kh8ow4Em 2_V~s9ة)mhР6JyImA}4nNCl3լD~#v'uW@@@@IV@@@@ j G9 &G5e#zi\kȚ6TyЄ1^Jp"]O@uuTzR1mj^C'jFMNm"6+Wo5.Ngs9K+"XRLPXMeWjp{j1+mac5 &q$"kO{HӣޯcsWkjgჿ$#GߨEr v&5l-e:iPդu|V5S-{4mt/L1(i_JKР)   \$Y]@@@@NP_O ';SOLL=&mJmٰR,XMxq}\'Aquua6m&i-Z䬪:cumMl&Lq1n56Ni^-no+'>6Ȧ/ڠ]XU:>QsҜ#1p4:}r *2Xa벏]nΚUq޷j/2$yHŒZ蘓ˋ5d&uuӀ_ûk-ZUKF?lt'YiHz+Yju@@@@*9Z7    )`1[Ǖ2XQ w$UR1 w.rYsJJTf+:ґbPQa*]CBL 66NKEJ͢YVhb)+QaY-E6n(_(%ą۶ Zv|}eqb=6s]2{|w>L,yz"Zʧ6y 3Ŷ~[    @ ɪտ@@@@*P&D(ݱ\]-)Ք%f}筚XӨR*ufUhSԸ֍ͳY=Ǫ>]\2Z5%Lgq/F@@@*NR\#    POpJ-L9_[yrY sNllQ=:}_-|u9K?L^iz'@@@@!JV@@@@Z@Uz,WR/ڨSFʱK#@@@@ZIV @@@@'`QIa=~\'TJt΢@@@@ dU?'J!        @+hJͰ@@@@@@@@@z dU/& !        @k ɪQ&w iF3VK[i1cLyB qV({KZR,)E*r5cPr/u_M%ZY-̭y9q/^ܲz5A!@@@@ duhi@@@I ob[γ<|E?1C3xVy 7j֑V~STʛ{2-OѨIӴ`-Y2W=5]SGsJed55RZc^A5eѤ4i;X9T|=z&D   +@U#   @3 _kKYd3z}BR|kf)2*sO}=yRbeoܥs>=R}&n^(ڳ){Ǟn9؞QjGTumޫ.Ghp\?FFB>8鴹Y Fs|lV.cS"kۤUZVSY}y:o$6q:r+:1v9jcL=6+-+b;O]]YϬ[t.:IK]}!{mlΥFM)jo޷Cd /׉0oc(<];=;ĩCqNrBPy"B96)rhbBl k9;9'? C]n-s($Bva}]7bodޙyz^;68?Уg|Ë6O%ִD;I{S8}}fŸ[D6/Zއd}>.walp▻ų7G^ʼn1cNFr<:N>o5m:Sxoo;WTtx->l(=̎x&mǍn\NM P(@ P\ (P(@ P(@?cGe_贮2Pn@W(aǧ[yW9=(X՗>vVy i'y >v4șzǪ"u.jTm.X5DJޯ$Lc E8☽cēXcwTi$"ƨdeP$dL+uT4ф +0m :78Q5HꄪI#]P7+֌n/)aFY'?Nv3}b:;G%dVV (紵Zʺokm/W1T"iU.4fU5m}N0_1 5s5Yc3b2t1@ێODV_R s]U挱Bߓ˧e8m^z;76^nR(@ P(07d5[jC P(@ Pu-0vU5UCkkHy|uHlYx~rb lLz8CZ@veɏ:@ }JPKΜw<:N"9R Gm1j6ЁCZ1cy:]N 8pl0嗿Qn5۴#0@ 5;|B0r^+-%aܽSdsk}N۩RܿS/ wڕVYJ@X|ɝ5 b?Ъ{oFª cZHOp'nף_ P(@ P^IVso(@ P(@ PnmxH /5u_[mHMN;;bf6&ӱF0,.pJ6;O&^kh8~=FHJbaC P(@ P 0jh(@ P(@] SlU_o 1:6OGSsYAh<*F3]^Ƈz9} v(FFG1h##CK2Wy2Msc;)=,%8+zo<5^'W %nv%{%K-eh\̆8Žn^Xg5)h}sZkipmc'?jڎ71.!cdz>5c,SХq, K6<"gD0>km|eP(@ P(Y 0f_(@ P(@ V=3FKOz{Ǒ!(myXr~64 @QY8TVՌGa~>BY\ZkF2yRg;UYiwvPz[WgW*oѮJ gE7ʓ}LupMeEu2?\W7Ie~3`^el W"B\Q,vAqGvVm}wƉ{h6ƟByVu6臊ZA~9zFߗ3((@ P(@ PE&Y"&CQ(@ P(p I$S"ȲJtn#ecN|YrT5,E™l_~?mQ(@ P(0{L=KF(@ P(@ P:XdɰbQr˶B{X&ވɉ,!-Gt"28OEY~ƨ$dqcˋ'|˗XaYK N֛}':dH@ h~ރ`sl ,tXj3fB͈(ΨL2'&7Fq~ ڢȔK&jV`717V@,-݂.w!TmZɵ-< :/vƙ[ g.t"h.Ã(BP ^:G[9<}9 _L$㒷@wV9jzkif:fn'ׇSƽ)ph[yN۰ 3bZkR472QMu`> 4~<0^qoz-tECfB񜉻T<Ý=&bL7~l|WNh\o P(@ PfO G H(@ P(@ P(00c+(Db2!J DGWoCyY@ "_Jo !3F8/Ct 9,Θ4ߙ1JjoJ,(^Rf|kpX3w<5Q}bӏJb()PS3`Ƈ{^?Kfu&{P-^23,^,Y:\>{3}~ޟMl/Q}s=Jf(@ P(@ PC&Y!.CS(@ P(@Uv[>t^jCE›D|E^qTX͈0#*őj'I. [́=jD$Yq!)@ P(@ P@ˢ)P(@ P(@ Dlߊa<pZ7o,%X#,DŁ(›`ksz*n+w~GUugp (@ P(@ P 9C P(@ PpOwIU!"h(/h>Ї{1+hK P(@ P(0m&YMP(@ P(@ cxx >t [oYBQ _r(@ P(@ P` 0j E P(@ PE IDAT(@ P(@ P(@ /͋Qp(@ P(@ P(@ P(@ PpX(@ P(@ P(@ P(@ P`X(@ P(@ P(@ P(@ PL aQ(@ P(@ P(@ P(@ PCyB`h6IENDB`opentimelineio-0.18.1/docs/_static/spatial_coords_example3.svg0000664000175000017500000000432515110656141022316 0ustar meme (X=0.0, Y=0.0) X Y min = (-4.5, -4.5) max = (4.5, 4.5) (Width=9.0) (Height=9.0) opentimelineio-0.18.1/docs/_static/OpenTimelineIO_Logo.svg0000664000175000017500000002016615110656141021313 0ustar meme opentimelineio-0.18.1/docs/_static/spatial_coords_example1.svg0000664000175000017500000000415215110656141022312 0ustar meme (X=0.0, Y=0.0) X Y min = (0.0, 0.0) max = (16.0, 9.0) (Width=16.0) (Height=9.0) opentimelineio-0.18.1/docs/_static/OpenTimelineIO_Logo.pdf0000664000175000017500000012302515110656141021263 0ustar meme%PDF-1.5 % 4 0 obj <> endobj xref 4 10 0000000016 00000 n 0000000637 00000 n 0000000697 00000 n 0000001031 00000 n 0000001064 00000 n 0000002608 00000 n 0000005255 00000 n 0000005368 00000 n 0000005495 00000 n 0000000496 00000 n trailer <<65A5C8A318D24E5996876C8A3EECEDEB>]/Prev 42313>> startxref 0 %%EOF 13 0 obj <>stream hb```g``aa[T,  bP#t!ʌ!Hak endstream endobj 5 0 obj <> endobj 6 0 obj <>/ExtGState<>/Properties<>>>/Rotate 0/TrimBox[0.0 0.0 409.918 59.2813]/Type/Page>> endobj 7 0 obj [/ICCBased 9 0 R] endobj 8 0 obj <>stream HW[7W>m^4qHv\]sf!H p}ϿSxz?>[|=]ݏG cj4woC߽2)s8 |>>CpD#s.#\_9x9.ki#u B S;l%షbjaBWs* ,:!\Q ^V&Mb js=2G(-P- >XQ3hc>>NP7!c2AB0Hcf|M#HY9?q@!Z@NAܠ2<;Ӄb$O×0`:@pՖoF.w7T ^ eP*+Xqn5{#؝{Ny4õ^ 8X#JDrsq(/\TXkצB'aiW+P l7U eȅŒ BP[.,Y i3R:BG)jC 8EvG\]axR5T!WoII%|74LpVM5Suב/X`5 [z rd<7 ^ޭ T"E%v;ZVKon5\"g&un-غ96!']U%AS;24#RUm(;:8Aw`P4yVI!- I/mx$݈JX 2pUE O[HhI" VNa(&i&Rv|ǣPf: %+ؿT-M"X+VSh p3hCeu<ިj3|1݉|)z4/\!ӅpYojځ/{_@8b+IeXk! z(5^94]9ՙD,ڠ}HSHԋύΖ?UYÄrZ2nh$I2Ђh&`"τTĘ$(lZGm: Qm5nzNsf!e*?ښ\"G*nk{JȒ}=t`T›-RuM%Ke+RƉc$y@=|X[,U0O-ZZ-u(5JQ;dk&Db\ڳq^[Ɠ%m~+1$M:Z6[KP:cJfort<ήlL)4[-d`Y .JJ5aNs|s  endstream endobj 9 0 obj <>stream HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 N')].uJr  wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km endstream endobj 10 0 obj <> endobj 11 0 obj <> endobj 12 0 obj <> endobj 1 0 obj <> endobj 2 0 obj <>stream application/pdf OTIO Adobe Illustrator CC 23.0 (Macintosh) 2019-06-20T14:10:58-07:00 2019-06-20T14:10:58-07:00 2019-06-20T14:10:58-07:00 256 256 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//2Q== proof:pdf uuid:65E6390686CF11DBA6E2D887CEACB407 xmp.did:aaa30eb2-a54e-4015-ab75-02043de25f1a uuid:faaab2c5-633a-f044-9cb3-29a0bd96001b uuid:f76f3654-03ba-f441-812c-f08b8235f16f xmp.did:712c8e06-aa34-463a-8677-442310e0b7e8 uuid:65E6390686CF11DBA6E2D887CEACB407 proof:pdf saved xmp.iid:ED7F1174072068119109D20C90CE7944 2009-12-20T22:47:44-08:00 Adobe Illustrator CS5 / saved xmp.iid:aaa30eb2-a54e-4015-ab75-02043de25f1a 2016-08-29T22:48:22-07:00 Adobe Illustrator CC 2017 (Macintosh) / Web Document 1 False False 960.000000 560.000000 Pixels Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 0 0 0 RGB Red RGB PROCESS 255 0 0 RGB Yellow RGB PROCESS 255 255 0 RGB Green RGB PROCESS 0 255 0 RGB Cyan RGB PROCESS 0 255 255 RGB Blue RGB PROCESS 0 0 255 RGB Magenta RGB PROCESS 255 0 255 R=193 G=39 B=45 RGB PROCESS 193 39 45 R=237 G=28 B=36 RGB PROCESS 237 28 36 R=241 G=90 B=36 RGB PROCESS 241 90 36 R=247 G=147 B=30 RGB PROCESS 247 147 30 R=251 G=176 B=59 RGB PROCESS 251 176 59 R=252 G=238 B=33 RGB PROCESS 252 238 33 R=217 G=224 B=33 RGB PROCESS 217 224 33 R=140 G=198 B=63 RGB PROCESS 140 198 63 R=57 G=181 B=74 RGB PROCESS 57 181 74 R=0 G=146 B=69 RGB PROCESS 0 146 69 R=0 G=104 B=55 RGB PROCESS 0 104 55 R=34 G=181 B=115 RGB PROCESS 34 181 115 R=0 G=169 B=157 RGB PROCESS 0 169 157 R=41 G=171 B=226 RGB PROCESS 41 171 226 R=0 G=113 B=188 RGB PROCESS 0 113 188 R=46 G=49 B=146 RGB PROCESS 46 49 146 R=27 G=20 B=100 RGB PROCESS 27 20 100 R=102 G=45 B=145 RGB PROCESS 102 45 145 R=147 G=39 B=143 RGB PROCESS 147 39 143 R=158 G=0 B=93 RGB PROCESS 158 0 93 R=212 G=20 B=90 RGB PROCESS 212 20 90 R=237 G=30 B=121 RGB PROCESS 237 30 121 R=199 G=178 B=153 RGB PROCESS 199 178 153 R=153 G=134 B=117 RGB PROCESS 153 134 117 R=115 G=99 B=87 RGB PROCESS 115 99 87 R=83 G=71 B=65 RGB PROCESS 83 71 65 R=198 G=156 B=109 RGB PROCESS 198 156 109 R=166 G=124 B=82 RGB PROCESS 166 124 82 R=140 G=98 B=57 RGB PROCESS 140 98 57 R=117 G=76 B=36 RGB PROCESS 117 76 36 R=96 G=56 B=19 RGB PROCESS 96 56 19 R=66 G=33 B=11 RGB PROCESS 66 33 11 Grays 1 R=0 G=0 B=0 RGB PROCESS 0 0 0 R=26 G=26 B=26 RGB PROCESS 26 26 26 R=51 G=51 B=51 RGB PROCESS 51 51 51 R=77 G=77 B=77 RGB PROCESS 77 77 77 R=102 G=102 B=102 RGB PROCESS 102 102 102 R=128 G=128 B=128 RGB PROCESS 128 128 128 R=153 G=153 B=153 RGB PROCESS 153 153 153 R=179 G=179 B=179 RGB PROCESS 179 179 179 R=204 G=204 B=204 RGB PROCESS 204 204 204 R=230 G=230 B=230 RGB PROCESS 230 230 230 R=242 G=242 B=242 RGB PROCESS 242 242 242 Web Color Group 1 R=63 G=169 B=245 RGB PROCESS 63 169 245 R=122 G=201 B=67 RGB PROCESS 122 201 67 R=255 G=147 B=30 RGB PROCESS 255 147 30 R=255 G=29 B=37 RGB PROCESS 255 29 37 R=255 G=123 B=172 RGB PROCESS 255 123 172 R=189 G=204 B=212 RGB PROCESS 189 204 212 Adobe PDF library 15.00 21.0.0 endstream endobj 3 0 obj <> endobj xref 0 4 0000000000 65535 f 0000005622 00000 n 0000005673 00000 n 0000042104 00000 n trailer <<65A5C8A318D24E5996876C8A3EECEDEB>]>> startxref 116 %%EOF opentimelineio-0.18.1/docs/_static/multiple_tracks.png0000664000175000017500000125151315110656141020705 0ustar memePNG  IHDR h iCCPICC ProfileHWXS[RI(ޑ^ޥ%`BP+ ]D"( Q,, @EEY]&t}{N3Ϲs@Ɏ]P*'(3SRG(Q_LL2 \**\q:WɃ(&@XڍfH jBH".2)2l-eA f 3Kx380]N 7'ͅ>yy+!6O.Ord3ǰ\[rţsBf Cb%5þ`*-hU!J%n8$A?`π/Xb qN;XF C8]+Ϗ r"yeBG6(0n' 1\i$O1⫢py,VԨP+l aPօrҹZ| CdX2O1ʁ q΁O6@2-Ix@4/"h2fڀ h4"<8ƽqO<Bup8@b1Ds p-)pCitb̙ lAҿ7q q \N? 3q瓰nIwH{31>ĖaX;v`š'ҕ0:[[l~ y %+`U1*Z3ctFT}w≑l?7Y;Kp#l(B #`q.@Apep/ x!!4h! b8 n7D H d"DE Zd3A~E#gHrE7'CP7 Gѩh&:-FKЕh%Z@3eڃD0)bfa,,K20!6+*jkփ `q" \!xgf|ހ^|JtVB(!II(%T憐H$j͈L!fWOC$IdE"EؤBR)iG@V$AT\AO>I&?#+(+(x(D+pf+RحЬpEOaB1xQ)ٔEJJ>/EEECEwI|Ņ/(*~R-,zzF3RiYC:nKs Uz74b z+J ʦ,e|*ʷT***y*+T\TyJR5U T媖R=1,qѧFT3S UV+W;֩6>KJzaJMOt[>n\5}5ye4oh~bjjhjzk[jOҞMx9WձԉՙKCgHWO7X@wY= =_lz'|_0ՙ~\f%9hcb 6ii0lhf`#QzVAc}HƵwMLLL6753M2]jhL,Ԭج9|yu EVee+Ŋoժ˚`n-eC)հ]lhj k&Ojlk}bf7*4 MxNۜn;3#::qquԹnq;}{GB#zxx|>l"o ^;zi;{| |>>||{|Ye{o/?:t&n|dT4<'t!$':goHnR״_o ?ZV_wcce HƬƞa[=f'V,99rg2vS.\xۥ.:;NΆ+W_mu̵ۧk篇^|#F̈́oMs{;>~uVz\zNv<{t1''JҞV<VyKP_,x9}lϤϕ_,4 z$od-dKT4#7{ tK*(E?aL*.: a5j/@T. GY.z-$7(@<2232|.I;$[(mߔ pHYs%%IR$iTXtXML:com.adobe.xmp 2408 1708 IiDOTV(VVIW@IDATx XT7o&bժYi)EWF*1l4v%/yBM/A7 jYG*p4YlcOFSIo]pEװl-nU*\s3ιN3>98W(@ P(@ P(@ P(@ P(@ |A P(@ P(@ P(@ P(@ PNx"P(@ P(@ P(@ P(@ P0I-`,(@ P(@ P(@ P(@ P(@xP(@ P(@ P(@ P(@ P0I-`,(@ P(@ P(@ P(@ P(@xP(@ P(@ P(@ P(@ P0I-`,(@ P(@ P(@ P(@ P(@xP(@ P(@ P(@ P(@ P0I-`,(@ P(@ P(@ P(@ P(@xP(@ P(@ P(@ P(@ P0I-`,(@ P(@ P(@ P(@ P(@xP(@ P(@ P(@ P(@ P0I-`,(@ P(@ P(@ P(@ P(@xP-[mmmlS(@ P(@ P(@ P(MC cʕ=Z=\111 gd(@ P(@ P(@ P(@ H*Z[[{|t hRp |+_!(@ P(@ P(@ P(@ P ܽ{GǀV!(@:D P(@ P(@ P(@ P$Z^ >(y"o=R(@ P(@ P(@ P`@laO P DBj(@ P(@ P(@ P(@ 0eb h]_(@ P(@ P(@ P(@ Mfy@ 0Ea(@ P(@ P(@ P(@ P`@ܜ@ $ۡ(@ P(@ P(@ P(@ DZS  hO P(@ P(@ P(@ PR 0%eYyPвbg P(@ P0S~Я;q87>  87{tv q~l+K P(@ PZ&`@WJ P(@ Pl9y|Rt=m%m݋p_[\Uk~oNV>)rO(@ P(@{'ֽ)@ hp(@ P(@ qME@XК-Z۬X?{`(@ P(@ Q0b) P`@7zܖ(@ PdЌՋsLj%!30mFzSM >H[r(@ P(@{!ֽP>)@ 0eE(@ P(ЧC3XjKPߌz wcbqgr. Ђŏ-Z/ P(@ P@8 &ۢ( zM)@ P(@ Po t^@>1>i =j(@ P(@ P@%Z/ O P(@ PE@+ؤu 8w?F kcN?|mh'ֺ|gpK|F<2mzrW xp&N_}hm/thoO| <ڱ}=v~/?܄rtMnrjƙ3h/qߙ1ؐ(@ P(@ P}\>~)@`@+zjP(@ P@ t3Ps]pEQ9g"\omWQ~71wlc΢TFb6ڛN]ۊflLզB;8PSucSof BSY;NT^ߥ^zW6Gv4gLR(@ P( i@ P JЊB(@ P(hUVfomUK'AƾߌWOT` !5Yz" xlKAXbg"?Xhw۪S P(@ P@`@<| P zЊZ'(@ P( h%.ZW=c_' l´ڛa拾 +-,I47T`jZx#=Ecgc5Ƨ7_&q[e3Q -DMc!ⵇmO(@ P(@ P}QJHo`nk2n/jBWw[C@rndޛR[RcQZazRiE5kn<)܍t]n.=y6FЫ huߌ[P(@ P@0+aA1r(RKpiK2s -1UKi>g]9@=݃3e#͓tS1֋5JX31ڳ3vTiSV!k2oi6Fn4x2ZDk; P(@ P= h/[ d1ƛVh+eK;xp"|GoGhM Йy(wC A[s}c&XYe7.>:B]_֣v{ou0(Z(\D P(@ P00)5a%P%Wu.O݇Y\_ĹysR-D,p4U_u5.nbpu람5m;D@Uw@+)vg) ȍO'Y=碮%,;Jl6 >(@ P(@ Pp De@ 4[1*W&^\o܊eZ-(O|QR{+-h쓿uìNt.]5ǀVWB|(@ P(0)Aԣc  Q5T.#kOWAtr :?ߋwyR"t'PJ#6ǖ`Ү= X:q.T=GnA9Z" P(@ PoKUD&Ul|m`V Cu ]t(Y*ӆ_ڗ~~֯f=BsYI4_~E(FkX:"[ "ͽQ(@ PL h勩2 ]@K5a;Vc/Zހqwm;q] P(@ PQвŹ )=oڿcŁ9sw烾~ŁLx5b8Sm(97cڧ'QY8$la4흷<Z)jTݲ*١#8tོD<] "z#ꕳS|;[q8A!KKw5P1bU7 }91UCІ 'p9՗e^z.~ %ґ@Q:fGD1brѰP~c9~teZb꾵AGn:_ [*L\֚CK5HP.WVx=1EӚ-j7<oOc }p^t{V9#Z[JZǔ4M)kg h-)@ P(@ P(@ P(@ P΀pY"S ^/ .M9iC+Dhч вڠUOSG~1ݞ]ZU(-`^zE=v4Y~W-(Ouvɖ#A[T᠀BI?tFP]U[N#u M "vS\KyZC|08C=N2`ϣ`>Fвr-u/Ҹ+4m"Jk؞  ah1Q(Wj Ysa\ g@/Ma.ц7ԟD5ةP5~tj Zz^' [ZQ<Bm4`RZmWΉ]v@ H.8mVfGs3UΠY rkN3~ h [ZZ-1{`s`©i~[NMb7Xpjeq8zSX/ù+zo=^4-|p m^sW85o28{`©i~[B,F3]-(mH4Yqi׀QЅ-3ZiUS7z9 }Ugn;{Ze" 8^Y٪< ~lRDFZU/ҝ RZTYtTYm+2&ahi:C⭷ A)&OFhW w?M?sf{&^&$e" M^&$e" M^&$e" M^&$e" M^&$e" M^&$e" M^&$e" M^&$e" M^&d Z/FgZ"HG=:JSF![5HNr)ԬZ_yVE*?BcJQ}7lS}!o8at31ؖhbTW.ReCAzL_t^|ŗcT-v+MГQ E@&VLJ4Ͼ,O?6}0zp >vGu\ vj݇Eo\peE0YZ/}@f| 'k>j-WcK M~ŀangu_"-93X/3Tk2֌Y/3Tk2֌Y/3Tk2֌Y/3Tk2֌Y/3Tk2֌Y/3Tk2֌Y/3Tk2֌Y/3Tk2֌Y/3Tk3Z"ŃmPIfMGѩt 8b`)ͮE1PG0-t`ݰMuWBl_8|OpIM0֮ BZNblՑ0^wTۛTh29vZc34rE(Z1Ashz~b2`f _::7 M "\:7R+v/edbCzޝΝF?oĺFat ؃x7a@Ka'c2y;zy),DdxzYLN^^ K._pЧ>HJ41ڌM>zGYPET7TjMWeUS@; Q'_{jO1ZM1}w/!J˺psw9$hRhîa) k$_rFFM^0eM&4zjb&4zjb&4zjb&4zjb&4zjb&4zjb&4zjb&4zjb&4zjbhW~J,֩2zdYG)Sn/@}4!oX0э:ٞ U&U $rb-qH1|1S*jCcFq-CrLRۺU S#KeJ'i oi C91U8яe|P}t(} eVLр˹&:0~;m'FjL9[8_ &oW"vFYDVtS*+rqvfZ}p_Ցx.(UFuUMu򓏱Pec:;ׯf`@+f=\vxzFnLj/jǙ~АgBL ׽\<< d?{ú币#'֐Lqm=ø/r1| *uo8L=Wq9S0x>.zX_HS/G3^߃+Bs5ubwâ#H_z)h绱_NTu˘?gH*o_Rԫ Ueזy. i!F%)~?{͟ni62ի}P|&P(ydzuq{.|xQ!03ޚw:dEaz XwوwJ{W׍l)t4qiS&jg'?/k|cD~Nȩ$Fϊ-]J4nXs r>7h؎2ԋm)!Ia˫ͯb]:y^Lˆo@jJ7^>]^yV~fdy^v2i1נnoٽ tA+amb@Kװ`k/m%yn۞ 㑷T|+rr<<;~|#F["FS]&Bj+ڵ}QZV/kdhS^7`ܪ:dM #pUkħ' X 2uDrLH+ٹ8 $_z~yG~E'hϹjůRly*BEX;-Rz8Q/l+oF洑p8|ewP r݇r࣍sH'-لTcfZ𚏚 pVRS^7(գߠAvH}U%>{-  % wn`M %,[1-n(:^[sY|l FTr4pL%\M jޒ5xm&#MV~ƷD C} ?}I~/ώ ;e@~.F9ͻghMR%=++^U,GTndj"~D6xKp~7O P@LmDmk|?~=Jsʎ0QYʮs5Q@+ټ맏:Q4i^]{'_A} {0S;x>o@DRp'^>Fk@41-HP:jt>wI/m+) hu`x,[V+"3}A[g//bvZ"SdiԲx8_p i@-?z@ީ;|~^|]d/HuTi4Yt#̙"$Iz $~OzR;I e.40扚*bnt a׭XjKőU8s8U6gLJ<^x:ܙ/!;:GT7b˯KuU9HC/y=nˆ'C~6#xWFՉ|?B ӛj>EZprޖRBaށ$輀sWJ%pR?%=++F{KmH{2K{K{K \{V?χ<%=JX _'ק`:8b?ۈ_q|ZDaYn/RҿkCbOFQcGᲚxbo?e79Uġ~Y`|1 Szkim'N<3(>--bh< OE(6;ӧt MnJǬтv7 tE?zG:Lx~F6dXs}Z!&p|W1~h]DJ~#,^0~h@ (w҈};مdV02HV ]E??Ogن qVSyLݸ:E<5ݴ+Վʥw3xޖIb֝вxwIgC%c@+O: VC})'!{3g$Fp=%xu1B+h<ɺ'=hW9b䳽X൉s-/OdqZOm-h_×_|/E 6ō4-:ZW[ŗ aַ"n䐈V\qU׮ QcaHPPD{ɝ9pJ~+WǰQcc1fQ:pC\C}c][B~}}j;FX|qp+7~T o&z;lxsKK71s YݻQ=WYP9MQ&~RЪfk,E"8o̞$X8Oo+Q%8ָNidgG繘 UguvO|8 U13ؠIk2K "#FL[grUTՀlQ#]?)uㄘ1ηE<~ e]Y1ܣ;+ƞFG΄,ԥ?6 u{:ZVazrtCzxCzIzCzzCzi>Itd<\}Z}<30.LZsǍ~ /̯ig)G#crt=B.$Z՘b!|Zl@cZ4ײhp/!@[p}Ò0RfF|Yg!Z]'saH%z3G( -^4_ j'FY&>-|Fc^bwfcZ}hZm%`ԫF,wufP P@?{o#5-ҎĀ=({޺p{Wn뀖,wσ-Ǟ3 ÍzZK'"AAR1ő$1"nH"eѝ.-lq SS_u'-;'K0T^p4UXƋ@vKC.R?mIp QZR {& Mr_dʥwSDC{ ?'ORqqwWљ9H܀}؝p{u}yȭӆZ?@_qy婕,VG3E4$,XyWy%WޯNb`X<>{+bʓ`J7l~Ŀ[4|bWzi $^vry]yX^YyZ/ϿQ tCzwKPq)-U# ZJZ^~6l??%Bl7P6g* ESEū>h}&khmz|=vƁ0Iuv tzCzzCzzCzzCzy?Iv Q{ hyľWsqt ~^S7'dЌgBK:uOFwQN< tE5_S[ZjëGW5P3Iz5,%𓔋c?B{J/_yS<+IT'CO5ȭxޖň۬;#Cn(6C[0;g*0--_,*e"xDp~!FI#(=)oV)A?<`&\Ӽ~ojp\$O_>/F/|1&םGxq(F/(F/re}|Y\0b׋bWgA8SiNb:F1ӈ5/.k+ƾ]=Qi/#_M`;K{ vhez|=h.=+u#=Y$!kd!kd!mdaz|(=b_€V_:9d h815Ԫpafqe tE5_ISv1 D1 R٘= m"< A-EM(I+IW"ǒwи1Υ:"ZE@ʿVr}mחG/yo^.5a%۲ϥo~3_vG\ `QĨR\cK`r0ս2&Y quKLfӮ9Iهر@=yҬP=9uKLjW<P~)?$'c-9s Y^~6VB2{*;g9'^/ڟ[^.- yX^IyH[/ol^񞇴ח_}@+Zk0[>iƒ.%0W`CvvIK?L#A"n\^F3-x%4l P|z~;Vl fL)!IS/g}T0BQ42V>#hs}9pu͚͊zkQMEX"O*1b5%^ʱ]=[¿[gs Q\NyJ_Wg*y@MxF3gN,BZ+x9%g]/_!շ^?z\_iR&=%=y]/yX^>zZ so!q% >ŃhYA0"h۹893SY3#2̋s P(@ P(@ P(@ P$V .(p7l_ ;a/Ǿɾ7,؆mmw ȐS+?Ş)ѲĨZuJJVkVcd(@ P(@ P(@ P(@ (U1Q+ฎӿzC9 ;}CVە8xyC9K~SԣdFЪ_}kb"6Ot]kߣs k#t5u-.:ꛘd̚ 4+_w 2?- }! 0'x g{@qlCG}yiL8Zqo h<}qô؇H x?}DS;p+8a=<5vW>_è \o8GNk <3=`ZA|pqoϓg#e}?>:9l21nV}.قxoEo`[xφ˵zSG(@ P(@ P(@ P(@E-"$4uKo<〖_,%8؋a = P,Hsm,=k%l=+}' ǖS,o7NAEQt wWOx+cl۪OeAs㶺R$f^RϋV蚨ϟyz~;/=п[RQ:%0NPr2g@O˶-dٖ{򅴮ʿb&[n5>0Ī+MAbkg%bgޘkęw<axU;CW P(@ P(@ P(@ P}Y\};(9UEiNI%挿{f/ ;|)-QR_ZVL8U P)k+eh:VB%^=WLNG?Šyqؓi SzAҾ/~7Ռo'{66k%|z.>T=5"qKrN req.dq(?_p9( : ]!l fʊPX:!+%״Sar=a1kC賲񴒾U~q[\J P(@ P(@ P(@ P(@ .pr]On'+]qZ_Z\v׹˜ۋ6^nݲtmMޭ{n٩k 6-3=_sx6sj͹ֽ׾ϞZ[wSk_[dsVp'}{ݽcYerΥ*wMt=tp7Cj܆Iݗ2q޽|}@c5ks}sNҜwSwwn~rP!iN #l[>w|V;&9(@ P(@ P(@ P(@ p-(@ .mʖ{6hQ'KtO'F*;_[D 5{>eK09ϠըTi 9ոeo]1`Tv}Ҁ0 q<5u{}Zg@Prov.P~;aOX|fEѺ%xdb xu\Gcs+ŌBH|vl@9JY03t=v`+&.;Or>|[/)@ P(@ DXƍ=1"{(@ P(@ P@ hum5(@ R7%\N%l?u/ xZϭEG} 4l_ ;!0Wd ?G'NT9>plL.R?lt,JM_۟A0_j8ڲz>|#c:U]S5hيpj(3^/s6Nހ;տ~^ѱkXw.T>H^)@ P(@ DN`<&߿?r;(@ P(@ P@ h(@ \h)9%FȚ>̯)FpS[PxNfDo)veb¢ހ,5+`_6m j'w"(8tkrЌCK뚀VdYln6aU?tܜN{ZUő;{an%y%'̿Q_ӕߩ#~3ȝTH^ǩѩDuvv"+5KW; Bq*&+'ъh*/Z[e9NJt-   V`ʔ)N?n    @: (E@ 4VEW&4[P9贴4:Gv8>7(ea!X@'*z8njw)W ,%VN||jh3/8c+ڣO3[@fk~mmw֓o4SuCfZ[Ԯ_+5X!ERNw.T:E@@@~ %     p.= h~zKi rsޖ9@@@ eMh(    .u=?}ּoʗ_~:]6|&fgJi8 >S-7++{PM:UZ5i:_Ĭݧ?{WO4mjDV&ߚU@؁wTs] 4N.a盪Q}ܯig>7F9oQfFhJ@@@@ haQh    \־UǪ;hGGkhI_;շ՛5sq&[/?ʎ,mUuc)^PGkN -,UR]2PG{\YLw9&h @@@SVzօV!    @ h߯}NrTp'wЏgѼLUP?z@ Ps(3ӏ-uieAj6,s#+,P_Reu*{|Ga{Nkah޹P!lxH(؂RX{+K붕:]YW/d   PP     #~zKi ZyE[/ hA-(?weRU 6<6iZ8ZWy:ҽTVK\иX[囦/ZOŕ?Oi%uie9 ='3AJ\d9qq@@@EVTv     @:URM oI?9DLЩ،\Њ|:㊟;O[}ZM@ sM*񵅗L_啫奻n_C/Z6ݫ`{ktōKBOiP*nם d5i!ZbWiqm@@@KVzՃ     Y m@˗_ӕ1`2tgBTMʄJ^3:KuTnjЯ=7icZz,8 mW]7#]:p =>uxl3%NwJĀ^{hC;ZlE^nto[&UZuCgC   4)@@@@@ 8f|K"sCGGkAkQuj5F|UW+=--PuZ]Qroƭ_+ %̦ԉi_CτЎ8]S1@@@@_h ;E@@@@i@+p=hሕL :fC*1S.7,Yu;ۖąb_ʟ{H~8^BIiܩf`35Y^asui5~UZNJ#n ι֌Ql 7@?,.͙Q@@@BVZF     @oz69v "a@N8:2{֦3:z86oF )_3=㠌 3Ecm7_@@@@h){D@@@@ (pZd8   xNJ !    pq к"   `-*F{@@@@@ AV@@@@ hYAh h{_̹K%^z=㫿PfW{{}N5slK8׻;Q͡:vYeqMFg_A<ɚwi7k$c5ժ9z"ta#֨ۃvvG@@ a    `h2@.@km5ojzp&:DKޔ֣i`}k4dWye?\{r)BOߩNJk56So*>>2hONMLh۩%wަOxQ-4@@@ok7/z.>VMJi8 >S-7+Z߭f)s$e 3 czj]vM /gCB#_NfP5hMPbS~ekҁwԛߗ93MSo:F9{ʴN#>)35~lF`'"0nZ&lL*Ԡtwl:v@oUHmm5tP}rt_L۱e٩u/Li;4Z??{]3e+Zn_.!7ʌŦ'v˼SxΜTvqjpzfA+~ &xͫ0WPYUS3њKPISh+ SG;pёnjSq26n   aZ..   n U瘟+cNŎ2''^]&hPIO_SR Nq}[ș~B;C *n1qSNI6;瞏)/ȉNQUhs&w1s_^^m%V[9_y~ثݩ v 4_۝+=wk(),lwu2Z#Q^K#ֶݩ?rة>|9|s}5VE݋;,4n*   xN`ʔ)N?n    @: 0t p>ZvW>xֿi&mSԧB;.߆~JjSלּԤfE&G' [C6wBk]'wZenEм*{ մo_d4.iX}uie'-ﴵQT#h_eeN@pmQ  `-OA@@@{M GFTǝu)hsʌ.sGj.W˽;QkHN}#h]~ghA۝EH[j3RuIlDҤ+c#VUqgxrtmcs$aNHTH[\~$~l>pO㺟REFrwR]]uזQLϩ<"z1#]MIdzQTIK,6y`,E@@Av@@@/ "gP KIen@'t)p̞DA9Ѱ{GHxoDY)  gi)JEW  Kk@ˌn_;}ة(+sV=DI_Ph@`GU})k%Epǟؑ|hP+U@+U'8v{bS*<4cׂN-9ur8)bk<ǹ(i@IDAT:šϩH*xRSt{~J >;էԌeSvH[m7Ή) "rA.G@@+˹~o)N   GF *;AMªK: CEY\;~J8FIjgAhirݩ|ѤUL;ZSU;iH+,7@@@@ hCh\nɗ_76Ѧ$):.<<ޏ ! +s2nw ܰςP(voAs11Wn*ޠ;/31SOn%ꅕHe?-HSTVijfw* zFN;[ݠT!6UuAJG&uvH!  >9\rIV2n    @JJ@=fX _B"!J݄AwY4/1[&F$2#Y=)?>J01 ͨFIúB<2Qs?TKSmr)N/+,wvG/焦t\ECpIŎ?>dF*Gj@WH#nL' BUoY?lwTMhá),^6~GQԾxOk?V4T%5vJ#F\/W@@w2n    @\lp @sji70X#ӥ1ЪgNݬ;`>lPgk&ij2++#r3'u4:c4 hݐVsv A̹j%9 iaI;ziQe1nμFWܸP:ig󰵩U\ٰu[tSY+:y5Z_/_}{ c0l7/t@@@s}233pnf-3L<@@@@KVs\@NZk~ty7ݫԾP#X~Uֿ!V2 .;I7'N'w>-}_j rA+ |Ik|miePֹli  /nMoC|cӭު;vC_@@@@h5 wZaNi`7jS[X-s\8Ə#ҁeZy2R;=~EkDhSn75Cx38tV_   eeeKxn;wns<@@@@SVsl@xUr/ʅk`F3.\c~48e 3eCq@@(ZZZ4|p577/ɓ'5x҃F@@@ uU V tX:j>wч#C6LKҰ/̪X@@~?CJ֭ΞSE@@@hP%ڈ    )~K_Rhٻᆱn)z<        'YYY     ЗR}!    :]cs@@@@@;Z @@@@Z࣏> 3LZ!   )@@;g        @@         pq к8Y#@ <>+B@@@@@@@w=Cn ^ӱ! 2d,^        xT jll5"aK. @@@@@@@8>;ZcC@ ,Ъ@@@@@@@< = ZQ ^օ7       oZ[#= C(VC@@@@@@@"Z"hy       )@@;gi(@@+ B@@@@@@@8GZ @_ +I       @JZh]/N@@@@@@@OFK   >@d @_ E   @'IMZ469Ԕm[~;r5\%yTc6th-͔Q q[T͵*7z$<\nN<צeE]B'˥YSV4}@2Ђ6UQºa,@@@@R$ ph]xs   p jU5U[<2h!NiEUݩewd%֠iaqg?ۖS]N-ոqҦ}5ʸ   }"@@O  pݐ=    й@*Gi5=B7S}+5ۿW-'ӻݬg5wф Z=`7g?9H~gxѫ۶H|M_j|=\'eF[ 5J4+v:nYYsY+9+Z)    з֓! k:6D@@@Go+o=f& 7>;[M#pԊmhnvK׌?nV[QSG2S 2S z(hLk+\R}zpM{RJ4XhL&5fh|   })@@/5 pM@@@@s.౻BW)}zpͿKW&GoּS(6wV`/JX9z1LWMf hm)-Dv`VVwdv⫹L/pv~RYGVNÃ#z%kۡeva=@@@VVz7@zMdž   @@khOeumd,/߫y[J2jm3p`r+M7kg"M h-ܧ"II+T\*p\ol~ˤ2u.OAkœ/jk W)!    u~\+ pΚ @@@G/Bȼ {zro>~_>dW   @)|]?oZ 6EnH*1q`ڴHK_akZOŬ'wjunVE<   }(@@1 p.Em@@@JM5oЯO)WvAxYs FOwi+th\3}` U3t32fkҴ5nkbt}s[9VjzobBLi7ƅβ͚G;f%@@@8KZg   ud/    *HV*`-#|M_1$zg@]nY;F=4aԧ5U̩ !1Uvh- &} aL heZ ʺ-ʼȺKB,4@@@@h%{BI91   ] /_3WFN A.7Sr@6I @%U[Qݱk˔ˊ6PEYT|@M[ Fw~؅t eN/YXͱg:ldrW++)w@@@@z*  u!9   *2+6ܱMsxJie>XyG$$2vHsÓk7#o_= WV -%К)|k{,> ۗ_jU556uZz&g@@@JV_I@h# #   Е@^ZW]QKs'LUsnn̋h֚.3Ҕ~|*yT[nFz$:6ZVuo{Vߌ~UkF P5g*2h׳4]\v:b\r3E#Zf驽%wY   >dG :7?F@@@;i#kO?bO]]OmƢ+fhE*zTiz`Fɗ;*4ѵ˟|5!:>GB!lǥR,{RʬR[o'LWrUojӖQPKiVtzw,wÎ_ j7`լ23Vxif/JKviFf1u<    Y: ,VEΧ˾@@@ 4Gl&dFZ}y,Ud-WMZC̍J[Vo=L v_w,Kc Z:Xf,~*Nܽio= 4#h :^%*6kNg흻    pU@> G@@@ 4/WxW.F|:STW j8uJA'#m5_+=|Nz]oO4쓟U c+Яt192f+@@@EVTv E/@@        AZ,*v вn@@@@@@@hu2@ к @@@@@@@ $@@As@;Z @@@@@@@hW3Z rZ       <tmA@@@@@@@oh#{AY9@@@@@@@NVڕ!*@@ˮoذ!9sˮS/e]Q/j-z%`Wk_.ZK^v ZKҿ]vEE в_B OjW/R/ O]vE/e]Q/j-z%`Wk_.ZK^v ZKв^<,@@ˮz%`Wk_.ZK^v ZKҿ]vE/e]Q/j-z%`Wk hU/Z eWqyJ/e]Q/j-z%`Wk_.ZK^v ZKҿ]vEE вA^v ZKҿ]vE/e]Q/j-z%`Wk_.ZK^v ZZvՋ"hU\ޠR/j-z%`Wk_.ZK^v ZKҿ]vE/e]Q/j--Ek@*.oP]vE/e]Q/j-z%`Wk_.ZK^v ZKҿ]v] ao%m׷MxҭӾs'k@ S/Ғ)#-Pb=W)q6n[&;SUq j[wiUMF{^ wjjMZ9wl"x^z? ֨/|]sd\h=S퟼[MgH2Nk}59KF;W[K6u[Ҵ5 __) =y^ U~WɺK~Y~y_/*򚴾^n\W/u(XCSoޭYv^z"/K^<5ZV%/^7xhT&/+V 92!RM3Gg6lP x~Xai>j'W~k%*Φ~z;A=QwohT*7?LzտJ{Fow,-T3+v~B߽:X\[ nvCX/>]nfFLg~~kPtVߧrZU3+XժbXqA叚S0SaZ޻aczozë>k^yx^zC5CGA@+EG)@?jVt{ {5ۗݓ4;͡oxxqՔi0EmzWG?|TÿoDпf4}31Xozc|!Mpkr^Wpl֮CӵV'}4iGQmq5_ >m=x5,3եbUڹ=klO^[6e_eׇ]U x~Z9Y&5&tqeR}|pӯ  Xf ObUBpovj?+ pF; wcgڤ_+K/_3Wnr:+vV #ۓZ޻a{:7^zEy욇wk^k3}6SVoP:p: `k*kyDgMv^%W Ԫ0ki$|F9~pL{=cy}|dP/\ܯxm˦O{⫷V#L%.M$GB^WݖB4a/MhǷ~7>b B#XCW.7kݼ1$zIC,k׫'K<]/]l*H@++<֥?6zս?+|=h~zx隇kGyx^^z%5C] y7Z?rO.^c oAM OLʍ,/&X <@)}IݽtyD !h. Xf!}گj"#h=冟KC;ѿjnhP+54aذ^+Xf/ԫU|_pX9Je:XTQpT/ӒWzBS;3Ϝg;<}=G+7 t_텙h||*W/??\˾z^[ViRԀV ~xߍϯ0#5MV?*rh]ApG>ki pë5&O^jzëk+\z}@*nO@0rb$3"+H7wW5%;P*Ȝ*3ѲipuHU)^muw[bk߶%EWOd84'wRnV|9撮Zdꚼ^?^a_g4m>Nc⺌׮y^Wyx^^zyWk^W}Ǯy^s̓V'r_oo҄{ K"ԫdd4]ZvE?{ O"@g]ײwՠ}zdCZ܇z%y^u&sK03@H)]莑ѹ"Ie^W8.uaffj43:{G|L~x^vkOS4&񽥚0CKCnO+P¬[B6Ózac0`n_T)|HQGL@ hv~.h>;V@$~Z i%@@@@@@@|;z衇z}*zMdž         @a)         kZcC@@@@@@@@@kZ]@@@@@@@@^ӱ!         е}X        ZV@@@@@@@@ZV>,E@@@@@@@@z-@@tl        t-@@k"         w/QU_[RR /`e⥀Q+^ *-D?Ji`Ajzb+p 6l%x jPZCIjr0I=3{%'wٳ~铗 |ezw1 V쟸]7 jM3$q!" Xw)T/{ ?cl}]vl.&p]οpN0[,7Pwt M;izxmͿrdtk@IDAT[TWTmQ4t(]p_c:̜6mW}N<%;{օ*͌qk]6_-^W}a?c8pK5~dK.mzJGr߮oL_qOa6^yw<.0TUe?G*zk~k:Fm.]{MOCFӉcdn*rK +[.a@+.0Ro>+2|F'pN5~.@@g G^}ںu|>Iwy4hJJJO+L/_v^Yze9s Z2[ѩL:1Q8|Tiu)T51W^Zr.]ԱSF3I_ڵ9Vԯb]gfMp^h. TzNʗGifx= kZ4kd$S\6aɧG%5hZ䝒wx =Pgu]7|SG'Y33VxIAUՅκ-u921΁:y;T.@@ 0Y#UZo=4_h>ed뿦Ə9/r4/G[̑yyZ5k؁qh˫7 9.S-4n$>0{[^;'8;cݝ^z7S^QEE6V| 辨c N͑7#W;914xD[\c!c!G *1k1.;Bw̦?OMeU8[q.ia#ey{RN^M@Hi{LַhСjZ:ߞpġ'& !    @ Jah гwlҚ5_.ZS3_ߩ P5ՠr& h1-s\<}ov59P揍jZ7WNoDƐynvq^1f)}if.Ӫהģ^>@ {OZ Znx)v wZ&`Ov@)К<85ΚEWb7ݧC!mOg?+=곑SѠ$*lf*t'SQGwG,OwP.]fvU%m;g.@@q˱GP:gіʰK|yvImLUgs[kAU[A_vJUp-5U?K1pײ*pgY?=Qs ?C=䥨 ;/ +lҤI     ]]θ@#ɵ#Ѩb7pQPe )!pȧͪ(pA冃JQVuC(PceCT;e:YNIʉAXUŹi'4tmn;تo GZꬢl|U"sЪ Lȭ &ߌ2JY\;>7~诸e5Xᐜk&KƦj+?3bhBGiY\Ĺ*dV |ןuhk-bmIyGѝ푑g>hk Pyp:8%9o!.@@ ?~/ikK.~gZW]ugj Ū(*jks˷J?4G>/{0- VeyURRbVXյ* B̎    @ Jah sbC5/>oևUu%n'<*vC`JP]VAl=?nVd0K~|Y2k ,>V[7x2Ӳ7J.xdYmN{Q?ûN3M]#7f#8{w5sߟyuU@+swʲی *qrwE  @t'3f\`*B-}]7{9;V8$5tts^rxp~hwH"vmC;o r¡Sw    @ Jݵad Уڂ;QEv{r~az *5M[}*[Ҽ4-ʋB-!@t'P]be~3r n9q!.{^Wgvh 8GX[ױ/_UTQi廁%Ua(Qy;Ce `5TYyM r VK]KSϮSۅdwPKYrZqE#@Pͪ,ʍ sY1GRynj7  .\h7降3ƺ=;-ߣ )rt$Y4$j]U:_e;CmeϚЎhU䅾O:G_    A{ʼ@@ks[i꟞}:Q -j3%҇jPzeڬ ͡Mi1WZmqwZA9 2He]͍t7-]CjoM Cd-z}u1J7m7ӧvJ e5(vcUXuFqu[{]ͺ12جKa N@@`wu~a}G-젥SN9E=\/_~9do6o[y]of|w}nSW Мb4l7Ď#JώM;[tc8uaZaYU7 kt<Y>WjڐضF歖 hiv̴    @7 Ս"K +/ίj1]zKSɕ1xk~dz)W'um4*s|rmS@wq|I}I젥N;Mk׮T/V{TTU}RC5 `ܰTЄ;/mp:Z+.טh{vl~U;J:_#A];O4Q4JFω 락GcָC/?[kY6mWrO1'ĸڶIU4W2=iiJ7Pc' h.Swa;Մmߦ2;(#.@@@@ % رi^x}YLM"lv-)˪B2={MVؿr_~7iBכ#7  $538CGVQQz^Z;_#.UqV3UQJM h|Ok`t4׬gS&DJj޾V3 fZmqwr Z2;\ϯ2oݬ/>O+͗]W^9혩ddj5j&jPzLڤO4!KUkLW]u@EFЊr    @h @z@.B˟҂ezr0m@zyOj_җdyg=jݦiநv9qp}9L8i90ܻ/#GwϾP[͜,t7P`v׺ɄkޢsaE,;fT@9&1< GH|οKKZVJleTƘ:+'[2Sog]V(wks٠fwϒ oմ->bbx    +@@˥@ 771cl>sI=r{aoa  @7߬ 6ߏ駟._] x hm_3WgۇVK ;6kD`EYq]{߼{uk絻lNYdfiM"vv L7EkJ E'cwgVIZMN?z5r2eWOS;BGaNP)ɀ'G nYy-0g0}FXqY5Z~05fsa owNhNSμZُKY%O 5MiĻ:jK@@@@zް@@@,p>ǤZ>Su9稰SdvВ?Wew*f]uİ@š]r@+Mki2ɗڭK5c:xA+us] heiMo7&xQ ;v)H6=oYX/#GKFTf/Oq@0Pi7f ڼ~e^Ӗi@y%KicDEZqSZ7wltμ9[6ĴB2s 4< F<4O@@@@z^L@@@)p(==]6lHٓN:I&L~3O͵A-m}4 2(f/dm7kȠSҭqwKT@ZGGpR^!к*m?pwV\cb쬋VWYš huf}@@@@*@@K/O<-OLB@@@@ ‹@@@HUc=V7p~ᤆ8l0M>]?z<1Q@@@@RXV /CC@@@ Uo|Z|yRC<tWhժUD!@@@@HaZ)8 @@@T8ꨣtmKjH@+)^ #    @ JEaH   G{*(    =@VXD   ԧ>EiIu}N3fc=;hyb    ^   @* V>}d?HjwGT'& !    @ Jah   @kktꮻJjC̙3#xG@@@@@@ h04@@@RQY Ѓ>;#!2D]w~xG@@@@@@ h04@@@RQQV^^ϟ=Xp z=#剉B     Rxq   (P__;N>n֤x1hZrz<1Q@@@@RXV /CC@@@ ^OZs=#剉B     Rxq   (m6|>=s꫓o~S=z<1Q@@@@RXV /CC@@@ l٢qi+t-T'& !    @ Jah   ͛uy穤DӦMKj;wxOhyb    ^   @* +0a~/Njgu뭷T'& !    @ Jah   /,/IQGnMwz<1Q@@@@RXV /CC@@@ 읳N "(    =@VXD   x5}tk:s3(77S=vD!@@@@HaZ)8 @@@TX~l٢:+!i{=#剉B     Rxq   (skѶmtg$5oײe<#剉B     Rxq   (3nui%5~.]-OLB@@@@ ‹@@@HEիWoTMMF֒%K<#剉B     Rxq   (裏nS}}  h%Ea@@@@z"2@@@@^^ب>:?O??;hyb    ^   '-ܬw}W} wz'u=hΝVO}Jw}~uU4'& !    @ Jah Zy3jҬk4*g{ڬִ1ӳekux(鍳UJY6johW^KGjQ:} ݽ}^}[: ҧFڵk{4z( :N; R{;Nmmm] |k_ڵk[#C-^x圇 ~"    @w ]Wq#@*dv}ok߬hF)Awl"T<i޲R-|yj:_=ug\9EW^iO  p@@ׯK/K.d쀖βCZ^^(Q@@@@RYV*cC%eU7XJanZscf%m47O i\ZOȮjإˌKqC:[Dn_ѳuFVGV6 +[5mK5kF}/Nev*il|@q&(@fNZG~T51D@>NۡaÆ<0eY-X}Z1A@@@@n(@@.CF)5zj?ҺcMUmꛞ^-ZNRN@oZeB [9Twݡru!ڹ~FL`+vjKӥ+m*[ݲv__攚9L*[_MU.Z(ծ@O_5s@wmҜaieY|Ģ3~" ZZZt꣏>bɒ%~sӟrrrtw﷬󐀖#O@@@@1n6;6]_뇚b|Yէ!gi \w 'UZX6n˾[_νA&պK^ґ\#Ui+-h }|N0*޾I/U>7T&]ƏwqWY6mWt~O<{a;dF'ҤWjĞLshj4c+ި$ 'ټk6\}_u_W/!Q]6jf=ƫ}{oϴ4gu1>fͿ~G{NeTWS#O({ܗ7/7LU(ͺjy2{L;%hoܭ!q7j߮}GqMfQV]}gj+MrG36jDFfcƩ8l/@7t~`*){W_'_Tӧj ~"    @0 B8>?r+LMV~K -[oU49dV4GT'hӗefeeXmyvWXY9t/0j.cl??NvW|yVQߝ?2X0˽mβ[#澌u2 cRSbu;ųWU2e:4?۬ڨ^6Yϐ)(*٦u-q1V –]mUWSmUUW[5Vuy Xkt:<4]7EƔW  p@6lHȄ+S6L@SyФI3j(b˰򋊭"+'n4b8Y%EfNuʯ Z w[Qm8뗔l xgn讣JWwZ@&&Vu}ǀUW-5U: 2ADjJV  &!j2w E- 2#GO:vҾ4O@x_]ݴv`@kٲeLEA@@@@HQZ)0 z@lǙ_$!,tdtIp `~U;v; 8 he&PՕvcH07GKZ[m H>+zb#ϫcKg?k GUg*s@o~8Ejr1TO4VS"|e;{ˀ]Ū,η23VfvU]Way *`eԧ>eM6-fZZZu^hy     ]=O D@# LSqpډAj2p`5Ӌd+?%b(*=ٵ+2HZ4jqN"eGݫ*ÁdwpwzdM9pXhw@VUwft MN`LVaM|̲xmh4X VWGσk@$[oyOuB}Qnnns˟$    _ =G PWEMjNªCTh!E C*(0ŽڪL~i|Vm\QvvkJ0nt>H(*]Vn hwNu̼BdL+>,⬟q8UZG"fY:0a4gq=Y9agNyܳ.vrp*PEn8鱗p@8묳CZmmx}yg* "    @ JхaX /Ȫkj2MٿŠ̼΃HV@+B [9YEN'3"rn2󭚦/ښjpi/Ъ ,*hYM[pNV=4=>> XEteXX9m~X5EV?O(D[Xn2dWs6-u"x V :Dv*4=W]jBAN^I.сȮfs@*CY .Z-G@˥@@@@z^L>Y*?;dz; EvxIq`ݠvL*Ɏ WǾp2 bB ̤X]{>ͳ8 hL N*'SH ̟TEn8 evJnϊh&0#jL4r.麾򇍃>LHM9cMp.ZH}?[W賔kE @]w}77]\ДLfGv/sYWUå܀VUwj]~YiNQ~"E@IDAT '/x -TD@@@@8E&/@>@w׫47MC RZlo}kȠWi#N㦪 iAqxjɃ`iҴ1v7rTgݭZMmks;Tlks[LXj;߮2Uj Ƕn7}U Cc @RB4`=;<颋. {=     jRmE Z?sI Դ&}qkv+򺵚ZҒt"5ӄ|iw H,R۪˵nn.3jꚑ[Ib;*f`٪i[/zdD  PIC=o=aEx    MVw[1ƋZQ.fOR :{puJ4nإ ? dm:|ӟ[;'tn.ZfO2: ὪG4~, " >C}Z|^< Q@@@@RZVJ/CHumk5?cȊoV~k̎bZzsd许F@?O 4H+VЂ]humD @@@@HmZ>@[qW>سG~&N:eĝZoO J$  @@ 5x`iFK@@@@@@ h04@@@z@@@@ *E9@@@X :crJ};;hyb    ^   @Oc>cD!@@@@HaZ)8 @@@$CC O~vmF@@@@@@ h04@@@zݻ5tP=#ַij<1Q@@@@RXV /CC@@@' ӣ>[ohyb    ^   @O}N=͛ij<1Q@@@@RXV /CC!|r?Tsss0D@@@@@@@Oя~oSDh%R g%EQ@@@@@@@jjj:!:`:*"!#8 @@@@@@@eh0@@t@kΝ   @'|=\-[LfTk6xg};@@@@@ >큀K ph~szD@5~x{コ]wuhyb    Z&@ u jA@uuu t ^u{g*s@@@@@ :4&  @_ /PkS?剉B}ڻ]}9ej~־oLی    =_V_cfDV7Y(  /ф Ck_D!^rT{ umP߫OW~טnzO_:Y5w0pl۵߫E;`=kB@@@w ]l@ h04@@ R`HRL@kAh`ZgtV$6Uz{xQK{ߞxYf^pI8x`|e훣j )@@@znP @@عs&N˗ꫯ4vD!^gЇ>"uw ;cZ\麳K CZn!Ocox(t;su=   S: ph,IA@4iVX3fx.-OLB0 cW}_-Ϙݖ6:L}o(](So[NWkO~Ok,N]}F,h#vսQO92M@Z1LA@@z23I*1F@@TxtE)//OW]u!D!>qYdax{ hŸUkwqQiv.˾Z8ggtnYwh[-z5ʄB@+jMD@@Mzj3WHiZ)< @@ }]~\R@hyb@LH0>}Pٛq;׾Gm|E׿uM,>mߪ_4KN:E_:Knkߞ*/+.ۿNvʅ:cxT)؎=ugPG7LGy }rFNԓuϵIwQ?A׏ M}Т 8mQӧ/45+[@@@zL@@:<'!L@kvdqhS nPDibUm0G$' #LpԌlhF9bkЉB< 5cgF9Z{^/ثF56U몞Y U,|oGN<}mB8T>\tcڱ29цfMA@ˑ'   @/ "@ Jݵad  %cMW [N*|(=V.^CYS'ط]Y^"d@X[\]Ce=9r'fTl@ D}|{C^ gXӵ0ϚԌ{vxܑBU:㨃цE>X   @ Ci @ א  ~[_W裏j:%剉B|2#??H;^ n e=:0΢,M 'Z;_6Gq?V#ADG8ڰ [ g}S\Q12..ۗǝb#"%h$q oŔ'9O@@@zLZ@@ I7VZ[Zm߷O}qoYHkykCʚQ3pH,`E56ܡ{+%]a5Ot]?{sD h%nv ZH   hEg Rs]  @ [:u{1]veZ('.pQzm< [oսSYE~5ʊ h}}ZgEGelCQs\b9.1Q덂5}quUwV%f íX@ko6 0|V6yڧNխ=Y/;W?fEeveiBZ1K-6;h|{騣cZf,՚u5|G;E v^x.qu9qAG=ƄLFGC@3#  &Zi+^  BofhyڷC٧NլeZxԮLOn"DZ&m^N3V;T} ?0 mx{Fh#~g~૬[?܅E 8&%8L4) a1ai$qø5hSaY`NCa #h̭usu:>d׹y~Ο\ՀV[J)(⤧+5h2+KOٸm5Wr'ۊѤ% A ;@@@2P :CFg ̸+@@ ;xb5VIвD!\ ZwirmxWX$rDg/hx8Ck^ض\oKd5:4UxvV ZMYǶ䱾13ʫkmNZ]$aCgkmbfmf IV%CIFmZ A+ @@@2J 7E' 7@@' ڵKGO?kVHвD!\ Z[Z-Qش_%wWQpf)¨ykxBVv5&Q4,xe424ʴN-S-%[h'n g芔jt+|j&\a";]1$QG$hRa   @ ig X:  0;w+_y}K_;l1Q.HzfRf?o}mi:  B*}_OS]s5Zf-[LBj* 4rFp6Qkܸ ZM5l@ORHuwE)t$=L]kV4, &S 6 ZBi_-UFxv֨ǚef23d!+~UԸMcnЂnR{ Wif PLF.W :Ic@@@7 h2Hk:|t@@ $hpqlߡ߿7ox֧PowWQf{^Mfj:ݿסzS)19ߚ嫫nM:PC~J韥srnɨ^KB@@@ Hʄ(3FH "Lt@@;vЍ7ިR9VA@@@@@HTD:"@VGx   @& wSNN}Yegg" A@@@@@HTD:"@VGx   @& l߾]cƌs= Z(     @V'R% :c@@2Q`۶m;vy1 Z(     @V'R% :c@@2Q`֭馛d > Z(     @V'R% :c@@2Q}kZt*[$hb    t ZJ @GHZ=ѣ{ue6# /Gv'm*G$^NTou7L_-eNMAtxY0`xA,]$^4$^i$K# 6WEeHMAtxY0`xA,]$A˂& Е$hu~۾;O RR`rSjx_L;1Axŏ/'F%~W|'!^NJ>6N/ Hg{vvVARWSXJ!v"^I@La+Ihx%1Ub')VARWSX Z)Ħ)@ Ztw$QW"#^΋ID:;FD="^u6oެo|Z|Hg{vvVARWSXJ!v"^I@La+Ihx%1Ub')VARWSX Z)Ħ)@ Ztw$QW"#^΋ID:;FD="^uz-7N/>/h9ٞ]e(l&Ax%1Ub')VARWSXJ!v"^I@La+Ihx%1Ub')VAV i H$@V"y1I#HyLjbG+/$QW|M6[n~3 2$~Aˑ-Ca3 + )x; M$ B$4E*W Jb ^)NBS+ )x; M$ RMS @"w&hז%Z~}ϋua:7Fs OK o x+j康Lk7\8u`*{nպipoo٢A1o&\u(&^-T < E3Є}9LΆ&^/._G&ީ[x7~h*//m==;~[i'wv](cT~Wa:#/u`*jmg0@*i|7>>M.@H3u_b]$L s3GQsII#'^_-Lr0_rP=Z|lV߯{?l9r]qΑI!G<yI~4?=WsǬm[;-"/w^Hx"/wU[(6 Z,_v#eХ<\/Iv57ˍ<ιe򻮍KycOjnAVh-&\p2[f`EEm1<5<P.ȉ^(6# O+jי2GUmfe"A~!緭ՈKK^Ho{3u|dy^Y:t]4+e}^f~=[CiG-S5 ZVl*{c:ul&{h],mY;SYllyUYW|; ZNl9FSYU>ig2]'wջWҰ;&9|4SOzRsM_w3Waz$nm,_;cBIݸyoO|ESuoxW}}ˇ`'r@ti/SV/|ӂNr5t׍[y5^ַ.7]psxõkmWl6^10p#CG.AV"@pWqMb\`>Eo޻uy{nzí 욇{knW0<¯.pkrG+x燒ۮy@Kܖ2LU jr28~NrA+%yYF%7ۻ_l 8'ŭi*~l\QۿvRoF.-QNz:KϫuEZjTwi$|YF:OSr :,{=>u`fJz/~+2n[ǒ횊Z-em.R?i#[|PGng1 Z뙐}KM;J#O htq57ˍ<\/^pk:Ga5^Q. %ˮyJ uLb{Fe;]_3h˖#zzu*Z?r͝88퓳|qc|ji:-+-{KH㴛uBGΐYsH.> ZŹ}N@/?vL-׹QF%wr^!^^}HM3^k-R]* n 0m9|N۷^{M7j*]qٞ]A&ЁW Ml?ԻE7}0DxՂwza8|ᩙ,?Mw_-f?ȻSň Z1P`WLMgj"A l2f  ke y5^nx[Y<њ5^V&7]Hx}T]ˮmRe}m)NVkh*-f/*7oWEE>τv'ή?~RA6lhޝN= ttrqu]76UghiJ_/zZ-k8R]}ufʷDq';Pews|n 9t>Iv;v˵~{~8kx^pkBQ?t5+N:_pk:GA*5^hw\Hx0+\ A+Jd Rw%h5epI@h=#±-%qw%TzsSL?f1Ihq[7"ס[J)-He%/b ^/ְ }'2Vj_SM2I&A$hYӉ1p *ۮ..6K3K3⶿6G> {_^ZaY^~e.k6O-G%A[WߤY@ըHL ,3 &qtWMwX,m[;S#*nLnv+52skZNjkrN >Fi`yɸG+ƭ</^pkzík]}>x]]v#0tk$h_l ]+-imfsr_Pɸa2Ӥ7iεZw O4@.ѥ{uL=w4ݼKPg$ % -Ps'/n^+!^~e֑c53Fo23>.N[?y#5G&iu ЖR }or RI p[Upu_, 5kZWJ v O/ERy5:nfAofAa(٠4n ӟzjSvpxWaz:Tg Չ?gVRm3_4oh$h/ny}=d^_& ܲ4vԣn__c^Ya+eZawO^GY̬5~zq/)c+Ɲd U{Wjԃx$jcf5Yv:Vڣ v[] lVgvocfPJ斥Ku1_/Yz?4軱uQf\Уt=qsBIDqsv+'A˅<=^q_7.xzcs5˼xõ2θe}nm<=^vCsmy$Ƹ'i2"n~xEf%ha6KǪUU?ZY4aNFhlpMZ{6Y.3ZըGӃˢN2Tv;|y}hWG>zR̼DׯkM4Iկ4p@[lή T,zYpE i/mPwgȟlX-Qr^n9t8~&^MU65;(=aa:Em_=^7iK|$F?i ߜڑF?H 5R[opK)G+ƅ<\on.x𚇫_hM}wKr5tWsMܴMghn8xE^_gs\1+yxIǎ\<|u $hY)lwDu BqW}#6N|t̜ȷЇ/:C'>N֧gZ7iJ):Y8JfB"ɺ)ݯydB+ydBy#fD:1e-dRp̓4hH,#Oϣt“5p=ExE{8rzG=^}UyZz 7gg{vv} SYJEN05Tjz[ SYJEN05Tjz[ SYJEN05JmBǁ8q`Ptx%q!$JCˁAI%gݺul{PH0D@@@_yCj^{Vy !    N Ai?   Tg?ohׯQeB     `   ^|E7N{QVVeB     `   HrS4     ~@IDAT Aˮ@@@@/_[nEԧ>e.fвD!@@@@p Z]C@@@M/Əl -[LB@@@@ 5@@@$l2M0A>O Z(     Ak   ILyyyO|F-& !    Hrpp   nXtnVݻW}54l1Q@@@@,@C@@@p%KtmFm -[LB@@@@ 5@8ZA?{OfgK߼Y}όmp߽>n/]  y}cޑeB     ` yԕ37)\.)Vݎ;@@g x$xbp&Oa1@@@p Z. $@pE3mix0%U:g~h#\.DmPI~" xt.R[c-[LB x   8Z-G!d@8*<SF\I@V+" C}YM8QS>pbw.Eh+=    AY7 jܰQ[O=&q>ʾnf wjk܏Ѡ>ZTva]qՠ6y[oyp.1S,QЇ}oF}X[n9+i邬qt#x٥Z5ce.3A+M&.&.Z~Q 7:dkQ  (--wܡ-.l1Q"    h:hئ{ιR bȣ=oj|V(Eo,ӞY:g-zrs4J*Z,IJԕ37*x6m uΕ3MO_ܢ6׆+b;dM$ ~[|Z`n}Ty7sٽXrTZp{4D;'_GݜJhGy9UZcHUu}`^;N_Ӷ'ۣfbLk~G+*SClx[#|`,1BW)zQQF2BԪΖohzњu};c  OI&O>R!x+2)    As 3+uMQ )DwUi4䟚eS ݡKOOsii0*dVMdxs4J %UtgZ4ǣ^m<~v[c*{ nf6$5hvO_y|nV,8Km%XvN~@fvWn%`G~2mN0+_{S_3CTqf[ rzRկݟD[噸4lNNgUiuC9f)U4[GS.W赵~rNlӘWʟeuP|k\. $ytIp5AZS 'gߖkڜc߷:kA yyB҅-& !WdS@@@- uޒlyͧ7{樶j+陼Pcw)+}ޒ1x7{KsL$_I߯* {Lwczjj-)SxDuٻ~~NX_?Ɵ7.4=a)k[fYǙS\v}a[}-u{Q˭қ{vI|voG5X.W'XQwO5=Q--7C133c۫\1)[w`OY(._T9{d:wl  aŋm򗿄lcĈ^@DW$   t_[Nx JxևujݻϟS5ckKs`L*gEڛURIEJx+]Z-)-.]m4= 'MpK}l;{"c1G}IN޲5zֲpVHєIb~Cf"e=5"15 =бͅ@r ]mX $5{4|X"/kz_p ?7;g~ xꩧJ=H.lSQ"@@@pK:{3z.8!V(/r}M7 +2_di>Ox@}Z=·"4^ޔ=Z4wǖ84՚.Yn4.R֙m숽@xs~e*JxwHqx܉zUڼJk>ktepf[͒KK}r_hD<fYoYRLlc!b}1. <:Xfh",4laxÈ_w}h1H88?]Nbnup1b' N̠)S$hKڞ0˖Raར@@@@I$h9)\-P-^@[$[wL1 Tu&Wr&AY5$hf-PYPf $唨~T NiM>>[POnlf)a݊kf3c W6<4NonysSK}Z1K Z&$>E 5 ZÉP;ғE.H_WSR O(\%Xm_8ugmm@pAKSNJ҅-& !WdS@@@-@C@-:zޭLЂ`Q'_9ٚK4LV5K5#f:3eVIM+Pi*!'Krj\զݣTO3Jכ6BxWc7VCfow577zj3!&hE'g2.(Ud^:wen ψ#8K &- 慙ԬG]N6Dw/FSTUWO'8z56G/ӿ![|IjxkS&/}#FoY^h2Rn@@tx':1 HD:C@@@PWw@ Xc{j#QUᄛ*CIR%iLi\N g~0QR>gwO] %n8/De%nrx- A5z*L=.6Sx x_#?IL,3In[Ch>2o* /diU[- &IoI^o}cvdJЗ>O_UtL)h$<7I[ LeqR6-زߘ_j5gSU˧޲%]yicb~ qذMcιR,Ua"#܌aY s4qi~[a(]3i|/Wc7ў NwD@,&AKӧOWmm.ˑ,[߆# H{j-:铣4~dV['ݑ5b |]>qr bۉoSt@{kL;5GL߭ZHmo ڶ7gi藮u9}=Xs`j{m=    fLTٞB>r "j\;Jh&e[OK]_XV|gWʛ_,̶SA4p<'zÐsݞ5lˬQ ˼Z7k7\V+vKZ׿}۶o- /l(3CgxfUy|9%5Zx[h,d{LUuqC jCZg-㭭 ϼ6嚽[g 7;XAieԒq?s@ <ÇaVTD ^Q8SM9o883fl͑}0e-mF_%H5ߧs}rW[:,t"=7cx#=54۷al"   Zf2W!8pT R:gOݺitSf6س_ڐb>QL\Nq%@@ &AK:mu jB n~8SbfSꟅ8 -&M27~<~lSn=KtzfZ:qmǕZj׮3C[kѳNYVgj9qB拶.YYlߝcF@@@1$h9&tp@bTk'W٩;aM ,sqR+f\N0;QnD!   @bp:.i ZQ'}? ,E;An )B Z j5*ԃHVhE3m) Z!~"  d Zft@+zqFtE]'s<%n3rEf_kW=y*0P=۩9 ̟?o٪۠| @@?3fȑ#:lՓ [RN]-Gfk8Be]1LWY#fUffps/~E/ }mQoiqCշo^ Z'u5j#Y1F]zY7lfWopAqF/W\wE3oA;بo#l(xIT |CUO7 QK-~[qƹl}eϺl#Accyrb ZQ   @ g Z1wr"GE%c~ @`…9sԫv%]! @W]ecܦhs" W\s<eLxJqUo cik I*6vE3-Ujni {@qY˷b͊Do,9Zocvo-ͺ~_֕-qo_e]ef j5tlժBբR9f*/Oz7mӭ&m) Zmك   $hed4   x4k,33s9:`'VEBW AlTLޒ'B;1ڣ^Y U6_`$9Yޠ^c1K [ ZݴhicբSTS&S*VVx._?XueVjW%n.?oUƒfV,s+tU+2鍹#5|MIК&a?a,m|bd.   $hen9   )xGo[32Z^Ggbx^8A+@y턩иsJTj|ѕ3})J%%:ɿc9&ܲkLGN53?@@@p Z#@@@@$h9>DtZ0,[/vToPkM׫8 'KY E" P%K,ƛ*nu&-\c~^=vCo55,{CzfWBJi̺'yTYCW.NE@@@  2$@@@(;묳βœ']تB rWD=_SV_isJe{<Z1r2Mx o<~>R4AE/srTIyj\r|iX[NMSVžuOc͠ux:~WYp~\}5lH]b:$A+{   !@VfęQ"   ?u}W[9y҅j(.^qpYBܲ wn&Z8٣}+y u`33hRxD_3SJ'onjiدިO+ ZfNPL - f,&A(SnoV)ӸHYgF"3| l'LŘA+TG&IV$2VռJMB7O@@@\ @ @@@HzHGl'VEBW {8go=OF(=ڸ1evWc7$@"Q(QɲahHTdȾze}(ҞQUU便p#F]f,UVi AQ̠` @@@ Hʤh3V@@@P`wFyfԂ\q{e/"9"`^YX/\-SSC g)Qi-N>%Գ$^2WUNS2cUrBo<['"7O^sr _6Mbխ"Fe?֔ s2x\} L\IЊUOxFZ5uи>u/TV!p{Ŧ ڋ<-@@@^!@@@@ =~@'Nga.lUG!p@+ZN:56=tL)2678Ow-:zJ2 Zu&i֑͗ ZQ1   .@VG#   &EEE={tu.lJ!p)%Aͬbhzѭ7mrAjE@@Hw=@@@47o>Pm [LB xHS$hq*Qv:˵40jx8WNC[hcF!Y@@@$h1 @@@ %\"@V ybnpS\ACl%h51hbEʞU   . Aeh   8I?﫹Yݺu5.l1Q"5O5Zѳ콛PCCC=ϴhKl"   ~$h_1   i)zҢ}C@҅-& !WdS@@@-@C@@@pܹs5gԿ˿I(@ ^O@@@p ZC@@@=~ifk`$]b/{E?@@@@$h9:c#A6(Aiǎ %f/Q/׶mZ>    rRNN   dٳ#6 Z(@F <     RL    =c:qm lSQE]֭:;/qv"    A+ڴ   @ |Uqqm+ed7߬+WYзرcbŊ3a    Lz   Hr]H]*zj}+_ه_W=ztcD@@@R-@Vi@@@ ???a[lSQ?/X_ooz}(j?w@@@@ Ai@@@ OGNm* "w#i~gF@@@g ̸+@@@\'Kx'HвMEA2Rwрƾk.}ӟ@@@@+HJ}F@@@ ^-ZH GMm* " Ҏ;˵m۶`    Lz   |ˎ-^XFm* "SnqM>=c-8   8S-gƅ^!   :Yfgѱcl-TD ctE!wyk@@@@$h93. @@@ s=*--7c#A6ho?+VdG@@@g ̸+@@@\'@Bʀpի}=zcDG@@@@ Z! ~"   @ ѣGm Z(@F CPF;0x@@@p ZΌ B@@@u3gԒ%KTWWg{l$h٦     8T-n!   63fLG=4lSQ@@@@*@CC@@@p^_WC#A6@@@@@$h940t @@@ }zuaC#A6@@@@@$h940t @@@ L>]媭=4lSQ@@@@*@CC@@@p-?סCl-TD@@@@ -@@@&@"x@@@@@ Zv(   ,p]wiʕzm Z(     Aˡ[   M`ڴi{g{h$h٦     8T-n!   6Sꗿ7)OT rqy'Ym[!}[hi8=uqv.蛥}:sGt]ɁԷdSQ;P;ڵk?vHвMEA2Ts |aӹNU]:X{\.4K.Q;6G4ߦP]( >IZ}fZ덗ߒ.33Y K3j9Oij.T._ ٩L=ꉩgS`  $S @ u{%bF{o-}<:{IRޒl3V3m{U0<ș-ySEJ6N=_c_Xf%Sb֡BzP̱{הxkc80<ݭOBOO?xȯ:ZV=,~[zm qޏ|#q:0bn +&3|\quVxLV%Ѽ']3s! @ ؖϬx>eAtϙv}ƙߕyb^?R*oaNo}! @ {@@L_n[=e/)U2E[p[RU"{CmWI$W-,ǻV@H ZI@@s7E&ǻOn׷:Bf-2oR|P–fWJxKzms_&yM)$b5{7g2_1M8U{J{9%U譯6pJl"1ΞUX|'&l9j}DU3u Dk*{0j?;VC&NG?ڮA]\F 8WLBc˹bwM8*ǻ S GOm}щyqN]ϬĆ|fI,qo)g#I$؟3k>PNNΙ>hfVꆯr SR;(?* "r VFDQbŋ+sw!(JUebX%<!HX AH髺g29f3%Uo}~}sgIF(\[HH1j @C)ڜ 8x=ԬG1U+D]2+ gf4" [.ǿ3WͰ?nN }}5}.95W⍌؅8C= #uDLZk(\1z J]ŎX*۾Oj.ĨkFKիDXZĖR oOn7A 3w4oOri[3U`ړ3嵡|o}Zot}v`'h"+m۶"D\{hMlϾphSW {OKjg4&8^X8=n Ƥ(}<̄')05ePXQr5%WuO1m2Lҕ3 ѶrqxUWw6׬R (~Kvb2cwE/' Ъ˹.1|Z=ϽP;9Eo`q&l{8=޸~̋sͥW`|,W9&h< XT XcJmС3㳂5DI! 8^3]4DvVÐ)%!/6l7*vAG[j"wbfROyX9c>Nc[CAP &7P#+WDn ! ZXj6.OއI"}{Ey+jLk9/g"mvIDszwl wD{#܎dʇxq5}  P k̮Yjy 퇨su&kܶEpFzEL@asb^#ĵ-S̭\k[m-YzYl&mLxBfHH@KX! ss3et|+e) $'x]_刀{F)Ugf)驉f['xKL73=EsGe碱Ixm\Rd&I,\{⥌l_Y`N g.pG5 TMx"Bu)8s hcvzǍ" B%3wB Ya>"£ ]#)_eQ$΅k8%11AI,pcf|Sq>TRU@y}$ߩJVVckUg;Jjuhǣu_O2L!/M'ʤ35BL%Y*[]o_H0^ ]p6,4m|rRPrBR%?/W/`W*qf_d/4 1[nEݻwHR.f&%{E+6{E^#qմ'҆ KgO衰]_/[HYf5ka:5ΚIǒBk{#ED'*Ymd%6\  hrh5y @G/=XpK8%dnsh)VV$Ť^7t#S#|?rP(^%D'e^Wg/E%[`)J| zhRdc|FBCy[M]N%IAIe{w(]!A//`uRWVB=SzV*!6(FIQG5! C&~Y C.;4=p$5b;n4.Ue$SB,,S8Zf޺b"Zd_` =żCI@ǰ87X^G5Mzt&Z^d-G&:IAq:TP&FԼf^OBeRyDy=.- g<9iRZ$[}b1 lu[y!k! 8JO@ MlruyyH)v;  ddDr%XWRZ<5^],/Γ^˸@s 86o߾!EVH"'+Pu[Gֽ:a<dʷQk[ך?Z #kYE5(qZЮqJf >˥*k^$5ͺnj0Ix/WŜؖVK\E$@$@@F*HH$`Odũ1/߭/G^F>ݻ)q.G'+?We{ o'Lvu&$kG;JVzD_v[-ff0f)>4W$SiYn6H!JRvOCjlUkY/ϙԢů('ZLrK߿BQ$b-+=WW* Pf~>QŸNcfcZv/_PX=.9'?7G$rhǼ,%59Q6_v[M>Q%MjZ67YϫMg`,9ܪJ؅f}^ T|ᕬ(Hju(c %.qjExVk ⌰VS"'q!IVH">hu}}޳^׼潢 Pc+ZmVJy oyjxpBڝI z$k6 iNW1Џm g#퇥uoӼ,k{n1'y0E$@$@MGcϚIFq|p\UQN VCczMT}k|hZ̰Y>m3/a= *c':F3Z}M&sNGZyS!}83 +8^_N?d=Z6$ZGJdZ( ٵFR6AllSLAHAWIRZ)O3[Y|CuZۘ:ay<\SU> UUq|)JvJr>,ҿsi[]lrqTYag-s Jsp$ů_|cܛ%))JJ_3 b]VA0H $,o\Cs6>޵]y9oqE4=x!(r h+E`FIHH PЄY> LE02wY Uu(BEUVZ{JiiC4&0b%(E%J7(:_ĴėTWijUc:~I[AݼԢy jx Kz `/˕11JJVJ>@I^E+PkBw%9`DyL ǰ\xtK&ܼR;ry\KI(Z?_\I䳯͂|_PeR%y+faW܈TYd'C"$(9Iq(3M=)"0.Y2Y\ONjd5}mS|TCcEEA5cUx]/R,A1.˾$Y+K@iM3Enm*6γPxmb~& J%H69tqAoSt$(ShUsZ^n$p$p7+4 B$ +^1{W.Nu8a32L%S?+SIa=b[Wn!fPo(uE51ǻ|5zاzD 4M`8n<ǨK5>s[1 aHHZ˛ LqGI/_}{pܾ6Ǵ*elzE*SebMD$"j(g)QhkZ%Ƙt>vC7FN6'+@k9@I7' S`eY7/9Oq`obM0Rؚ,2ĄnNSĪ!z_ G(J~qjl; WliZCQ5T>J7٪&#oӏqǰ*r t'OOɒ&c7>v\s1~]K3'cK=T/xW5b/c&DZk#M5]J0˪/:M 7Y6xfj[㴶{mWҳRߵ@_k}*#_?gťXU$nF%***$C) 3@ར}O3W% ims֝-=;n=[.#Of|Po'u^gBs:\IZg]^%/s?N1fHHH PY QNZ](MiH&&L1zi.?xTè#^bk*G8GIԇT%Į,Qcy=JRz|fWGojGS2rTżC0g2*5=h9pF۳25qyUYT{¦P\I-&%/[C_,"@<_+ɆSnTrӓb/A=>)(#@uITdbB@p?i8M!RiUre>F%P?_{a9Z-9H)Y@ÞJ%v=B$%3C bƹnV K}1I_F{]FI9H(?3E NE3[Zz`Æ AHVШH(#{Eyh`H‘YV4kOqD  !@Vpg$@$@Aزf vZ87&#z6_Kj!IR8$B۝17u-JP )[P2e`oƗEE(A i7S6 *`ɢ!΄u8Eh30τIHS2:9GUGю@w ]W/F7̑.T"/[ @m$`#@ HH^Q{E $@$~x͒y^{s' 4 <% hNʰ~!ȒOb*hň,OL,!HU1I$@MIl̈dɶ$iG.ڸHx{ҋԐ%"d"τ2kI L8?#֯_4*f$ %{ŰF$@$ kIHHZ j*P\?ٷ]3ThsbG}ȱ$jP۳~Z>Kz61 D+FF?J  ,   '@V3e$@$@$@$@$@$@$@.&L-[`ݺu.[WQ΅kIHHHHHHHH"ZWHHHHHH"u][ hIHHHHHHHH”Za14HHHHHHc۶monZAbF         p%G$@$pD}Y]v823gg:YFVWFdžF ce^}8mz  P 2o888888888888ݻwa[d}ё  GBc]ӝi.@s}(:zܗH~ax3111111111111Pc MK)$cccC#P<{'9c5 &c9vi&HHh#.f~tfHH ?%UXKUu/yoՙH‹@Qq D @{_>̭y$@$@$@$@ M`J #ߠ@ cȽH G?$@$Аn{8{h5$fMaC B$@$y1+ 4Sh5ӎeH"NV-&?hKD$@͕tAdxIMQ1#  ®Kh @^ ,$@$@$@$ P;#;ZoH PULY" 47S@koA,}ַl @ˍ ב ;yp-G$@$@$@ OgHH (;ZA!c& fJfڱl #)׃xh#VEaKF$@$As~hu\kP]]#2qmX)kmկ8$= kB$ 4/h5dkH) T@}q˯ص1ԢuqvTGDtx]n{:[5^;[cm`QP(Y  ¦+hHڽ_}n܂}ZukaT8WO vݳ4Z|/ZG0`I^,ŎyWWZZ]s-O>> gF}[qZj-~8hBbaHH~PU?Y 1#ߌM!ì:s?_8+ Szo݌N.To o=|a3yԖ*=J.Z}wѦ ۿҟ6LǶvypR6q-5ؔ {kއQ\ME"zIW,z<ή:w:Fvn.}kkc4Ӯiچ=t/`߹ jQ[vU=F\OOY} j~Ć\W-ѣgDuo}{~pPHcZw9u2o/[U <=hSCLph6"-i?YG1HN 4$@ K@@aYthbp:&<. ~ÚC1G;?èS{J20-5Ik6nmꨩ7ߒQ孬%Nl =A_[VDQ(ͻEezt^/`N|JTَq# @s&@Vs]H hET`ѿ(  &^t=MB[@khlZVU2|=6ݽ}&b>|/q/D\zc٫vm񓺭\x(Њ^$>m.MPL:; +o[y,h 6@kxtl?<<'[kRҽ~V5{ kUu@<Ǿ:183:xKعM̻_on OhgЪ#cBv+tyxp{.E$P^ /K$N`xg ޽y7Mǩ߰J%:SnlZVΏ{7oeK&/ss_>WeXu5ӵS;oWހg>ݤ8e^5H”Za14H# ?S9_-j}Eρm5E(YV9k Ǜ{U2ޚ󐖳GU^s5B̦Afޅ B^= .iU NN &B&@Vȸ N`³շ>u!rt7/ĥ]L{8X}hEH P=H1O: >.=0 vmZ쥳*g${=H$8^ 9k!BdlOs_<  +!$iGbq>+bo_Q/**l{C-l4+HœZaA4H! ?S!^U.2|.bnC|CUCAN&c5.1Po~Ʈ͛D&WO\R[c38sݿ E&}=f#:cв5eeYqKR|u)DIⅺ'YE+ɖ]aXZA@b _.,couش_x|5xN&2<7ʘ/Be%>Š' N:G>}j,&b/耖w"}Wμ/K6~ F?#{99]Ea~1M*t9$D¯EBp~ {pa?`R?u3@ֈעN>Q{*oO6Ƿ BUk%\ 6m?b}q~z#jxU%aȺo{>\ڶf.{'}K{,|2\qk>hN"8\3/*v{3]jjJQQ#\}Õ$h5esH8=Ip/p@ݒ,S\SˁZ~-wڷ>s;+V;*ډ?Riy>wT %mǢC=wSHgk1ow'#Lwp_z qۭϦ1Cv=3ܑS8f6-$@@+4^M$@ F@~`@0[ 2SP|'smTUzR!`= ,J=c.nz<ݲUVYm}.CTl,O`%EnG;! WH5mѻۋ*lM5Y_Eq8֛Eڝof˗zJuUoM@I$@+z@xزrgKxI*؟?ncV鴋1w`? RB{p}iV)Zn?"Li[mv5 J`Lzu\+ln+4QXgGZZU%BUplx%Cl4c2u?zdٰI3e2扼bu+:@P7!{bWN9]EZ.pH hCyHHh5#b/6z)rgC6]wE͞&c7RMVҺ:|t 뢴ᩈuHxcx1ismVu07|Vq9'ƺ=b=J-HpDLCt-[@Q] t<}q^֨ڽKdxT^_⺗tQC}ZBPGi7yΩ-C5X^ ]U{=@5p.{pkEE E~m`_w_>ű-;.@$@VW*~ H)## t"bW)'> Ni߸;KzIFɺaZ` %I[J:FO7%fތ7įUp",EVܥʛ⊮~M}Յ!Zn fЂc-ahYm. Y~xY Jӄ JҧqAOѢűX0hTHaG*֧gYKj^9 ng.ˉ]F^nmo>Cfj =;Wwv@^Z r˗ba@+)nj =zSIu]]lqŽ촗{guj{o6Zё@=x[aq9EC$+&|vi9 PK05V8S>6{@Q!Gm]#=^~xY lI=F(~fʲ) L|HNVa?̊BWh ]xrQ>U V,V݄WQuxV`U[j- ,LY(вX0E$ #^ XebRx( @Vx !X)v=,û? ]mj*Eӥf<8mƾ=0/ѻB@KWWcʽ%U7*X#|iaw>y?z՞D!jXLZq= 4'h5d[!h% n !j@+F-aAM MjIk( ,Jf-o?Wbd˱ʻ- SGyF8R^n#:ID[n "z SEr8h9B* o_ux@˭"sZo@hb{8am]K3Ism]9ƽlјu !L,3<#]"x!lz+܀/ ryx:;p kpƼ 72ġLi,hEV#'Pq8 qWU|#g*3 l$*vc뱡`3~ھ [6LKy@hd?n?k.}"p2m:N Μw*$ @ W @)j>VxgތAP}rwA+>Xms:wUo\c2E{Z`Ir}3)u hB$Z@kjQX6t.Y5E]0x[?#aZK‰]f >3>p,bfg֮ qZhbΘqf -u݌5hB FVh"Zq4 TIƺ'FTLK jC0I@.փ D蘙FKxŚvnc\و[!@ ͙" q쾈Z@+ƒ@xpP`2Zոr}Ug= R-nݿ/[:oC+o]+N>n%B3UeI0 ˦wە" pN[J'.^繸8ƖiGnFڟ =1,Rng޼EAÌ$@AZѸ,3b_6+O/@ڤ(s9PjBjeZEr9Wf^l+ 5+!O|o D6yKZOF!6B%ˮZ| x~x/G.R-ݟE|=d:Z6k*^p^?~s{ao%+a/2$G3DY) س%ϭ ]N:iǕFRyHjL(" 8 Puv:L$v ³V:,_Uj KzqEJva5* ㊔ twNڳ osi7c@_hs]jjbB 0*衒exD$7Q"`o!s},Nkv?gNgH @V$m$#۟EMM? 5gp.t]${ G "4wSTaXօE8US6ј>if««R}݅S.¢ؓt͎7kCHɰ| xg}Y& ^;weOzspRVX8^_ h#$( > @!I+4 14Bd;{֚/ի.EJva5Jl, OPi"4y1E8UcܖrYwai/u«UjWY ٥cyS$5N"v~YGG8Dl!&WtkaH@+n# F$ ?SՈ*- 3}&:|i;'bbCfv C0?2ٟjD1n\޿a пQC]W  5)ޕX6FS n6RJ?-h)Њ>d HI8hFsNvc|CfvC0)u}xljV FT U _/`߯/m%] kЄ qO Fߜkpe^m$b^^iFE 2!=X+Yޘ:Ȗ $М h5fH 8h}i$rI<'[h6܃[qұV>=>p⇕@/`}Z'k'.fS=2{:+>M7[^nE?s Tw\|qLxgu. /QK|%"\\b]VY#1wXܻnV)\?#wƼy?X+Q,!& ĺ$c}:N<3y gc ^V^rK@̬k;ƫӃ+ $fFf֡lNv#m|Wx5.<:[Ң,ld[9l)IUeڞ+mD;;ފu[V&3]o015oLB t1Nk̗WYE i T$q^ "h0 4).>cÀ!w`h~8]1_([~[\_= DпkWK1V0fA'yb+ke1Q[y_Bo1K$<͕6κ qh_<hE^$K?飚_' ŜFJ’imqD_)1*GpQh J;UbށB^!:$ic[i{.7|Eiμ..oGbq--RqU s*l[xV={h4jwY3",",Ph 1ebTMoaVM g.~%f?1S$BEIoᭇuF#)@$HN`V,-ĝ h)CppZ[lJYtRs`RoÁuN",pk/ ϼ ދ0cpJܬ {~25kf连J,ye={S/pxs_*RرQSSk ύj8Sܵ:$ `&h6(j6]Ɇ߼S*򢑸a7uWY-@SaɣR2k42ݪGnGvXw9wjE'"g3{BH hEv_zh25 70dL6'rv_? /N_Z_⎗vaDX@- } ѭW`;v5Ĝ3|gfE0w)^[y܄tĜ_ϡX<_ҽ3cn"Ce`V{e\K$@(e5$@$$v W{wF-?e:?ڴ_@Gl6Gyph(ݶ e{ .# ҟqFSs<t@ g[Zaq'~7O6? PFI \ Ɩmش]\Ccѥ{;շNn{L[. /Q5VbCavVxOY8=\jQu/ Vaohg i[}QZjDTGG!yp$Pjʱsw!~޳ U{:Cn}оZh z1f^0U;Q'V{ksB/)-م_'u> AS'{*B.8A?6 @rw /;ZE(Mutl(j=vAMutq[IVfm$(j`$ h ˑ$p{):z[NJMm$@$.( $@G=~8p1s@+:& %@_4au]M̛_(}H <P@+H$@ D@@+)n#Ghc& H'@V 'h6v M6JC(j̬ PՈY @@ޑ8fhuR +I@u((Њ>d 9h5qBMKIZ>Hh: p&@V8m#ThEjn fG@~`@uo6,Soo?{wEyq "*DoE ^AJVQQĪ[ FZ*hA h0&g7ِg_/̳3ϼsW_}kJ+Z-Z>Wɑw~eWjċx@jՖ+EVjŋ"'$h9?9P+VR+^mZί-r~5$^VNL"^1r'E֏H9r-rzU ^Ԋ-nӗ*yۥ_Yj(Z+"^%ZeJxZ b ; Z4 ?Ώ˪$h9?Fr۪1֐xY5"|@O=Wdx9=B#A+H qz"G"=x9=BiO\vbT׉xŷqĨįo1ˉQ_'0ءvv(}fFOKī[4cZn_wѷhīEl%Tu=:+~8x91*DV|'!ĉQ_'ƉcN^;9e  { ^Rx9%A99rJ$Ճ-{NB\zNVso Hn:C{f$hm7w(vόxm7}uA{_1ؾ8o Z΋I@Hy㈗bF+Y{Ir^|khg&^ΎOtW?/g'v+ZٟIrv|t  ; Z< ?Ώ˪$h9?Fr۪1֐xY5"ߏ|;xO=r >g[O?1īh&hՎ-5jZ;~KV;欹m!ňCa&C/f5M(#A! -$8?F/ߓYkn/ZCeՈ|~|dOe)'l_vQ-gn-H +rq[ eW$h9#&jD:GD5"^t7x9/&jD8o Z΋ 5B*`=aOujS5T :"޳kڄzy:.O5/sWbEoFX =:4e߻.Akzkzf}Y+ާN8Ǻ`D/uO(+ 7F4䈾:wtW^|xE_m{fQ5V٠wVk@'?G%7ifjJ[{tm~[ޚ?,ڬokJyci;^Z-hmB?уtJ?3Ak<zy1A빿F\W_6J~$seIjMOW^a~hmMW}P$sv>^mUouknS86n町çs95 e گ Z}r-xP_V$ ڧgL/whJ+@*UZYm[2xN&'=S*^lv*[5kt‘h`ݐ̽#+vfRu9@ O.^?|%oSuF?O[:= Z B*`=aO-߮=:+FUCP|q&'b6!z r~y1>u?%^?Tn"f z]4Oga"ủ8dkOcz>1*_dFN$}O=o_.[5fL*|nWƍ:+FSNvho=|˰%֞m=D֪~~)zblZnW:lG #c4ypDeNj[?C<#_U/OL$-7%#lx79hdǞUj\ۆ13gRz.[n:肙1e:g;)U[j.J;;oӠ-ۉzjeN=~$-bLu%y2z̡j/kbÂ._J߯iV봿/]kl[?l'Ы_ƈizܱQ1ʵӠTWUYvn.|6!ʚIZ-^Ϝr3tzqݮhV ŏgg'Ց2> '`=aOR-x6P}S7STR2/&7^{얄+7~  {PridpwK?zP/jh[!}öp vv>-ܔ§6K'4xϝuǵ֛&|?t&Z.pMxĤ~~uek,,5O!yLƸ"^Q//׹jNWnN!G?QSW뚻Gj.jvRxumWNe*ɂt_jtyGKIW 䬺bz} S]T>h_ǢҀ Ჩm ZQ㦘d!3tomZ=O7 #oVJMw*(nI ٸ]ZYp(/bIzx;ÒG[J*}rIk_ os̶uG o[^kWl_[kl&&i+~AFVzkt&R0WY{&TN/,* l֣tIhQ[Qkx&ݬ#VϏO2 ]E3~[vf?Nu"W6~6$hMHЪ1' =ϿKܠ?L M1iRWV޶>~Jܠ{κXwן/Oޠfm0lN|9=^֏wE&&hܸMo>hgwѣH?92  ZWLj4[%{zo_ӛU{L>ŦWHgXx).p׮1=\kz Rc.\. Hfu\k5 ZVǼ_{: '_OzwքkCDŪ/ti˅:Kza Vp=>D.vݕU'L|,2*U~>Ug/vxsEY׬4kVZRӋRtVU*o"\k&TgwѧL]gxZu:I }wqz]<Gp}xn_G7+o]:RBC,;Vz^<-$AW׋5yܲb>Z2} GCB\W&HVQ2!@`'쩚efKVMct֬g@f-jݓN:`S-筒+NPY J}MIЊ^az׌~g_qP]~cy3}03_^-:FrOI?3眭}h˜Ez-筦۾մ1 854 4MG|d)7[Oo|#]w}n>z5.nQǏpC-Xݷ^;NœFNnKКc./TIͳs{|z.#_5/Ouߺ!$yIIV4KJ|޲z2\ e5lpnY~nm_)wZs~̾}cJi+imuC?q,>T ߅3/r{dT*%$ԛ|ISb{0ozp؇T.~m+6SM|_U,3HJ='}5TKE+h׏N~udR*A+Gۊ>I?P9OIj4&2 Z4x󕞿#N.lyܲ+8s0 Zϼ@ Z?|5O0^Ֆe~{,CR[ܨWzF^NqM$hrʥrk|++U_7&6 ct@UֿtP:O :Jpsֺn33/=NόNr_$GCjQ$$XH7Vh̃5z4s7b7-jvaT $uպ8cIx4=l(%CԢÒ~ I&Au Zzqr6-#]>zt9+/+]"ӫ`O=SvMLВAك Z]WIrlhVl5d-29z)h +xIВAkedZ1+;٤=X=՞kw%h%|nn-03!#u'ʩMu"ݕw6qA-CkÇ9BpKxIn ey=>F1?A^q&q~sI[x4^>U}wVrg j\z`8hجʷg9ϘA'iS_\vCO՟o$q?yQG4nʯ{B\uCF5~-.}kDG#KUWLݘu8k3.D=v8]].y5n^ケZ֛O$$P_ vetpeuk[M#wR;/jݪ]hbzYŚ|2t3|~(o^3I@G&Oؾ2D:(53AM$hBulܴRݐ[XM/%hXI]Nz J|aP 'AAkG{ u -s3E]6ܶNjVFxnHz$xB5ԛ̹߻ш$!^6&s]}-At!<@͙w()3 Pܦ Kv8MHx$$Hw=6FqsC=S;樭ћ+jjÔd7:7+y_h:a:i޺FzJd?-Ӫ §ktU#]W;ҭn tfGN*e4 ZDž~hŷҽRN7+M\.|jRjIf9SQZ45:Z Gjy̫MЁ_rWqm )-G%whl5P&/AYEz*!$zIIЊqj[IfP"]7l_랟djS5(ЎaNHk{o%}.OS ]<}Ikg^4PnF~.>d?1 $GM3q=Ῐ$VZu\}o,ט?ϯ9EO{(?nKRJM&`23nJ Tɳ3gY M7$x#a}ep-#Bgğ eQ;+F~SwӓE o\f\}5Lߪt}P33WV/k{ԸFs'_#:D+r+tm{O~A/w\6)Z&9$̰c@ZҲ>yI8z&E3 Gek#(ы;4˷3[jզwWƅ^LIx%lm_?ly~=plt;Xf{n75$h9cپU_ .W W\00K"QM.SGsUI iĿje$DqhU'c_jרp_Hz ؞ba>HF.ko5ez!A+ZVsX_ $=jG}&|ru>q=8l {j|jy> ^znׯMuoG>^-MxLع.Aˠ7n\3O@>/Y?{Fx1H 0B Z%zyⰨ-j81@IDATO?$&Jj2<%^^'O-,_Ҕr&RT JZ{S/A,ό^~$CKr˂[2PKo0vYoS/h6qI}|J ?6f|(!%"&/?Z0Aeֶ-O2v}3N~ [*+`s_׋ ,_gNyf;$'*ܢ97~jBesīS1}:FVqH$t+OVer @K')[m=>s=}7@KbZ)+}uծwwEtU- Zer{7]Ʃqktkig{g9ÖQnW%RnWxE{izE8$h}5=plk϶m۶-2Vݺ5՞;`nL WWkKc:u^~&X]@x1]3xV(-b;uR^:ٵޛ: ZFu.սk3g͵mKR-Wm]3]TW7<²஋W`Riٷk[w[מ1$k5^@* o_]oR:4 Z3@`'nH~gOm ?7ܑS AkGjo;r kGjoW|ã/N;a_;JI$Hyܜ<H_S ^ΎOtW?[{ݖlu[Ɛ2"^)*_EJ-Ԫ-WjŋԊE XOIr~Iq~5$^V 'A1֐ V '^Ώ˪ M։$hE)dڿ, Zdj@I2Z_x JFZ{Ijx4W.p7'Yh6ī9!g'^ΊGs!^ 9k< ZΊA,`=a'A+ ?Ώ˪$h9?Fr۪1֐xY5"u<>9"l'X4F@xBV;`$B&^-@kǯX{Ij@؜5mB9rH lVxلrH1@ج b$h9$TZCep{#k mp{YkH_0OgOӮ#A+ROvؾJ9 ZΈZ@bW列3`ˮ3Y{IrFLՂ ܉t7x9/&jD8or^LՈx%q8jTzNW~#k UIr~5Ucd!jD'A+҃O }%o֞ A=7 $ɛ7W{'?o╼Y{~KV{F޼m)S"a˞SJ/D^=='"A) '$h9u 1֐xY5-ZC.p[5x9?F/FaY#)7)'l_vQ-gn-H +rq[ eW$h9#&jD:GD5"^t7x9/&jD8o\k%h @`!? XXXXXXXXXXXXX=z[kZ.AşeLkΗZ`&r>S<^;|Ws;aߖ8?\ցXk&d(D t%hQD,7?XXXXXXXXXXXX[uF\c=b(j(1'xE?FĊ1rV񢽗4ѿC:::::::]7mϋ  4hցu^7D+`UPuù=,#>ޭmcz1ZZ?쮽 {'_MZ?iW=x jt3@@׮]ukƌߒ؇ @ViceCnY\ZpF 7b[X`@`G 4nҒߕ1ǜ 9Iׯ֪uUԯWlӺZLǴ =zޞl5  W-Ɩ%CHIF-uNDg;4GQC7.ѵSQNQ8gpW8N`iI*y!Gиz4 &˗WfO{i} @$h%o7@OS+mR KceeOӝEѓISW4yԀ|F@$HJ  ffCa =08(_U/OT¾)4 :t 1PK4qX©ha3CeazUMJ7t}Ur68fah{<p@.]t7j鶗lSQp@mȸ2ffe楚:aZp4+cvF^^<,8 suQ6;$wi\7=S3}V>\\rFM;|JVzti*($B C@ HJQ{@7 X&VլXZfz:׫94' zYMJa7-ӵ}=_+r˂XwO4UܩDs&jœcTK^R{̜g/S`h3CKk^ֱ6z'?# ]]wU7|^_ Z(XQL"_"UWըEuLAaEE E&ǿX5>" `[~ oԂ9OnݺC~Yfy| wz󵷴ο0:3uƈ*]>@';(_XV2I7VkTk8iF=MZa,k\|'ùK4XZy.6J'IXgoBBzLnDMİwDG"J~&+ey µu EsEtVJ>'=m}9xFTd.ǾXw @/+*e96yIx$VK2H>ѶQr]nf"ӶrNDۊ.L;K__;K:-en37g5y Ixx!@M'KsiK<'(nFn.TƓWR|STSW))=;3?7#|ꃓ\ ;zҲ<%bOe/@]=K)M`(OVaYh4'̌t".zysb/{Zziy7_X).oZZOv(|?S\\$xsvi^e&|gx̉`pŞPY8_^Oa|S>;T1;,j.SFS4;2mprUyQx:iپf*):+N.N]MgmIadںv&$TM}mC{= UҒ 2_mja=鞜’ӷN ;S;Hj$h%EaRL߇Ú`9nァ+8>˴!4L2]ʢO%@c p_e;x7jO~9=Sln^ӧgzJ*IԲƉ T=@@qznd/Ts CiޛX]-<鹞JO~f GT$7LO-1fζedM*-`rY8q"x׎IRj //V\˄ĢCc(0+OF^הl+,N3J&/mz &(x|zNqąsl-6nⲈ$#EAkS*^0x Mύ0zP^jk(h> 4-R #O(\*zFt:Km2>1#F @ A,>8=9Yt_rPTBOq!@M*ßJЊ)4ߨuLZ&Ev.wp:ˣIKL &jH >0bRqbו'$'ZMUeL.UX8u lTVVE46y"za6`,àPMYdYiRo^4sgoUy=Bj5 !@ߏ=h%EaRR߇ͅ߇v ^UKqox3ߩ) %+3Ǔ3_\tOV\rSe @ǯhE&hou {dƘU#;A/cy)(8 :F Q$hEh{SQYZ\ !OfA7 ӯV+~TܦN'3/nZO0z &h}Mocfw1^eRʒLO;8+zbKHPHuB1zzъv9S"Цc?ԙp"p'VYVq0=guD}'7 'cb?ۮJVR\F };>Xz?-X$ػGv IЊy\7aiKY@b#q(A+]gzlrt$&7!:\O\Z|ax  ՜@{ʣ降#|2$OVI~zU ɓ_jgI.&OY^uĥp PKXHRD!lMOL”?A(ZmP͝OZ4iYZVwUhm(/og69'YBG >RAUŔ,βу%1PR^McelY &zѻl>{J&IBضm?'''H-~ڌ= k=فst_Voͣz ɛXUOZwn)_:;$DvY?j'/ƒ*UXLzm2əke3"Oe|ul,DT*3׋kR`8 %tźs6-ya~WReG2OݳfZɇ** 'U3!x$LyLU9XKdBwf{YWY]L]KŗM]kCP@Y'`hO6#j><^;BthVyd9NzLyXQ hll֙6mZ2_󐠕H1~ Ns d~yS:a SK F,^9~rJejq=Z=<_8t!x㱷.o'J C@&$h5!a e_NZZo.@2NEq7.l,U?43PO`~G7%J\):f{y-'mG76eÏNNHh(>,g`BDiM6N7c61 %>y%tGH'oip6ggwP7&AP X=ūhMv4Zm4ܥC ڥwz;A @yDw{ ZIqQЧTimn0_O߇ěX; C-N9~Rjnqn8{N_>:=6ZzD_c-@@ Z(l@'tobԿlȳOn{}kyn<½+gy1\|%ae䇒KVUVɊ:N/3 tM|giANTw2L_^¥gXO+7w-\Fez xߓ=K e(A+rN*J# wz瑑) 5ŞK/e:ySHzG]l2]c+bġb~3# *E>S4xq`ؼ3`cepou0bIandX+*m>@Vغuޛ4IJFu=Y7y+fdx*,73y}Zog$" pJVZcKnoZ]:am;QsF<]fG@: Z~h+-3./7M|r'(ոN;$Ś;yD(~+: -4sjcL}Fկn]`f 5(2?}*\@ JaH|d&LV AU9wD}#t,_oA $hz? `uK2=%$#3&hĀ8&vmԂۆe*{Pu|\`Z\@#9WQ!jic:voZÔi B5r#=v3f[o]EzвMEAH!~}}:ǯԎ9ԎGH}R?, v Tkɺ`BeY?{FKEvL֭>r-HвMEA@ }L@8~913@@HJ/ UqK**W|Zi~jȠC#@kivSnnNj{$h٦  f>l3Z& ІI# @$h%M@@@@ YNݻw߯oIвMEA@@@@p Z B@@@M}覛nh$h٦     8T-j!   &oV=zЃ>oe     P   l٢={nۋFm* "    CHrh`   n AMdY@@@@@ Ѣ,   HF{챇z!]AZ(     AˡZ   Ioў{~XYYY-TD@@@@ P-@@@$_W^9sLb{HвMEA@@@@p Z B@@@Mݻ4ydۋFm* "    CHrh`   n^{GyD]wE#A6@@@@@$h940T @@@7 |Wӧ?7E#A6@@@@@$h940T @@@7 l޼Y{キ}Q]{-TD@@@@ P-@@@$@ɲ     @2$h%EY@@@hMԷo_=cklOlSQ@@@@*@CC@@@p@eeg=m{HвMEA@@@@p Z B@@@M_~w_&Md{HвMEA@@@@p Z B@@@M_|o?kĉ-TD@@@@ P-@@@$矫_~5k&L`{HвMEA@@@@p Z B@@@M7n'xBW_}E#A6@@@@@$h940T @@@7 TTTG?|I]uU-TD@@@@ P-@@@$@ɲ     @2$h%EY@@@hg}멧ҕW^i{e     P   6lؠ+e     P   iӟ/e     P   ֯_jٺl/ Z(     AˡZ   I\p̙K/e     P   >Sxzu%^4lSQ@@@@*@CC@@@p'|Agm/ Z(     AˡZ   I-7EeA@@@@dHJF    "u頃ҳ>LӠ-TD@@@@ P-@@@$vZ|z4n8ۋFm* "    CHrh`   nXf9/h$h٦     8T-j!   &?X?OWE#A6@@@@@$h940T @@@7 ?Y/m/ Z(     AˡZ   I`2d袋.h$h٦     8T-j!   &2zzt^4lSQ@@@@*@CC@@@p Zn&˂    e@@@@E}~ӟ_\`{e     P   Saa?|ۋFm* "    CHrh`   nXjsE#A6@@@@@$h940T @@@7 \Riii_;e     P   JKKu륗^ҹk{HвMEA@@@@p Z B@@@M+VGo;vE#A6@@@@@$h940T @@@7 |:#/+##e     P   HrS4Y@@@@HFd(   6l+N;oժU=z̙}y^y}h>#    @ j Ёl_W딋^5 <6ɥ_=Wg @ ,:JKձȎ*^_Zmծ;pO][8Mݏ;%H&\ZյC5lQ1F! GuJJJ~'o2., Z1Y    )$@V "@j 7Kc87g2 }lmAv7;/R 3TRHڸ`?5t}Ur( ζX G;.-P;  jJ쳏mۖp94x)4΋80 F@@@@ A+eBEE@ V!?gJġ^[4sj7ޔWjK0A+$h- AMRw[_M3L–zjԼkLSQL5bFzZ4OʙoaTk#9z"yIЪ_PlU>{SoԲYI&gI@@=\1vygylٲ A+!#@@@@@ HJ QEHmuƭKw G;X}C-֚=uaVNU WtרwVpdhoԒE|TwHU{]z#i. ݂kha^EH~Ҹ6^jZcћV {% X@."{kd!d .4793g%3L25}̜==L~oЁ/''E}Hi fRZ~]Я/h̘T0O5)YO4zudN1>Ok-7DZDsuzUft Lˤƭ_C7F'3?jf9/|m|I-kф 7}Jw~{ퟪ+L=nW倶=v83A7H6%[ef{z'$M'U\2_:w~o~[z^КVT3*|S Cf.6?@}"K~'p=uV.(y[-Z5`Pjei=s{ N}UZff*4>f'?pILкG̰:+Wݦ*uzߣܕ"ف hhh… S3hʕ);hN@@@@ @ MUy#Vuj_gEYG:}T駨(IVMHy +X+V?ʃא8Šs[ǩz5ƶ/+ ;=:=zJU5j+ls[eeɏYY3{HTh; %m^u:uuyXMIƷ2TŠйzvs=2  ĉ^5f뷿m3ge     Uf2R[Vu@/4ׇ;]]=J}wG,fSG\pꗑ:yj9 x'NnxViezbtfHt\֫-681Ue_RMy]ujs25uqGSSVA&A뢞{V}EMWTHppwv[Tq;@@ C5k;'# TdWfJ a    xM2ƍxMTxf,F%YʪX4Wf&g hY=sk*#XYk۞LNt3[WLd椽Xˬf3RsMtF8lk fZ13T9mv1!!k鐗D 5@IDAT}:gNzJC3m39v?1VԸ&>wUw*vr3񲺛pw7׆mWm}U7 YO{'ʽH,SG U5>EUٵ>{@@ hO~ Zi1@@@@X@@ (E@I]!ڸ`RtЉ]*R7sx$mUdw"ɎZTi5ihk*#KZ{^,gb  X> j}#iVZL4B@@@@< ah /Z5PU%L%6U?~3!$_OV%Q:3 *UXe%:udUSUaEDX^cM* tVUw:q׶πVU2p+ܟZۭp,Z$؄ C&0o޼g21    <, CB DT5 %NškR"n+DxzP?eVB,Z Qg$LŠ$(*ٚBVqH0DCQ 3wE<{Xh@(5f+˄:"3Ňź:;toU]:u>0S#ql*Eqyg@˲N׭ VZ4~eTYe59;@@ /Rg~z&@@@@@ haQS [mf){FARgwrA?&SR`^9wwU_}VCD+kz;X+A/;PVay>g]d|PQ[ҩh ݂TkngN﯉,h@y-g ?xg,>B_ӍVyx)REVU]el5V:6׻YȰS-馹Wu/1pa"{w hYD˪:mU8GZ ټ@Di#ՕheEc@@@@CZyXhvCHv'8PtފD>I9ᡨb1I,kO+\e_WQZgư$m cBI-z-vGf|igfI94;$)ؙTP ê+sBLv0nUTLh}uw* ?_X OKjM,z,{h%> hf͵1׷&~& C/,ʘ@@@@@ hYAW3SQ4T @P8$S䎯& 8"Aߊ HdfJ8(OEV?t[G*c8NU9.@m5V$-,K&+M|\ )ٱf6QYeմ:ty\\1rg⌛2~p=VuOՅWnv&2 }eU5w/V=fc-c7>hDNP":{sd}~U$Uf5?ᝎ3*6)25pA@ `2    |~d @A t5݌뮝Qz \{kڎw86Uˬm\Ns> zu7h|tO-9GFkر[̡&5O$zrZ{d>Q|RW߱U6Qk&/w^X"i{;MXAXr@{ښ2ŏyob_ϿCy y-pw8     JV*# k;I:v~UkV/KV^g#!zgYimV֫?Udrei=s4{ɿf[_ָUt&Ml>+.! @he n@@@@@`h ='FH)٪I$G=o%UJJWuۤ/9RvXTը6ഷgfѺ.8Vݡj霿0GPӷ=j БOkIgt! #P,:    h\ \|e)+g/iU]nnf[*hz?c/x,xTG@|+@@˷@@@@1FLP@ t6pAO4cY׎P;uߤQc5qp;=zF@ ՗@@@@@ P%ƈ    5B e#    #Z>*&   h\    #Oȫ9W   ghyT @@@@RJf@@@~Z_F     58?F@@@ !.]#    fN    55A@@@@| O`,    #@@+     AZ,CFo|̯1@@@@@@(0~x}ԃ>88 @ HUW]E8kv      vH#@H&>#C@@@@@@`e hEh<@)h={vq8  /G?ro>   P`ɑ hE( Xր8 ♀  @hΖ@@@H%@@+ @ G!Z   uZY'C@@@ / @heEc@ QV [@@ȖlI    Jߊ @:Q  Ї>p؅  h @@@q Ї>p؅#@@+%   00Zs(@@@#@@k0z$ J4a  h  @F21   Y F:A""A&@@k`n  @:Ju4jԨtؒ +njI/6zz\gƜ@@q$ LV2!  d(@@Rzh_x ͋6zeŽ<ХS {XC5ɟJ'1ZӴ?2PtU|Bv/RԌukN @@,@@@`8h :D_ U9@@< eL@k}ZL@kZ@Zr= 'v뱿_$/Z];_}ţGq=z,P7Stcɯ   @:Q  h$   dEإZ`IsvCǻg /xm[45>.Պ#L=E͙DH˅]@@ȁ%hZ#\<dCV6@@~Qtvn {fnԱ4!Ycs>oC9ȝ{+~`їNo{Hۃ,Ċ7Ҷb%3;Zah3h?ZM~1ԸٮqHx  dUVV9 - h @@CV8]-u嚷>323=43=*}^bx*^:ճw\V%}8S;4Õl=gt5s\~vDw.q,qCڸע1@@XVd)@@Ov" ߈   TVr1!&VεɸFDt]qָPZ[oBИ}B44[υV5hSeرi?Mv%Ц; COҕN8)\P<><'a-~?{Xj>Z5pNه5s55:̍;c٭mx  dQV1 0x  5H@G@@Z}wejyCGoፓq= /(F;{N}$)XToVA|*ضGGTމB%vPhߖiJ$@:M_Yv6oӭIjjfwT29vުP5ߤ5Njj<_vl}Z\hf eqK(F6n9;   h @H_VVD@@ SZeЪ3-w)&#кpFK75HpASb2N|_Y/*X{*;T]'iZ$e;ZkƵ"˿uL%-QO6 SΙp֌p3sgrwvn |ѓzgkIܲNK{uAc L`VFZĹn}#3>)RR+RL3֎mE=@@@`*q @rZ]؊-@@+m*"  bPn LJEK6Kg5II%׊ Á=ݸK23d})2CV̬N=-*ief:ffƷ\R}yr%VjR-'upfkvpִ45unT7ZZP8Ό^O?_m_<}oSYcK[\|VtqwcT Mrfd`c J;Q$\uNS6V> PzAݨ(^6@@@`0DZ @D  ʈ  d$@@7䶥fƪ7{y*sI]K7ҶRlsZ,r@[K$슱H+mլeF,O~_Af|IZ6%_+ }ĞR!_ݤ`VO/J@@@"M؂d$@@+#.#  bBIf9f9TQmmfYYβ~c((Ov^m|%?:SVljӍ<rl'}>=>t"™*@ݥ9ǮePktkޓZZpNԮ03m{[<_c\w{ZtӼM ZJyhֽ3ٯk]R-"  Y 5J:Bx"  5H@G@@Z}we?գ7p>͒3k&h+Z_Z13Tm43h-K=VWWWJ+cZ6i֥nM} `цZVh3V:ڔK=ƄLч=KkUp}7r[y=9e@@@`h 2@%@@kDE\ʅ*}"   3!VU4Wc?{Umv͊`QDq4]c&܊ݬ`ִHk6;k ;̄ukp&ZfqA5OֽtTkg]owQ-^p>*xܢuٟT~Mk6GfβwVjw8>zNYDfK {@@@heEk@?Z GV?@F@@`Nj BeOX*gEZ4uȒMfin3}9bg 2I..:KU*j '`եSZ_Ѕ&\u+\HNn.Dqь{6mM|8sKzqV CZ0ޘX♓oq/'[@@@ G#Exd(@@+C0#  Z*4wMx6-:4e@A7]괟Zvλ_U \n\̊u+2}FL@kZp*,> Oi'Ǻ?ǾfLv' Zf3d^\sѓd;Z>:M{";>gb^<@-@@kЄt#]K//C$l&5o[鐖&)r-\fnܥ˦%iMW_0[O$Ǣ-jZז}U~ݕ}sI UgXwMTB]P:sw,ʹ>gwkˊZPS{W#Tz]mC{G=s=Gh}ngP3?f~nE7cϱk{_u¼Z`s%f/j)Ė^z޸^S= v5k4}s܅ꫮv~ ^8n1Bb хCzlV y3z{?Û}_hPG^њ*lR#7Tp܉}epymk˾F_ԫVL]`jRWKLӾ'L8Ap tI\EEd^PmGY@VM=t_~sQc|׵!Z}#\K&XWh Ռs]@O_72FOD?m&\<ԖҢ[ͬŗGs[ËJ%, !+w ?ٍ}Ln6;T՞ D^Rzuu^WP̦}G..Кz÷<\/?~A@+C@`  Z=]q7{ɄEfEX??,zRl-]CO/Lb{Et<}gOXz^d.{^!_}-}zz3+}gy^̃V@ +H' 0 }вLU5fr9bd5;̠L'u_l|XC+:+%+|mc`O6di2O<*WUS暙F*ꏚe4%o煭~ץfSr?`fyxfɵgO f<LCߵe/%:,%r,Ywo<h%V@`*q @X-%.@@+o|蒷g`^{7\^\;G^_yorͩeZ=K 9zWhC۵h^m-5pse h5oNWq-V4jӮoso<h g?G"h%Sa ?Z̠S`؛˰rdТgͯ.Xǖ|8˾?˾@}yY<@}\PMFf!Dcl@Kx-zg[ih[.!o?G].)0M00~Wkk}>K*QCƤֽtLkg]co-מ?8I:o{iyo:zZgza8|{k5cҕ:wL_+svWԫu{x$5 e6zoپԴ4v&ZP)#ua+Bg?<+חg~_?kXr Ч>y؉/වV݊Z/I f֋}3͌$;;#&{FfKyux/|(D>sok5gL=2DEC.?s>2=cHXfNi&:L-Z4^]]&yJOgQP' & VLOԭЂX"/!R7ԍ:cܯTDfVԎշ|d~ŜK祊cfbN?S/VffI;ҼBGOL<\^:sՍs=`|Cş)R7:Q ֛[֌'+Ub d.|{z<h3+Ưy^( 3֫;^̃V_e 8W@GM~@<0~CdmO13dcn˷hO`zzxNC,V7QZ*4Nqے]֬{HrC2Yk[OkeB&lZ8qp~\2%C?}bJ2#^ԼW/2h_:a[^+vT;9Bb\L[]3 zJz^5fbtlZET3o6l'U:G=+;U]c+*֮4g^W׍_?k_<ho&@@kc w hI=w 1M5gN˟= wMʻ:; Lv>tIkSK_QI~ CΚl;3B?X%hZ_}>?+H\\:Ņ Fqwg|^Wk=OK[]4-7sGk5tٞxeffkl|G%7'מ?4 ];ĨxNe^]7Mh Akؼ%Dѡ{08ًOً\G?+̺ϼd̒'͒t~n4u2ˉD]x%zS7.vpfܳk }Wzžm{T=zBZJg~WuJgWol>÷2/?~A@+ m h ܎#@ZjR=#/trG4x `]CMqK8+:ۈ<JeR/]jQ76j` |7WKg I! WM&3xx|Uj_2TF,?N4&x6}=VVkJ_J~^ ҥ,(FW񯅹u/zt~@IDAT^^ʏV@%a@jC~?̏YfLw+=6^_{j6}O6J^c$[G;}}y3Z}?ًd*@@+S1#q~h]/7(T"I5Z^_[m?F^[-/-o򖀷F["z1ZZ_#Fy.@@+ 7<ƁC+ 7<Wv k\] z^zP/-o򖀷FzyK[zV-#D< o@! zŁC+ʵgϮQ/o=򖀷FzyK[E%Vhy^_V׈"@  7q yzyG@! zŁC3gWިԋzyK[E%^hy}y^U/F/@@+k@ hy8<H@qã^q yzyG@!nrAo[E%^hy}Q/o xkU/ZުE 5b V!      eY1 @D  5Jo14F@@@@@@ Ə|&C^Vޗ"@ _.߇@@@@@@HKg}ԃ>V{! چ=    0wygp0#     507B@@@! 5Ȝ@@@@r*@@+t    5=E@@@@| U`     TVR6"    hyX @@@&@@kUE@@@@WS@@@M)@@@@F[z.@@@ 5b     з}؋   (@@k95    dEVV@@@r!@@+    C)@@k(9   d$@@+#.#    @ â0$@+p~tW|Mn?o4y֗[Mވ  h\    #\p %pɹm +G&`]aZI[@-א+@`(m<͊+t@@@HVF\4F@ o?~1re|s'ҮF;W)Ӓ  hy~{PIs@@@q  9"W茵I#Cw"L@8" ]\ 9{Eq@@@-@@kЄt+ЩT//Ic&y5yN:}[_?ZMidbv٨H~vz2w[ԛx_n0S,._C73[_SzL3Ǩ1nJfY[n~zPxKzK>]Tܐв kEׇ5Όuwj5*k`tY6'c '\gۧ\.ެ+>=ߦ[7δYEgz =ڮCj ]>9s:5{ɚ}+Ѿz!f^%U{+ԓJkv )=9W=&X3]ǞU5Vn>HC9gVBBゥm5玿V4g{εasKtz0Ow\Tfj[z†ĆEj>h.Zo zXE?ՠzvWaX='xUZN9p=Nwkɔhi>TL9CS{Y9d*Q7ҲG1Wu*}|)QutQcb8 URӤN}UZfrEZNvUc UuyV/igUhGLvj jks's|#mku6녯{>EݬRQkG42zC8Uӽ3CTuU/~F:Z%`E^ \u_7kG}]%f)xn, _}Il72}^M/h|L ݦ`Ug5;5׽H1J+R[pH;w@+ТUoJ*77_ק3eu/  ] CA|AR@@@ @ VM,3*:sUfz&nVvv[UYgz[5e}*:ڒ6r7o) eZ5ǔY{Nq֊Uጩ|O+X{bk* +OZ/c;{*ⲲLL#c9b:KC=22&•Fu;h8^Tj_Trh۶?ҾT^TkS1]gujff HuxƤ9e;{ǩ:.:?Y21@һP  J`Μ9n @_|K}    0̠TcF \Ff*-_MvdOzl:tݔ% _.xᆰONBp(U6U 7Q?'c4RѾRs͠*>ֱ__;y^rfݶԞ*P+n hhu33cwjgVB9>mg֨H\{B!Fқ}Rwl0hf܊GXeT U33Enaڍ7=ld[̨/fn=N# nfqkpR " @@@@ hC^kB/UO i"KV[Dn&c4\m[YwJ,4Y.Y."EΫ:eM@S*&Px^9K΄рV9$]~EvH~#Gu9`$iiYrpVY"qSpD.8{YȄPUE@8IWYd~Q:љg̲M/aԯbO6}Y 1zhMI2r;i_lD@  +Q      0:[~a)fB-g=O3&@nTfr[4cKԁ,7S$=1$ZpF3pߋ,4Wex|>InE%c/VV$:(v L/}Z BEgrnWҦIZf{?}\֪uEeϠ}[f*伜Ya8ƚ;y_lE@h@hD+@@@ZY@`D t6wQT[Ù23#wo9ٚT4=a&z7^A/4;H('q9zy8 OIuv#A@Ndrޙpދ-pWpa>o,tf*o8oϟN`M{G,{8N'&\@рQ4XdH^@4Qc͒ .̈e ^5Rd9H,`{>1.zIWww@&Ew}/U@ī"h+;{:5fFjª[5 f9,Wc%#}wg .v  @ @ Wa3   䇀 @ MVdTp7\VuO QYݧZ5fUS:,k۾* 7Է&t|Q92zv&װ2t[VP^ꧬ<6[+MvqmcvXk1= +ѵ7:&%VSR]gKۼbϙZVUYY0Zۜp֩ڕAŠ=F*[sMY{8~.d#Rn^n%>{\RR&N̥8@F9s,@^ї@@@@`4 *|4u!)m6ѐҪs*TX*ڗTYCVuY8De_-u ͪrH*ꛝTP ~*GlwHmV}EQJTb~)8^KB@),3!&'fYEf U:uE /вv& VGwqU2jhBGis5GƳT&(g oQe>Z3{*"^#m{UCgZ'TgUCx>"R N^w#   t18?F`bTD@@@,qh ȵ@Y&f{Z2z Ԝ%w+v&}7}.~?hrї[pV+>Ӿ57ߡgd_otd.v+ԵS td:-6Ҽ6L>7    Mo5 5LTٞYJ%VZoV:3Qŷ_ZV$̮TV`5ևf[*qfЪ /]l?*/r:Zᑺ^s)D_MN{qC jkdX#3)]T_=;XEmc̒qJcF@@`̊3`:D`D d{EGSuaaux68;Uflg趾0e]ly?]yoPy_T7۳Ӯj Gz8Sپl@@@a`-7@`(.w^Tgg4zƎcSR%BE]lV;nu8>u[]{흡W}d1۸~ {m\uNT 9y7^Ntew]k28{O:ZSoEWƥ3q%*`;V(1 ].0.W?k^w@K@@ge3JP +"3ԘٔWg!N=qZjf:6a,=|=oxL?Kt~2;g^r1}emzѼ?>hȬE*/z3ЌܓmF[MOޭ;6șiW@@@,@@@a~0j;Ug@VL|8ܨ|60cxIpɢ#:}vN@@ d;t#@WDR1q?4Cп'89NNv9Y=>aIμ&ֱmwR6=: WH@+@@g~Ŏ66&CּEc(c3+R_/vځeO[y)0buϒXhY[k/ ɉ 6O*7JV%鹻]iegI?֜  @U+=M;@E8Cr˗Cٶn.xLt?E: MRȾ+(=nf< @@`A:bA+Oa `]15&钟w\yZ6Yc;@+ԦWzP?WvnȦ9씜?\Hdݗo i&G?JY{cyYkRq^Yme#t uc'ȉ?.gNmQn -PV9)W|ԹnߑIݕW[@㦙W] К<󣺘N+:+Wj\-{JZ>Ӑ;J+D@@%ji+ @ 1ؿGv\ɂҕ8 <o@@UEl)w+zAWDzG5:aYx"&/J&Iq9yifp\6ʭxQed]7-['O7r[%)?apfèaIFt Gº>h9NA+f-.ͣ{;C|u9   PhUy<@dxw~]e5DJ@@(7b#Xe<#MuF+#Ѹ<|:6yz`GGUZ:m C;yPBj[-iyh;8,KÁr]8 rh\SW$%l_'Ɂd>b+Hɑ7鋜9H4 Mwtx*#M5< ٙKv$iwZU>   ԈZ54@@@(PVRluF9(wŹכd㣯ʍ›ߔs? {Pk۳ͮ}sX2Gaժݓ˷ddw?rܰ}oZX{A|GܴhKH::{k)~quW˚8"rb="*a׈|1]xkjN *N?2y^w%'-LnVl8   @U-=I;@@@Br.4 f)0q9WzB-o SJjcD_{fVD9ANq]zjl-+@kn%eJ:ȮWu_ZQ3`G&SSSfɲY\eӫb+k%pb@_ʼ@No:jƀ2.鐯v+E;{r]-K   $@V-6mE@@@` tDu@`^f]"nTeƭ-=Qfj+PIhVZfI*kzChϨZ:䱍ΪYpL?Mk~+   ,erQw@@@\`栋*y P̿+-C>9fyɎ rWoqhK[J вX,CɖzFL'X崥99(/w窘].NV2f,OMeLY.КYu:8+ J'!Mf's?.}@@@Xh-   TAzچ R`LFV#oe7rŵ NȃhIGSzUhĻyrZS7H`J$#eH*AI-;M Z# OWE͒Ki(   ԒZ۴@@@%&0skEy(wœev~?)ktd3ryV/^% ?/+ Z6(&/V, r?ݯQN5c^˪N&OߧۢhXv8Dd`_yW2 |V;uHV߶7O@Z~O ^u+VQ= Zy   P[hVZ@@@@9AKATEcceQ `@N0䐼ceK9׈ھ r!tMʽR\)v~yXK%jK8#o_-U?ciyfYJ q@@@Ъq   ,m.v=|vWCQwAo呭BqkϮ}vVX2GaŔ[^x_Z@$cFr<ò;,v2u`c+seNjOʩ׿-GyHk~=$h嗏0cM vsV&,h]^.o4g@@@ZK8   /P~E[B(-0S26sy] f*]Ɯ_guV˚UzK~Mo+@+a;hތ+@@@ZKi   U+0렋a 0+әsUfQĹÏ'JfFR@@@.@RA   @ tQŝKC~W!ftCu[$) |6џ$~9;@@@X*\   R b G`a]0S2*n Y<۩Iϊ}:YqUWO5'@@@t*   5,@E w>MG`I@@@@`Zpr D@@@r(WtԶ+ji=   .@VC@@@ΧB")   ,Z NN    PAJwEm?G@@@Ъ~   ԰A54YbX$E@@@ @k)@@@ \)!P   TZC@@@ 袆;#0 ~W    h-89"   @]+E:j[@@@J @{!   P]ptJ?~\x G>?,s@@@@ @k)@@@ @l*"P3r-re>\?~?+ٳr7׌ E@@@ @2Z!    @ 444HXYi@͛7ӧ O@@@Xp@@@@\ʕ"%+SO=5m_~ey'MI@@@@`!Ze@@@@K @ظHruԔo[-[&|^<@@@@ @k!) @@@f%@֬H@M ܹSx㍢m ~a)   +@V 5C@@@Ъx뭷=X~sD@@@Z<@@@([H@ |/|A~x~ʯk=   ,Z%O    0Z3x;(r]v|[ߒo5B@@@@Ъ6     @˅[(x[n?{|͞c|@@@@Sԧl@@@Viy8ZAs7oӧO   TZT@@@h5x~@V^     PQhUTwP@@@p =عsgTGGG$G@@@ME`@@@I8[oA@@@*N*   Z?@`:O?4w/.@@@@E @kQ)@@@ @%     @% UɽC@@@qj @@@@@i   *@V,B@@@@vЪ   ,9\Qa@@@@( @    P9hUN_P@@@@4.͍@@@@`Zd@@@@@y @k^y@@@>ZEk@@@@@Ъ^    +@/ @@@@@` :"   PkhZ^@@@@OSZ   @U5]IC@@@@Yji8   /@V5D@@@@Кއ    h-">E#    5'd̟)ˎ+n<Ҹa 'T_*_ 7ʚeQ ĸt|3$z][[{ȞRj]8'gϏG|"W|~Wӡrq$-eyq#٬>B_T # @ ,-sl?;)U>t1-u=T77e\ܷqE*#ugw^s('om䘜22葿]_/_^*QK'8NH9Hx*~~]:   ԦZK`,SȆRvo!YeHU{<.W'DZҲ[^/k'Ɂ̳r" ɡ>ٱ;~_GSEyFJV㧤qmӿjE@V*%@_~JS΃rHmM7-T|DgF-=oxY`7>=<<L9)ٗvL?.E Ԛb!\HO+zreLJrW K wȺ2s@@@ߘ@G`7~_ksA{mH}eF]G+VUńʻxi:@|]7gCўQy~ɿ逭 [Ekqiy rTD)9-rA#]H;\-妐^W]͍kd. S\c}~hn vD: |ˡ@@@Yji8,Ԙ9+|w>DJ?ɗBoUr;a]?A\uSm"5 3'%urGcL#ȯNF5x4n^d|-yW\)k7WO6r U>9 +ƍl}.;%Q]+Jw}4W`SڭG蚜KyIr*i:G3'!}Lɯ~g{\Zm fh9Ӕ\GwSw坿%ɧQ6ۤmZ+~~lߚ?ZU,6 G]~^aoR^I.55vA+KMrӣv#i̧M઺\p%MGae  ;@kL~9>xBqy$Wl_1]Ⱥ/ߞ3_?"#kd[scN[okYXί}\cq 9n䇲y|>Ogwxo)=v/5ڳ#dn>9+ꫯk򯾲U8SqswV|AyqS S;-?M"W߸Y'7dRۥ5)Z dKQOCzKotv_E[#|[tr~(廂9iZ1?6o;[~:B[9^yvt6j6Xt?s@@@; @`2JΦoToFL_kx5[v`>'V4Zf>';ָXilêY&o=uYcۢq$;5RE~ߕL|U>*;Z$=6r sl2Eb 6-P_a{rs>jx{Ru&Uei7x99^v&'+2''ͭF*8n߬t]'Kr 8{!׎rSH$֦Fg?+-_=G1y>9i9e0i1e3pU5sͻ\ x   ,ZTjJ k"V)WlAXu:3ae>crB=Z)m}((1oSF.&N8rMk% 'jVM&uzƟ XJ[Ʈ:FڽOg>J[\q+j 2\y"65c_o?J?J} IDATʺ1b\|+27\EЫE<8TGu){W}n} .,3  P}eyL1?= Ǎװ,U;e~alV !kl)Q$pqng`(kk5n`UG@'/#('H/QP<*w@yVUn5/5}~?JsH:~)?'FT:g%}V;NNq1ǚy„롦{ia]갽b%|u=    0_h͗,"~` #nQ9MfIq3j"=7ıDcmGbl%5odFͧu#V<7֭`)&HL9u^*eV $U[[r/OsYAm\|nvn卝z B{ ֍ +@k6^F}>>Ӎ,FP ˧N"[`[sE"a?~7ydo'Ya R@URqw,qǠ9+@;mSmm, vfǓf~0RznaF촁W ƽ>Jr\C^qWlގ;ýŬ'y{<9i[s7kn2/=wU⭳U]lfg?z®{֘gz}_e۬F-fNs9'[ַawl ęy@@@@`КoaG` ls>Z\S2짴8~I~c짞#jQA庉a|ʟ( gZζjUPo⭯cfɵKAi&qkTJlKyWj0rQ )xKZ,'־3C}EՄb]A]]cl)M|JVkU/q@U`hA5ǂ>A$b+'_︸x{RU[+}-P{W|TX vpUog<촩h3;v|-3#Y{m9HaVvyi!B^<4x3sUaig~+Fl\M4OHY+Gm[   ,ZN P=t7js>U֍++;A@uS/fPTT,61doR68/midmӝT(&ؕw:7;zs-!51feT^xg&쀲|3f;ZysKo5 D on a IYCPIse>!    /@›S"Բ,}nfsn t(sPo11?{Qt:򒓶Vz6"nYlIШۢϦT8,Ivtv3[`f/#T"cl%]AJ(ҥK;QDS!9PPo}fmǨyؼq旽 3s@@jX:Z ,Ǽx|j}b+'p&~8 ?~7ܸ>I뱼1s:PNKwXЧmޔşJ8m7TOtԨ2[ENN.kw&זz3{Ձ*YzyyQ1QG2)V`WU,Z\D  4OZGF:;GA   ,ZO Ps߱Kߐ ޚ榃;2&g{Q (D$`ҩOԳRt{' OO+u;ͥ wVWzq /_%˸{iomf>e|V]m1fծHw}]Kf_Hx+;'.O"iI U-PqZqjΘxZ|nqf逘2[‰~!{!U{\-*ТWE*| \Zo ]si'(*Z4U@N51my57{PH@9/]*nڮF/G]^;-3 F{rd¬ۓE{}-HG@@@@`Z$xE{-52L7$*?@x>nk"r xϠtsV $-J'opY]xj~Ϫ.ͮ+`Mm$JV"6+dFTsoeWT@a8Q}cĺi"Ḳ Hv7L<7M3a;c1߹ƙb˘|+M\tt~{eH\ f+2e3C*Q0-xVd/wQphnS"Qf]?=l܅;M 56V˨Įg^z$oOY .}52sQAON Nd7s5k LU*,c(5 ն)x\c_V-;\ ˌ>c7dRS#P(sΕb0W|k eMIA^ uisohj`j#.n"5[^muW3_OT/:zg;C|>^G]iv*P0X&5sAtW:Ofc̗+y3#ٹ&Z[s+kk$R(@ P(@@?MaK(@ $@TU$u#N1&hyyUbjH P@%AI*%*TAD3}}ID6hUY<: RMJq#5U^+$Uԙĵ絜!D)Q$ز #lDe^I" UFGb[8rOґ#v֘k+$&Dk y=-\ծHD}R3H令 {}:۷K] f%/ s$SQg?'t4>C &y8O*d?Res(@ P(@ 0^c(@UlntZ{ee`dڐZO`1|Ӡe ݰ!%5 cF9wTgR0nk >v~)9Ӑ&r*3_X n *еwFWwf,V ) u064J"Ir^$/>k{Cz\Z{1 è}cXy.^u w8Fe^BǕ(@ P(@ P@;&hig˒)@ P "+-N ӎ`Ŵ'WWzD `dqchĎU=7zvΉ&<*˚wmNBtoDnc'R&+v(@k_9W*JWbNJT,x(_4^Wcc(@ PЇ6N^.*%ėh۱0cf{zQN,,_ɋ׺va~V=w=2kзͯiŖYa\@eEΆ8\qco)@ P(@ P->|b(ж^> ݈s!۔~`Klػn旊)Ð{!ao\o n~Bdήb+N7 O?Ils(@ Pp0A+kv2ĕ|??siȿg6br%32u%SA 58\]ƲFE#˧o_Cq"T#͙:FΘ6J,(@ P(@`VB(@tcUXVNȒXR̋8ẙV܎{Eb!LS&UE P(LJĨO݇w`U2OBe(]yXĈbb vbD^˭L P(@ P] Za&`v3|; w&gQud(`ؕ#1_&ZlY(@ P`V쓧fOt/g1&M ^+{ąa’')(@ P(@ PУ(@ P(@x P(@ P(@ P.x O P(@ PH`&h%pp5 P(@ P(@ $$&(@ P(@ P Qc)@ P(@ P(@ PH੧$Ͼl_΃*qc  <x.دx}Lם~-:w\}Lu=:?Ё<x 1c<x Hc` q|`(Я݂P@HqyRyp_HR>RxlP>2$9]HY##wo(cCE nVL9cc<x 1ccy PPO ZY$ P Iq?op_ cdžS <U3A}% ZZ?1c<x 1c1K(@ P RA/X>(@ P(@ PG^{5M6h Oe?,s(@ P(L%Pp@LЊP(@ P(e(@ P(@@&hp (@5"ˠ(@ P(@ P@&hiB)@ P(@ PLRdJ P 0A+bB@ P(@ PVLJR(@ Ph5`,(@ P(@M + (@ P(@ ( 0AK+)@ D, Y(@ P(@ PZ 0AK+YK P(@ P`V PPC Zj( P(@ P4`&,(@ P((-E("&d@ cƍXf (sSmxf<ֆ/ СIî&ǴL!1o%@ P 0A+\)nG P.p9twV+MQiڔѸttΝ2|0.o5v\(@ P !d(@ 0AKa(@HOOjEZZ,K4-(hǰ䟱rSm?=3xϖ|b/ǀ-(@ X-hv;k:i,(&|{4Ru;PE./wmRZr?<~V^Ԍ4dN W;q/w3 * 謑u%JSnRt!-]P Z TvA?H[|P@N4o™aHņ"؀iiىR@mqG P$ql[89FgO/K"c|'Eΰ[2ϥo`r}:T~ٱ62:<p a|(&h l(LJȰS@4MmE P Pm:L4`@~?{?}vc>J~Q[ͧ(&h'l (@ K܇FeM֑re81]+fKd{%KeÛw`+!%;=,|e>??]ߊG7LTLҊoQZt6طX6"SaIJvV(@X0A+ꬓH&h%CG P@SNq)/ (R H|6[R^25a.Rh-(C_A 㗫4h}XW9^dG GVJN>( WOh:%kt_ ݊#1XLhf4^"#-gغhwyIqpoMkHY'(@= 0AKa(@x`V݄ܹI$>M'>?_ŋOʗzٴ Fmp |(|;1vs(w&h=Bl(LЊȱ(@ P !ݰܣ`,k#a<н gJ+jy>Ƒ?ėʸSfdiYP vB;0cʆQ0|mR6X0[q/gMc('? Q1]~)=4_y[RhJq(rhP v~ORPtz;ǽT`1 \?^L?K? c l~#\^zus5)Kz'7:W"WtkL@ P|m;ɞQ$3>ΟK+}#pRl۷q %hмzv%{s(Rk[Oob ވѾN7 w8-w d<5xqkݢY(]u11#q,"IO"6ˑ&߹Yׄߔ;dߊ|)@ P`CRq*8 M P[8 R7BA-AY O _z,S+˧V Z0=+$ڌ-%z)@ $)+(@ #=:`>F+*Sgѡ$Y$Pk euSVPXk&/^;'spsv>&Rl!|"/{ψZFcDf~uHr (@ U Zz E Ļ=l?(@ PHFk3tP4vcT8%@̐Mhȯ@պk_O7PCki'A˷Iy%(gsp'̱aMkf ݉(@ $`)@ POjg4+6:Sd Z>̚q&\7 ;p`~sMhP6~!|CL5e#ds﷢'^g:HFAYm1_g]Np& P@LS~VN $8(@ PHT-H7tł< ^+ 0[6/ƌ btѹzJ3ɲ 9&($h4ty|jmہ<^:QW#T'R Zq664M|H;<gߌbE_k_]Lf|bubEW5ŏ܇"Ao*9\2| '?o)ϚgbD#)͵(@-*ˤ(0AG(@ P@ 'hU4uaUEb1ՠ3*D7]g~U/vHis.PK,1?Lh Z1j<j`3]`r|MZ ZI~v|JK> r/5Akpxl_Żfdvn߶` Z$wW?ne(}P5 o0j׸(W&h52l(LЊ(@ P DZ-HJ#A$v͆^6L!֋Q( U!FZr^˘}5LpS Z<(@ Pr%+ [%%h3GߋPSNxO᧔[7+:طm}.~ށKcLGHrJ5D Z@q.d*$)u(@0AKA`(@`VB(@ P-`= 80hT1!^=Hط3 |FВmh=cGoG?GGd,Ʊ*Aˈ=䎃(_bĮUJ#vWD ZIhvXR|˽ys*,. R駻|cUW2;bfgҖnwV.)a US$pPj!w Hl8&&r_62%G2(@ X Z:F ĵ:|l<(@ PHN1a'*!xNԯCBX ZؿsVn QP Z~ws,'ؕ \ ZI~(' FlStMՇsPz *S O"\y<ktOh9.~]pٴo히M"i=hsby\Qψ(OOvie h}g\hK]ێO)@ P`>VQ/!{@ X7nĚ5kPXXְz PI"{]CQm7r +]%$hPn %7h2`nǜIдU_J1L; Z@|N $`)@ P_c~1;g3A\Sc 7B*4x9#Fzb"ߔ 7%ۖ"!Yx|ב⳥I߇xJ/HᲐ#bay-%lCx[% P@@b 0A+1^QQHOOjEZZ,KkfUY-ӱms6Vp_:ugxlm/ e ZS&`dcod$,&.yYրf,͒5PH:&h%]a PG+XsHTb[&徎x<.0`KOedikgzĬiHSJDZYyk#zN'y-HW(&hńRI $2H h+0h Og(@ P@#V`[=WŖ{&yZ= Md ZD=įr/4)bEo,+liZmCH4,olD $ (@ rwIXroH8kNVe01[oBsjAԇG|tGe<5{%7Šs+oEOܣb;)W{&ҷĨH8EG\UM}:5(97B\(@ 0AK1b )@`V|ƍt$-MK/ >`7p$D(0 )e Z,S2UgPʗ:BHrOL:AXT=r1 g5]{W! @0A+yb͞R@O?a۰jOFyl:eQL\(7iM S?EwQ̚Np4ztm|?&``o%ϊ)ot}sbbԉ@ދ?ĴL$1Q'upkKB+(@= 0AKQa(@D`V"D}b*)c)@$8>L^Ўa@eßIPm9˱MulAk{d<4Y L\1bbѝ<5t`q}"I2l'] Z~((?})٣Sp&=ߟiw?gW%ɾdGvoثq<&ՙvYZwݣK*)I_`fg%1bTqV$zĀ,lnoxPםP#~y PnP!@ 0A+P(//ƍfF@R –px0"( u%~+Fk*v%YVo Hxl0WĻM˱5w#ji%ŭeTP?)oXU*}[0^.QHJ&h%ei PC+Q-Mͷ\C.ٓ8HNdTJrTE1T|܉|)KnJ?OE >ee?x+nh\8)JyFβzH+OಯO(d<٘wbtz;df9wh\(@ ] ZzG ī5rl7(@ PKv{J pc)9R"Ak Ԃر"܃h+hiSSz b`YaW-x`Z?3gB ZIfv.Qㅟ哴 ,{vnQ6cN0WS{"aic{*. %kÿv&-Iid=hO5}𣏕q͏v-3G&B|o P@0A+nBņRq&8 K P(,pn<ƄMA65yd'c"* 7=*tWAE-֭Za }Jo- Z`!^ʛMJZ"4VcarD P&hy(@ PP8߃#y =]k cp.ƿbnK-E }mwEWR5yV,Qnh>/b~ތwc,{tz"+>{G:JY#Bȗ(@ P1`VCP * ,E PU1>> Q⦩qHU;Cq 7LHY!gf_eo -=0} {8ua 甈,@L PϣK|Y볯(]̫9*\9|}yUŏE|i9|T2'Y '><})uG 5z",l>>9N~<8ϡ}H|+C&)(@ Pj 0AKmQG P)- (@ PP[?A"s]ˣ(LJs(J Zq%6(LJCFLR(@ $8:(ey(@%K(@ E ZzA $-(@ P@@0A+aBɎP@ 0A+.Q@ 0A+P 0AK#XK P(LJLR[Q`z,(&h%l(hLJ?(@ P ZRg`%F4Y"'-b (@ ģ1jl3(@ $;|sc=:Y~RV Z ZvbVO P(6t耥O-e82ǍD&D P LЊ2(@ \a Ǖ]bI܍(@0AK/`;(@D`VE.P^^7b͚5(,,z(@ P,D.F P(@ Pz`"P"D$A L ==ViiiX,1k+OwcÇz(ULrkd1݁)+gL/ugR 9we{WW|bK Z/j-/+⣵LЊ8@ 0A+bS:4;t$I:k/ ><2+2hxE[<7mc"[kO|Ys TѢ:_g-cs/%c1^J*]x76J-cTolZx)oH &h%F P :LЊa ¬Z|a6)x /"f0^aBd3K'WPanz Za%N /jb CKxf.-0^1ĿK@.LЊ:B ZIfvRSj~ZS]"_gbK@j}3AKxi]oh-nZxi-nZxi-nZxi-nZ-dY.(LJ#"(//ƍfF\ V@|ڶ>Jg+_Z_W| Z&h/K -RS/-RS/-RS/-RSҘ,˥] Z~L@|IƩyw/͉URS/͉URS/udҺ4ZX/u=.ZX/u=.ZX/u=.ZX/u=* ZZɲ\ P GO P dwҜX /U95/ҜX /U95/RXkO&h/K -RS/-RS/-RS/-RSҘ,˥] Z~3谈 3x9z`F*n?+ 1Hյ1iV&\lPջ9έo`|iҴBb;% q߃caQնDԩ/cW6aˊIvJOPy@ΝU|+rkv9ae{_ďW"o$ RWo^b``ѫyIXΈ׮f_lv>;I\й`N+ɻ0?sд ﵞu>q,n]/ֱwڸJs98IC7;F6<U9=NDTHc1{#_^^1?} \Ԁ|KyIO.^|aؤsiS\j@0QWDV}UuӯK& #m6,U"}:yx z_$ܔUs(qd)Fy3F\-ZSX4m N?M^{VYc'_`N4ƎcxwZ57JDk0a5b*w0m8 8c!,֣q|n"&T b?bYT~-,qHx7o ]""at:{7%^Gb՚fqHH1j~ ,Ƃ*Ta8ly8<&E!dZ{Mօ~tuWsg~dbHʔ%hy< b4oƔ'^ ]"8G")S7t,G:p87=*~쓠u^ܠA33 1Ŀ?d݌G&;n҄W^$x9zы{QL'p(\{#q,/vM+*\ C |:ۍ?Ed=-t >߃w_|۟FL\@ڔT;DvZ|\"yVylX_p\qݑ _9zP='y8 ,)bгgp^BnYt'~0S^/U߿ΟQwtxkςUunCO n/#>-_И o蛳F P 9qf/)@ )Ћ+RlHi"X9{qy1Z :Q b, 2,FebVĬ-F%F1rNԯC҄-/'>C+ja8U3œ@2%B0㋭[afG76/ƌ.MUjHB_X>MDrBʔL>t?z KL {vwN2AMO5/{:vg~F(C{xq{zsO9H G>Ņc1͞l^'`T/tu/uMMi;n/>1ӅP Z Tv Xeq:Vй,q{?捚}F1P|n{ނTJ&h]:aEp?n$Y3Y6!痭 o<+gczHױau&JhE "qk^Hxyg϶ؐnV&hyx`(;Q/)ZZy{>si$/-"au%*T.rs1 >]*Ti7 Z>^6njOвYF=e!5-[LsX1氶sOSyL P߇xwν*CDrF/t(~>Xy>Iۨ{o8ỳ dAO!x;=n{?8˯L1;G6hyF{~)V$S<(F>;i(z43AKC3k֍un9i{] pk sΩt Hׇ"n'gac i,N}w)^_,wϠ~K"YB{ x9"=&2X|.Iz0.Z瑿`Akɶȹ! S``)(@0AkXܔ@ "AkymZ,c|^^q;UZ1b ZLЊ}uZi2QZ$IqQ/ ί޶z3(PWNxَ0e26wCL,)Ȇq:}@L{=>򣨶xhh Zĭ bÙ>S-pǍ 8Ho;MO7tιnY&?^$Ϣg,iN<L3ꈧx9{\x9s'}oB &FG[*FG 8wt)^_}'Puiw(ptB~&_Jkx ZƳ+/_k߀[cÀt"//[a}ȟkLy/yt4xߔa첹(XI?߷;K=5"xӤp&h b(@x`VG(;A b-w-&h*faLRS3zeXR=Bi7z-QgKݘ1$&>Z67: ZYJx޶[WeogG Z]wG'D͘>S${m.ŏVhZ"A 15+U$ǺAP*+btկ"u|:L0٣sLh؋Vޭf4tQ9շgb&h72Gh-pS|W-_?\7zًUx&g[O7L>x>7wwO3y^Ǘ%[D}o n㏌wNvkQ}N])^^H&] Ξù L~6I VK/mp^wbСp/g"?Hg5bkdĦ%H~>`Mv`v,Hn&h%w{ P@ IT(1EbH@X&GDFӱG)eS/no]b"ڍ ZEm^1O}(jDfd7b"_6ÌPTׁ ]s[(#ۋ#wh|vHIrskG/E3LВDi ~Sbb$ umJ{(a;V!hx{}3J$Qez½>Ļ{DO̝"K,5i+o/5xEz~yO(9@hoOrD~nr$?C/bOb?|/x7k 5`-&hE`}@0A+Y"~R op~2< =xbp(mG"G8}h}/,LЊ@ƶţ(І8>B<ί63ўc,C+kK>YM{Dׯ%B60DvF>qW$BN]Ţkh*{ 7#sjބ.L"N'B`;)aO,kǣ?ۉz,\Qj=%l6׿:H)zvgA0RzuEiK*:n'}Aт'+GD'n~5f;*xyD)^N`7=RκyC\Ns-Wkx?;.6'dߌONhW|evL/ 0X6 0W.Fܠ•S5X_pX܄U/g#u}o1_X@t~y"}_&~7;7=5u):EJMO}}x5"+nA ץx0sk(WQgl1{~: ;Ub-=DmQ ZU@bMq(zcEq3bj{g٢`y8/A|Q=\1A+z@L:"?Kܩ1nE Zڂ>Jǣ;Q=h8lm(LMQTbDiP? W Y:s}Rr[8DE$h%@ /q~H a燰u~U / 1_/P\NjycZ{&ZΟo1iQs:WgZ{"B섌:o"9=eĭ.7g#(?Z @[k) ~.s&FA ++r~C[ϤxW!Wxg\:ȀkgxcH@?E/23]TR{U"TW8G {5n4ί`sJaպx(0O윿Ͼw_#|zR<+/uM(/mUŏ%Orv/sG$=&8w<'`4j:"iX䄘ֹUtgVtY(<LJXFظq#֬YBjn9Gpn,@CU)1Zny*[kX4A:%^F ʑ`Ӱ%J}OZIYhPPY󢒌ph"ao+FI0A_Fϻa?#Vo@Ym.J,;t_6x-y+S.Ř(UZ{w&??7 ʾ 3`l4pRê&nxn78vQ=~߾ƈE(NG\>ySB <iG w5Eӏͅ pv9S?儋9Ι 0ϯm8{c{xwy.9t1c<x 1c1 ,e PLν)@ P-0n8Oվ.D+#Jo-bmݺU `b#yl+FJb#w#c?y<x 1c<x 8 (@%K\@ostc䐁 8<6xl<6xl<6PJR3Cxބ1c<x 1cc` &(,0ȾZA P(@ Pt'pw8kml(@ P(@ P(@p(@ P(@ P &LЊ ;+(@ P(@ PT`,(@ P(@u'K(@ P(@ P/蛳F P(@ P`VP܌(@ P(@ PЭt6(@ P(@&h(@ P(@ P] ZA(@ P(LJk(@ P(@ PH&h%IM P(@ P@< 0A+6S(@ P(@ Pr&h5L P(@ Pp1(@ P(@ P%0Aи (@ P(@ P`VtY (@ P(@ P(e(@ P(@ D(;(@ P(@ P(s&h<l(@ P@Aǎ ':<.GƄI2i)l꿪z ]>yׇQ02={q(@ D_ Z7g Na+o7ކٓ/lǏom'ppE&F ^+0:ψ2.qY7b [r(@ P(@ PLRSeQT8w3&_X!uOc7al﷦ ksx3ƏO)@ PЙt6_S{#c~1 eM86Gq]aCKz9&<"?!wͫ1ր2W߭x~y#Іk(@ P(@ PJLR P=R')*WބM WEhۀw`oY ZO*e(@ P`.F)pа wSE v{}wcf^M:k jQh|@=9>SL(Z:('TˮOr5.S(@ P(@%K(@ D(`C} ,df ;05ZVh*v_ЉG煘*f-%C0dH77 7w* (%(@ P`V,Y@zףh}w+]NFJSYv-KΉ(\yhmk{qQgQԕe1g3ha{nK P(@ P`VR܎@V޻-/W7ߝi!YOƾ:5_ɸ8˧Ϗ@鐿bߩal_uڝ3Q,ʌeػvv@?(@ P(@ P@ P^" U'5V+lTV'xH.ZBKQűRxnjd,#5֛_.I*p/CQ$bj%(5 CB*9)OqB3)C2˽zB'5U2J5xZ}1Vvjl[ꍲcb,~@xv cS*3:`( F ܄(@ J 6 Z/޼Wr&f`yTz~ +u/2EYK'`3(@ P(@ PH Zqg PtIc(Te!hH|:ߝc,}")U}D3W{Cd ZyR]+J^o^l(&y2H?ZkId&ΧFȓJ"Ş'F r%}5.CaʛT񳳡ĵ+JV!Ɠܸћ\J7٩}^ItQ$ՉGC&Gym&xZ/WA=7d`ݏ/P(@=&AŞJN!<_ߛҍo}^ם[xTRSD_{&<>[ (@ P(@ PLRCeP)#U{hSUuRkœ*hw$yT0ŝ),hVٕ$AIrv\'UVIU b"DqpVL~드bHRJFrT+UWI-B_EjvfMdLlpLnk7WYፑ=I4wgÚJ\]·kHs P(7X%hx lJB!<__eVKpn\TggRA2/MRhkTb~fP0* P(@ P(qd) 'uuKMuRɛlW}T/AGjsN!8`T,=݅|%z$'Ġ RUUTbw{>Ϡdz,KեPPH%o_X{wGǦZk 4w'hTy.O}t`}SA^$\mJS\T|o =nHyUJ7t-P?{wu vrLsp"ʘ(8"H JS.µxB ;>J 瀜l++e E-J#'*)vߵgfϻf4kH@`F2<x/e 9597Osx%2yC;Gy@@@@hnH  @7 sPQh7Uٚв&spTA>?nePR3rW*žP _gżl.ևB^}-5@@@@&@@khn B QfBx Mu73?గ: ^ܱP0Hf ۛO_ z/*CK(eYmԏ[x,3huf*x&5dπ"Կ$fв†UeA<F ƴh>OuMO8kCYafDLP߂C[原!gٳEc>4} $3נj:#  0"-ؙ ύGsX~ZfA=7*'\*\yųC%/HJ% N#MVyP b e@@@@ ҈IS @_e7a+u7᪶7ng՞t΅5x=ϴ99p@^:z>YO,8yї^x6wz- H h׹Xsf)rO(fFhs}Ae0=\!u0|hm[SU0UX{?G&@@ +F&eY8[\~aDP'u     J͏@$z3Q|SxaUΉhm^yxةCLI7mK6Y%/  BX}v.oƼo&Mc51!.f毦_>g}_ ~U+Ī=hUY㘡 *XQYqVYbj#f)wi }}!T׹z0:ojY%PxJg Z3VI8\x@pH[dy=tBD 9er3 y=Ts,:X³n_دH_C@@@HM*{w/'@ t_+W4~DM?#/.-&LIM{[u_{lO}0xNjvi€mt ;itӍש^>]dNh׭.\T؉u8{-iw}\M[M}ƌQGtے-*uj]ӮX]pQ̾] a&^$ޣ֨:|ε  w}{~Z9|?84A*.]z=fo'ߪ^f4E@@@@! "! $pE[x ]=k|-zd2,Vwf-hzV?vW? M-/ke:ۻI9F@M Z6kYWAvcs@@@@ C2a@(򓗵Dƍ===QBwOcwOV>Sh'DeK> |Gl@R>-J{    @zhǑV@\-pI/?Z@V` JuBsF$<" T(-9Dl    5t@`|W.y_Nnȹ1H# Qhzc9|Vl    0rFΞ##   f@@@@zZY_":    5zk@@@@-Tq    AZ,*CB@@@@` eg   IE_@@@@@ D*\   Y!@@++@'@@@@@ Z)+    u@@@@~Zo@@@@`n     5t@@@bhŊp@@@@&@@m   "Z @@@@ ha   ^ *2@@@@F]F   @V 9@@@@HBVHl   ##@@kd9*    OV,i @@@,@@+͠4     qr    JV@@@@@ [heke   w@@@@p-W#   aZ..CC@@@@`%f   QF@@@@@ RV@@@@ heU9      A@@@2#@@+3@@@@O2   (@@+E@vG@@@@ 5%    Зd@@@@"@@-   BZ @@@@ XA   ^ j2@@@@FYwF   +hLt@@@@ 7!   Y     Jݐ@@@@`h ,"    @he!   `h V@@@@@ he[E   h(8    . m@@@FPeƈ    hy@@@W ru<    Z @@@@ khemi    $)@@+I(6C@@@ ʼ9GD@@@@ J'!   @h@@@@@`Dh;E@@@dh%6     : @@@Q.@@k>     "2@@@*@@˫e\    Z֌@@@ r]0    Њ"   d=A@@@@ {!   @heC     V^G@@@ThǾ      @@@ J•     "Z.*]E@@@` mg    xOjʈ@@@-ϔ     0jh3p@@@_V׈"    @V@@@AZ#ϡ@@@@@ -H#    0C6@@@@@ 2ͱ@@@@`P     (t @@@'     h@@@ pq    D()4D@@@X5    "58   Y%@@+Ag@@@@@`.    Zq(     0|ϖ@@@@ EZ);    /@@@@h%     ZnD@@@` Eg    xL p@@@-/U     0:hκ3j@@@\!@@e     Џ~p @@@FVst@@@@H]Vꆴ   $@@k`i@@@@2&@@+c@@@xW{M~/yEEEg?u@@@@@l UO _[oKݻN6Ko_gM)>=AeͿXOϕN]ndjKj>uR^tn-Wro!)ݺqL?twnVۥN9>9܏&@R?OJ{}/˖eɓ;rt @@@@ y@`Z^}NOhG}E?đhۜkuiz,k9I %Mߦk?FʫT۫|ݺrWc'Nd-ɵu[]qZ&uV@OܤYoT+u[ le,Z>Os[MGMP˚-'t{a @U~ȌaNvrN؋u[ huG&F%պTuzn>ҤMpW_oٻrB@xnf|3fΟ?믿>\    dlA \jyK'~[_W^it3tWDU>=g&\jѾG*Jc>Niʌ|\gZ'o4}Zwtx' >#ѭfR/hܸ{j]9YNZWO^Gߖ9M`|3o|Co";\ݻ{fZ>Vֿw|Z3L0^O$?XNʻ7WO8M4IOҍw޻.k L &?*П2=$M'4p&w4=v=ʵ:% iN>\+oiᵟM.Vy]zOW&ܨx>5둱wR+j"-VK͌Vy&TҌHb֥zt+Aq}Ołr=gs܀ x{e /g; @@@@HM 0,:cVe}[Uw*mW`pnE;+C$h3Ԫڼ"Mq[sY1'Ό0|8]1KJNUQ3: 4͍jJCEΗZy%%YQտP"ǔgw?J^V]i$A_\=aUCVy!-Pmb}gCVֶj;{j:}:}uѹZ w-E2g NELskե_PRt'W{>:mgoCfк\n_V_3[ŏ; 6gjWEeO@Ȁ[nѯ먣}Cү~+      @V )Mg&@IDATF_@7 "TX1 3Qax֥4Wf&6Te?*%U \Ω*bUb_p7?frJw;gkٺ B7m. j2*5Uge[}xf0Њ٦.&$d?9[:HU"0Uae`-{F3n' Vg՜>4~Y`F$Xkg]MA~[pڪuӑw['*cL72Hnd!,]#q+ du묫*>o_ @@@@paU (@H"B&1mN@'t)FLKǂPȑ:g[mê .XPZf;gwC9K2[EkbO[u55ƶ{mǬ`5 T>p>^`Kw `Be;^Y 3+z;.XΝv1~B:T]Zp*,jpsZE2{& Z&dhVcpƎF<h-$A@,8yd(儯8!    n 嶊_p@8`Ua,S۷pf}\V^:3l%:}lU*),& h>FUpҺ蹹mu |Vb5zǐ(8ceA܇1B{vXЌlTfVs1`F &E E48t@+APU6W4W  ;f̘a}sB@@@@(@@ˍU Jp3ph2.y[ Lܾ}j'ܮtgYOCC#Tmacs %; gX j% h9cF_y,bxK:/xisV`*>e]tG/Ss`s1 Wlyֹ`I 'U7CZRGe#@"~:8sB@@@@(@@ˍU J',vA񷅂0 BLO|3Toeuuvt\. o=p)+ ShŹ Pu+qf )}|=A+~YI3޳Sƭ~%󬺘٫:'%9*L:smMVuyIP;UXZi5]g٪jϴ5jWn90 7f\ Y-n3> @@@@p-7V>#+B ߖ`5U 19?V]i^j+.dd LJ(L(ʂa8LrfY*낵9_8;hhp@(јg3 ePbb]NTjsN6^:}!0Sbf cnb-:["$[a5vS%},9ѹ@D_e    U[+G@uN'j0M3-=t"1A:LfX]mVmYn[QeqΪ^ zفox֪-8 }̚PdT:xȣ;5t]LًaCUU溁€.sw ō"ٟ]Z4``)ksMem/wm{Yspf4CGѦj;\e׽ <3@@˺?\RiYY>2ϊ  ?-'@@@@@Z9hr3A;i( ؀N(   _;&e/W*\R`ۼmc %]2x_c~9$?`R>K*ly`.\cbjJ[a:?ZMDv*޷+ M,| -{hn(e6k^1%U39@p@@@@@ rk7N13SQ8T@P0$S_kO s[(TXe[ѧLqyVHj'Ѓ.XMyt)*7겸mJʭ =KMՁefSYZ*%-N801g@@@@@@@ \e| @|ufNM7^1}{.l;v}m}Wts̱x{`c[6Xs_\F@@@@@@Q04  w,MnjuKMLnn#؎`e u'sLʾJ'~B7}fV<2A!WԞs$Jm&^l:Vj>+H@@@@@@@- 'pE/ܫvo=8C+߭ៃ++w_''NM K5IlЋ+ 6cw5YnϢU޿0{Pӷ]j ؅gt׍;!      NJF@I;/kuIq(ٷҪzU;t4 }ES3^MT      d=A@@@@@@@@ XA        d=A@@@@@@@@ XA        d=A@@@@@@@@ XA        d=A |7M]rť#      D L8Qַկ~5.! Z֠k gEp @@@@@@vH#a Yꪫ<0       @eYWr  h @xȀVkkk\      L-g@! 2;"Z@@@@@@$@@Kd,  \-@@      1b@(@@+E@vGhq@@@@@@-/U @6ʆ*p-W#     Њ"  }@@@@@@KTMƂ @@+@@\]>:      #@@+ @Rdw@@@v_:{|3f[׌73G=>̀] q4@@@`xh /# 5jΈ@  Js   #ЩӴ`Ճ8W{Nm׌kcwT͢iZ>lܣS:?jk ť؆Sg3?kՅߋNSSwNQ   @jRco@ VV@A $#  L`֧! p>=G9qY>o66Z^_=\>`V+ gsM   Z(D rQ*d B@@;+ջSRa`Æ UY\1pnKRm'5gŲ:~BZi   @@+|{3F'kZu|RMJ>O hT̽]I%MgiŀM{}hTh?E3TaTjɝИ.Je Z9~p @@H4zZ.*@@+UAG@@TkJ5o}0cfz:efzU pfZQn?e/ ZYx0_M4kR\Y2WP0\/[Z0wm@@@a 5 4Z֨.?GtJ"m   PBB&VηZd\cP7`\v5/~KΈYBGmowE?o渉~m pioQz8궷?^<]')с$)Vg{~o,pfߊ6lzU!65_". #j-O,c錌   2 *@@˭ 5t@@Q) `v`@+P5GrkhM+5KмYm;Nu3*v6Z3+}e˟3ek)fþZM8kVT8k9"陳6Kn hl-Y1[L]fFfFt>JbǼ8Uއ[@@@Th* @h.! 5h2v@@@4 D h5)f-T/U;Oh sl^͏b̐ Ys7#iVyrV븙+߲7<9+,xf{ JE@#U`^ׄ'acY>ML(t.j_,6B:ڰO y}p?3ٻF+!.   )J]@p 0Zb[@@HZ#eڵrV\|m3fK(Z[{ ȀV2Pַ$x3f Lbt@@vh/Z6(7*U:g"ۗ"h>Z& yE4Xix4mҳ X2\  IV i ⮀(@@+E@vG@@KwivVzzӸ36Tz&8SdЩr ̬^M$ xw^FW)h~=Wdj6m֯9+w r:lWۯ~+%诽'OѤ1M f=\rhmqeZhHv`p@@HUVD Њ 0hZ&c@@HZej8RM֢?Wۿ_Nh]6!P(|}PeKl2%&d"-k)Pٞ&1Fd-XR{Omk8;72=mo}㻢s'IQpBK*"   58/F 5#@@@`XКI/13V%>5h׊|9E)ń6%ӣ7R6-rX[snUڸIKQYp< \xyB3UmyȘzt+z"asG1q@@HY ` ~(@@+E@vG@@BIf9Sf9BWQa~ef;Ew/77_w̜?.Cڸ;p{_֫bF _Gw+Q {uHԕi})-ɽF'-~Vg*櫯E{k4u`JK>/JΫr,=I+^Z:[Ls\D@@ 57# 5H06GbhŊp@@2)VNգ=w7LҸ QKm lЊ]ʄ& r5؍Gr6i%uhp[cƟkYyZkRk\1*eBmM&g/jW{}>йm    E@C$@@kHl  iH{@EeSșjê(_NPzNА x@6ZxiʬE:pfrĚUgkzpzE*lGYo0syF8bjٸ[,e.8Vx&Fbm=?*շ<) =SC`h.!  @hߔ@`t g Zi@ @@@Z'|ڂrђ W rRdxu_Ih4!43Y-pf N:eZΚ> 4ZmڮYV94t<9寭ծ5ZYY+ j^DәۈZn+uM-hڪG   @hKv@-  @Rdw@@HI QѦںdzt{ZU{TYʾqھ$7] Z yinMMp,q8#v@GLpU^ #EpHf=1W͡Kk_ҋkf+znͩѮҙZ 7L&ߺD|m   @:hC6@@! ;!  @Z4gup[tj{q:M:pmoާgޣe #L;6fEÚy0U`&5h'N= \?w*=U5%2ef23d&;x5nnѶZs/o׋%}ն=ZS8%SkG *^L?e($ώ܄   I֐ SV4܀$'@@+9'B@@7 t/k>rnvX=jm7usc'^ߟ|s|]ͧ~ff1jrG4`HzN~']}n?IfN)y^'FysjNSN}"wJirb+@@@`HN @@ 9Z9      ;hN#@@= 0t @@@@@@`HN @@ 9Z9      ;hN#@@= 0t @@@@@@`HN @@ 9Z9      ;hN#@@= 0t @@@@@@`HN @@ 9Z9      ;hN#@@= 0}tkΝ[/^\M+1p_Fٴʦj 5Q6mAz lM[Pl}^e+1p_Fٴʦj 5Q6mAz lM[ʦj-/T1 QAK_ޗ2/@2oW*zߗze<#RT2/ʼy*G^e~_yTHR+z}WS9"JE/R̛rDꕊ^%ys 2:Ȁ L#f^@N!W13rA҈WxF 4E2CP4bf)4z3MQ +hze9^i@S2!@`T Uf 0CuGk8TM5|2kz pLCuڤ^g;-SP6Gk8TM5|2kz pLCuڤ^g;-UD,@@k4W#@Zh1c0ci9J c^NˁWZ3uZD˜FWƨr ƌ5B2FQ0f1z1cPQ@+-kze:-"FABBA&@@khn#/GJ~hǥ^CsHkhn#)z m^#%?R^kv\54ڋzЎK6R{QqFj/5RC;. ЗdHRVPY/Ivz% %Q,)Dݠ^IBef+K d7WPYʒB$ $TlFIvz% %Q,)Dݠ^IBef+K d7WPYʒB$ ZIB $ K#Ojܸq ݣKf+ѭ vȪM/]%>3z/3iLV`0jc<ƮyL•K4ō.SX+>P]qXӦ};kq]1x4ݷ zxz:zWvKKjkWzbԕiKȋk4ٽ +o֫S u_~꾹_҃ų5Uz^-/њ{'x^vE>xؚxG4<4+zا߭_M٥/KzE A35aU,u/x^6>5gD=z:4BK2z#כ?إtTW.w L}q*#?VzZU['eOG/V^i?#u/1fo*F@+D@ -H# 0к)y跨ejj]w<KD^{R~&\9]3\i7գ}eSNF?sMPH,;PŁߐe{r{?㫧ESDe;Բ~׾z0i&Owtv3_ޫeՖing?nR%|{NԔj߸WNwꉟUO.G4'(M5m-vcWպ\<avyEO$(;JUh65oTӮ\9J|sϣΰ~сy^o\cGwx^'4maȯ5CqP/ /tݚ2p;n{.rY.d/Dl^@Kjoko}ckZ:h.{^+]8o wM--{NE3̷;۴gk m99uY/~^5o AҁFE=4IH.z-G08zuf?ܢη߯' 3oz^b-8zz)#Lt}FO9)k]'p^vϽA-o9Py._{\+gc<ΟxMEgTh"˜Ai`D iZ knRU#;oi$M9Q/|r2e[w_+=Aq[4xopVxdWOZ ̘uZ^ҁf*}wٓ ji|-.]&uY9W/5I<9O7<xpuִ;MOΛmf)q MWIN]ݹʽNj>jO!C@+g"k΄g\ ?7lf̳g,ڦ3[ %ʚ<ͬY̬Y1eQj6)Z03Gs|u'5g<1_' $ Uj}lV.4_lЬL^W@5C_XvT3H9Ҽ`~fMOKRzв8! >KZBQ*0Zn0Ǿz`!iB#LhH{!5[-|v[}\~zI=͵:ofbti-' `,W/WQf||{[5gNZa,9Sjӝ3kRΖ}Z6Q3C@aٝhzy=f %nti1ʂGP]ћk[Sθ𖁰AO /Vi~onf:ړҙL(#_BB$ܯ|f^qȩyzz<$ɞ39`yh&9[vy|RvE*/gɞ/<:O֘zmڣo'>0~cO?ܛ#5{{^KKS̗ Qa䓏]uiO3j_x\wzdҒ\wI@U/:. "Enž#Sc]s/ Y}4ߒek^}c hݰkiԷoz^t MCu5ˡnܫr-UoCwiھds~z^ե3x^tɩMY~rk,h.ۋ/fʦRr} -O8y*[w/앙{wloQZ3O]ʩ5ͪmZ)~QQjnzy^uzY}V\lrm'vk ?4Xl*sQ/Ly .ց35_0u$"52M}Y(sTuD ϺP|y1Fe>co֤/m_nQպ9a G^ԽFsk{|#uB@̠Y8FfheGV\fz^'hB\=5w cui}ML-< _ bZ2[/}jvi3`EP(n^T´Y _z]n֓_gres|f4"B@+R @R7 hn)NNejj]Zʧfܶz3OQvvw^-s9^l̬ zZ4uj3rX\s2kz:uل#'0)Rsb1Cz}YoMYP[Q2''\$xVz^0qA[yi:ܣ+g tƝ ;KwziΧ>5CnX/5ll.z|#]"'D@+~=0"J*7kTaO7ꉥnq5] /oуg&}i3hmD8eŋ4)MRl^Xˮړ=O g{c>ohڗkMԚ؁-iZ~߃S/^+7ѬרqU_zmE}s\ Fw@ he} d;Z=zj޼0^}SP0;_Wڷ܄flДiņ`ڮQode+7^>mo r緄=W/Hibg1ӻbUjcC7Wx^+ѯ}}zVNԇ*GUG5:I}[+Sl ӲM/i͒vJ+"eXvTyn`bwſ m2w>/%wʆ[Oj_fq_tpr&EXa|95 𓯢4/6˔_qնʭ*u1 /+fHQO,ׂߛ=Pe7ԮQ[4TSSf=f}w~)ziz|øЊ#ɚ+.7T)Roq{E@+k@v$6[-RNjg1Eb#sؽ6T8* $ ,M1(||l5Kow҉j?R>eL{6hz0pZ7Юꥈk_85>%ffPϨEiܥ'O+hz{=On'޼&9<^ωOG }]/ZNpeRK@IDAT#{t]v:m~uWu#/3|>.4E| Qո嚴 3#+2ȑ hhZӌp?-g\Ym/j_3S\E4gi5#7 yFufUw2{,Sv9Yn~d~,vuI3Kg룟WT/?m9ٱ'uZYCOZi7etR\VOCV/vr/q>}2w\oJ˯ۡy ]Z}o6[43uV`84ZrxNd9pHdWN>zϷG}-Kzcsw2sdV{\pqN#1ti-eWdvVflնy}KǿWKUc2q܀Vm-"S+ڈ3(h8+$2uO2?)/Z2REV@ ^Sk[kVPJYx[BI+fa}`n %TU*dRakVPJYx[BI+fa}`n %T( D@($ '/0'%I$8)xJII%+ N ^bRpRtJ$]b%+'%I$8)xJII%+ N ^bRpRtJ$]"K h [@-F0C˫Wϑ̗W#ԟ#o/F3_?G2_^?fR=d|y{|y5RJ9j1s!- h ߐ@` JWz WobK z^[H|@z/+ҫ|1_%^+拀VzE sD@ ht1 )J o1_1 )J o1_1 )J o1_1 )J o h=H;Zi7etRMVH0O]eRmFJjWTaU+f$y>vJI+O]eRmFJjWTaU+f$y>vVAt 3H@`q @@@@@@@`,ڨ &3jASٳ#X#U!      0@ h};vPggw       0v8*@-@@kb?G@@@@@@@@Q 5T        [ĞF        (@@kq@@@@@@@@&=@@@@@@@@FQ(R5        LlZ{=        Qĥj@@@@@@@@&3z@@@@@@@@EZK         0hMg i&٪M<Ϟ(kŚ1"z[uV,9ݪO9sf_H')W@@@`|`{FF@@@&@@khn܅ ۵RK>oE5mڴtFU[5{jt|Rtדj-Q7b=YQs']   0&k֬ OI{4    F`( 8 }|*EI]e[.zbԷVv+P\wWWA_on#NW *ճmhq@@@k ώAk4    N( 74iR6S9%jy,O6>uP~7^un Z'#vRS{utz-`7PM-X&̮   0bF@@@@`h*U":uj_jҫ-oISZs<}Bu%XKUZ<׻m]V;GV,}S{Q}&8ި/ z̽OÚ/q`;tToQS4{}ZĽ̘뭳.Urq%?Kgޫ++uTmߨSW/ip1>B}xZ:3:lλ|9߫=oeee f< j~14]OUWV ӇuՐ% ?n4ު{u:cZpƐޜؓ'z;Ba<-7@Vч5s]6AnD{jxz?qmp@@iZ#-J}    0FR@LbٙOeNWGvߥ7+#PTmZPSHŵmڸR2w۵RK>,ٶl[yl mPovjv{S;_doBUN#FFN=Xŗk =m8c Пhu-LLUZׇ%k- -8| io!ֵEWn_^aW!8rԞiD3Wy}nqVN[DUytIݸOY Ԛ /yZ Md_SkNa֕Ze u| V{giogdG-%@@@ 5Ԉ   #'@@k, CtJ2h]h'Ӹ/OsY}Q{M[7ho8 (ՁŦNi֛\Q։ݷgsVE<[|`?١N\=6G-1!&ۨ&_%RuIWYZn_Vwx 'km%d8WW}wÖp+_ =;+8ŎӟS;-N=BVTnN1NMˁ|}񓺠N;>oUBSOާyWKUj;S0'}L7e-Q0gwܪWֽ&k˯TC L>+>kʙ`/(_Xk6XȯV-n@@@ w"   }cZ@ / jJUUZ_U[5{.'ʊ-sopzD7;:rV_X7GQt˜U9&fիGQ F4t}sPKV2CxЊUg;W9#  !@@k4T@@@FJHIR Ч@ݖu`YmH_r͝T:z'i|: oAhWUХ[ǤNԕsTl3y:ޢVxZK앵|h7pT뉃zlwvUʽӂ+* M(g Ѥ#r>t͞ gu5;zպ8ZT۵dP ՁUV UfeP&gU1*[kuL .)Њ̑zZ+ŜO[Բ-f-ϊ[ҫSm\z܋Zz/+nWpl?{qP8"7{>/T|-8[ig5j39)^    #hA@@@! 7!8]欲W|_Y7,\U  t|"D9ujp//^o3H-7"*W&д &؉EV8D]J&Roos[Oٟ\?N55u:\P0TYV\˔4[f4?]Eum!m!Vm"78fs:cZ9mId[EOP /sNݥ-7[b}N|X@+J[eQ4mYxհnk   0,Zf@@@eZ L #xD=ڲ3>pf}x5'j3*8S+b mxZ1;1FzlAEEJx"Ƈ։ ohٜ^; lKXmU̻O1h)qەc7<wE%m[}"z=KiGkL Tv[dKPmڵr6hůw{\A@@FRHjR   >@_^n=N1+b}w5^I9zLV:e.:T7`ei#Ċ .S&0UyE]ՋU+94{M9Y==]f,͘SQL_xZmfH]/(UqާtjT 6箈e'w<1 솠bWJ:q=lH@lV˳" ^]]ʚa4GfYR:hE#!e6zׇ#5L   00Zs   qUHfۺ̶uikBEӬ>4]}G:旛-ݒAkV^%xCy䆥 *mő+ @%ږɼs0A/唫뱛rSNy{tL`ꑄt/ocѻibym2hTV{W:mqs8gìSOjɼ5Jt=yZ%^m*~C=㝳noh eQC6mL[ uH܇   Y֐@@@B 0VOw@URWa 9ae͹z'Xj)|Y-p8jcZu%sӒl9}T* nY]V]E[OpN=v݅VKW]*/Z]6;-pGm[kIwLLk·u8 jmV1Y% VGWRoF M@ U YuN,OnY}]uVig|ސԗuIJr8L_ź`}γU2[ sa3yY#V   c!p5X?#jJCvI+jKr+j|h\cXgrT8t?g\sfnsuLzZ ق}˪, ??) @@@!1LF@}y~0 i#&xt%jtC>\FO|ʂC?$uGN&Yp4PN0T`E=@Ga>Ś8͵Rq,)ܩ5Pz*blN{*'؏;j`\+m Ć˙ޒIZ`]i(B=mV~LiNa۬ך˜~U7T9Ĭeeӡ|9VyMUO&Vc(݀Vq+hDo[U 3\vV*RSۅA jTyf}͉|(Z%݀VA(&[nW{[zLٚwYuw3Tv)> _fѼW-;i4{ %eΡѫvgY3CUvM=uA4xLd]tM]LS4o#&TSojq{'Ѽ@*kN|O6=cT{h@@]k gtحi `:'Mdj>>wjMӴB2-m\i2n3=}Le-ހ191>uP~7^unLK6.v;'v&߆S   dLMƂ@VI]#T52ʛ|LpZ7pfݍO:ljTiNݍuZӓ5lR`Lt@@T@]Ǟ9YiZɿNb7,(3aO륃?uOPOߨEO Ut4#g⸀Vi}Yxjf_@|j潹GY {Bavz{|be7ꩊuTmߨ;ua~xx,*uZ('Oda(Һ6ݾة]WNݦP%ZA~ "[Q 'ǧ{M%]NU\.|_5UZ}T|bv&蛯Bm[9nYӃ\D}P\</ݩ[B)G   GękF Ч@oh~|*ݮ\@@@ (}ByY %:|68M8li82e}9ڼntݰ=rԬu 5%Z-+ci7[TfTu6l|WП@V02zSAɽzT]RVNi})җp *,Sfzd):}BwG/HM!   92g.  0z;|-)~-Y=#  @Z'i:{{My핮BSOޣy7=|(ց+%֪jyڎǴ8;lg &o.͹P}r*+eظ?OPe~kN_foѪ{ԕY-1-}10^9b2]Z[\J@+ބ3    P(    )@So &˯k۪{P]Y]us?rMi 9yViUfj(5IWѾz?o>bRZj]v?Xu Uyz{{IGt̫2v ĕSZoVZtdJ]dKB0-@@@+@@kbG@@@@H@ )k!t:[F¥ h՚VК䆤j۴p0A@Ee+b<}oӊnۉ1tV 6V @@@Z#H-     0N hU6.NS7冥<Ň*8Ԣm+=݊Oܪ9כ6#A>Gλ7qPPѤmkfhE,8B@@@`h,"     $lq(1+EEudje;šb@mIK6{0mFXv qu'c.af̰f쫞Иh@@@ 5ԍ    .0šEjڤ5N?s,kҧhei^>b6͍R9Ι~Zz% 쬓rJcX&hzZkv   #@@k|i@@@@FHzdf_m4/akhI6uЮ/tYJB5߬# &艿gORo)k_̚XNL@a f,`Nkan?l䪾kLe-   )@@kǝV@@@@@@@@@`К@@@@@@@@@`|h;"         5&!"        wZE@@@@@@@@ @@kL2CD@@@@@@@@ 5>        @d        #@@k|i@@@2Z[GH|]s-ZуU_jZhVfv>_7,\Z *մ=3YXmՑ^~Z |v䋿3zGibŚGOԯO;C>] xZ/y\F=Uwjt4#&}ZGy/o^^x>}J]lUj=yD?yYM2[ nX:?HS'4)/Urn }g2KOѼ7KCh/ k,c8vkBIv>_ٳS4}t]bfyoOQ]yf>d3,eMWuE[UG~\7hNGo[z֔Z`WyT7M[&T䗆moUg,͈<٢޸zO*oB 7h--?;iZ_a2W&VkrȧZut4nUZfdIQ6    */@@@@E> 'S*Vš2[55YE5m;jzu򭒢\]_AH!? 3ȫ399a'I=>r( Es^qU1$',] Y)AY w/ʀzAǪ,7y* >C~읨Ve٪뭆z04&_[[7nҰ}Y) |s+g-Ug儝kDmq@竦(w    @J(%zA'@@@@j:d[9~'c[ &$&4V\hՕDVEeU %BG~:TQj)u%1BX= VNY%eVeeU\) 7Zd-*5e+ PZ1 L뭞/*)*+ʭBY)D’rPeSdJA O8._l*mwoP^( 2=m(#KUY-0̪oL_W|м& hu:qUi"f{     0F@@@@ 3V"¨e1GlÖѬ'ҿ`~}_T_V}mC06Vl.*)*r~73쉌)6emY)?]]{ݾg],~ȊOWrzQ[ex?_ROG0fcV%kJrJ5*@$`fƖ ` hE^,6ي'FluVlW7 k    0>ǝV@@@@ V"⸤Zh;7`@UO޸KLXM[5WK0uIi:db#@nJrBq޶#/ނ0tzpK6-&<-q%p3xQTQ1pڎغ,wL6A&奯j2ϯaI]*VP렧*)̷~, *+ _h6    X +iA@@@HX.>@IDATvA`KSmѪ' eutuY]:_;:ڬvz;v/khŹD.CuV2Y+ҧH{+7ysVrF{wCUzUGm8[ڮ}u4Y9pXh@~U ƾGs0UЊ}f-WG9z˚m-ZD;ڥ*}1+{7    x }@@@Hy7 P@P hXPrJ3=uVnp* uZzfUWUr-oH00n(Z]I|("W(5/ EUWP[qxi?dFo_\nշ y/oބל\o /^krV2h 2 Mf 󇟱fsV톼)[@@@@`h<"   @8_~aVW$aEF RNZ1Kj ɴl8a`mӔX z:EF)/Ë@m/vBB^Ig@WGTj5+uX J19qy 2!wk?~/nkVN]VCu"0**6s]rbb.tsȉYEԂnL W)rkktUXԕLL8ЙȖ9őY[U?Ǫc{@@@@`h$    @ 9[W±UΖx57 8`}bFIJ* <*{\9u.&(.;.YqOԦZ{FSxp{phP^Н(35EpS~0r<}ZeWBs׾pE-ЕmG(5ȫsFJ["]SCE~x?PZ9B@@@[x#   @ Ԙ"@ C2ހIh0=& 8܀VdV BT=VpM0D+bD]VuiA\Y_NUQ0ؼItQW $*ٺ[?_${sCG1uV -_(e^QiC]/ /j/e&PT ۬bPvD'Bv.v)og;CO̗cDP;5]W}eIdխ%uD@@@H snB@@@jomQWٳfhR_jmoS);9kf͘W;ݩNdeϜpj3Ξ}pnձBf۴ѣʚ:ՌQy\G̭&$KgT2vZ9Sw6uԚ:P]nSWͺ8Tb07pvuvZ&a3i7ώM6NvbGUlc'ֱ >3 aI@@@@`h-5#   Ne-\?qJqv% jܟk7-&Ye_k;6ZUWmrճF˒dnyv2LbݍuZ35l|`    !2d"    ڿ'zSSYro]_++jɜU/.NmIQ6ygZ*Zh?5wG}~/;Z:tVLF@@@4 fFw@@@@FF~mYTk{c'ILM-fYަ8F@@@pZ> @@@@ @O77[&~򅚕=7uwIS5#{< K6~!   ~@@@@@@@@&3p@@@@@@@@mZ-L         0ahMةg         0F[@@@@ evءx@)G:   X dgg瞱l@ @@@@ ig%   CZr  ZJ    sNZN"   0eudZ    xZy[:   -掏K 0lZ&@@@HWZ:s@@@`4h*u"-@@@&3p@@@p Z#H    s5   WjEh     5a#  $ S D@@@@ F@@@FGR+ @@g@@@& ; @@@ (BF@ R   7z /zYikgu .Ggkg۽jy5ɽ`]@^VO!@ C@@@@`h1- @W~󧴩i"mݹw55YG+dCy㢬pkkSj=[ԙ`ь鳴ׅ%Ky{9:>󶺽U%9u.h%@JXIL4Zm/   \ @P~ާK?#.?{Y6 ( oy7^'nfm5>]rmI^Z~}_:{c@RZVJOC4 ƓG@@@@`x '˾qyE@k~ofM~~{M,aHkut;jC@9 @J# a2lB    \( B4m;u%_@G.ϊOW_pM_w>|QDҙ1eߣK%h#R(2+W=~/UQz37`U1+W@o^cqN/.zz940~OR]>I@HmZ=?WV=G@@@a & #KtݶP5Dojۃ]:|p}"y@˽ecn6at;wz:~PV5BW9@ he,2HEZ8+ @@@D֘0 H Įe+ٮ.p8\d8!ESQV;]z^AK Muz :]`^]yA'8cjLEk0URWz$|+Pժ?Lnnwߧ?*7u!Ap@t 3G@ h ?@@@5ZFK $҉ǵf%Vr/-~["_@~)}BuQhYcV`;7x4nxGBQJl^H&uiB[ ['쟏߯0c^瘝> Sr3u+G&vҮ}#-5^ܓONhxɻj|jNT 7onZߋ6-y#Qi^:~slv@H)Z)5t2HVM&CA@@@ @[ :HzVZwXiཌྷwMpr|`6l _YnݷW@GfkJ^CC}/ҋBg,?7_|@w8 QZ&#   Wĝ{FJctC hџ(p8`rRM_wXLk{ߧF͹vT/ J\J}xoߡo,\N v|Bwo,LY 'q @J Js x:    O@`VxJ@wUrHG a:Z*WrBs5pۯ-woq8:}E]=\\qըu_6l}Mss @* J١o y;    Kְ@`fMO?7{W/4YTgfQ ?FU}oчӐ؂MUd{LعQc^}WL;Qm Z^ۻd۵{۴Pm阬^! 0Fߘ@`b КΨ@@@@1@H7{̪Sz+td]c3Mp(| k~׼&VӋOj? lV/,Sˉǵn<?EQc^r>>~FF>Lp:ڳmT)@Mָ0d `   } ۆ+ )+CDe8~yߵߊ eBHղ Y DmO } PKW86\:h2iJg/.gN! 0nƍ@ he3<@@@[V6\AHQwu:tSLzW?=S_y-|jm>~v-{&b╭LOܯPm1jtngUى 75:z鎿W/ҋoInc:m-)[@RNVM B  !0@@@@`o /pF#W,U?^+El'l~ 2F%?~5W޷]{B,}鮇-'Ҹztݮbyfċ usw|dcxg*g?ql@ “C@ hy@@@q/ Įewҕz+8;Hr;ץv=c"g٧G"eSmŭhq[_lV|է܂Qn9%=|3_]kEH l`Vë]ܦ]N{Z}FKVz+u\ gn  0ƕ@ he24@@@H.@@+W@RW v&˗T`4=Q[{*~V mfu4uZPQfvsET5}klO<0tT<#M<;~my9y^5mb_gEvu]ttȎ{'|cڨիk~-3yŶ.RF! `sZ6/C rl8   *@@+UAGhvo^cNsr/XO_ [\Bp~JYYZjI+c4^jDZmG;|O}g`KW|ͽFzzf@+@@˾g lZήG@@@hǩ V]RNgٝuIO{&jnVWtV \ST]tFt^V?ޭ[5gdz.uRmV?)o){NԄwS@8QN,"@K ?@@@,@@#,(r4 *@@Y8X@@@@aZ @h5>@)@@˙u `Z=D@@@$@@, @ " @ZVpM&@@ɨ   M*B@$@@Iբ $'@@+9'BWq<   FkJ@@A`ˆ|y w/5C/$ SV:5i  ЊZ @@@ZVp VZlAgOc  ޜ+"@ 2(@@@@ (6T#u:WXu z%LQp/WXu z%L@w \Zέ=G@@@hħoW|o7\cWcԚ|2jZCϾ1W^QksW7ԫ1jM7 2h:3J@@@H @@+7}s~6%] P33% K8z9N^R33% KC@[;z   ) JO &Or+E&>z51x^)6ԫSJO^M WMt:&2 h3`@@@  K8/tQp/WXu z%LQp/WXu z%L@w \Zέ=G@@@hħNx9"`N>wvL~@]qFFc^ⓚwJ˽a--֊`vg s%ڼ%iꎪ1dݻKURUclڵn;[bb܏|N{%_e7-^U%۪X Vn>[eK0'o377kTa9@c.s2r{z{PϿn{+ƫ/y:瘣rMϽU76msqv[Va[Vƕ@]_F    82u#/RiIVkbu[u-6rP(q iMNBg;麗6ar`U}]̶ctW! kg'+`{볃ոdis Iӵ嶿-^:~X?l항mw_a*V/oy/X~s߫sR"f۽P3V^4k@r ~]}#o̗YwhOIwY^̚1f5uiE)>UcfU& ˿cOLLsμ3Z+Uؽ^ަ@dyNz^%Q-zT{n}S}?W٦!?1Ԓ08|ɽRbm&I9^:&?G,su?e+{hyt6wGg |o}ns;>sjCk_ hiG#@@+=   pL@+G5>uTϘS֊p?-sכ2w_ /sf}c/85++6.c=E4_ }}4`7NW.{k6yf;M G 9+a<ةdNW}C=G%+ѡ,f^of@<bO;ߗTȬ"fg+uޙvJG3.7-?\kʻO3ϗшh`K{֠Sa7J{O騙Kgsu]. z~fG_Y3fiy l ۳K/.c?VA0 lZ=yZ`fК{#sg@E\TL   '඀V0,}x{҇ +@Xo('}s8EL Z7LK3mE-s9ΒI \rV{'Kȼ>bfGpύU~WM6*)Q Bzny2g(9uOϵV?(MdU=YOCff'_QT]nc^?-sR"Qcﯸe>شRW{U4ܘ4L3]3U3"6 вD@^A   pg@KfΠ0uMαKe 4|HRjX_=5xYJ<.ܫϹf.{>ZYRr~k!K/[ZmZs-0K} +?AOWhխ[@tJ"m    #:X̠c}7fn1]! oܱh{ds4K|w{@m8G'}a?~FTlVq@|VʺC;o8gB3h}K;G~նK?.-^vip:?Gbr^֞:^~3[hAʻ!B5{z<;^{ۋ93%Ӏ;,>8^>xoToa.1%WvFGvg>'-Ϸ0T3?jpyp-ז!   z,]8ek`L#w_ߌ$NB/•J4 Eźbcݹ'j_%woOvU<`'{ s_-6N8[ukOսۗuNuƊZiz|>5˄ݧ u>_|nlClI7n 6s?5k'+jϰUX;~g=n.u^v=hWNW|xT?H}fSzo/3$3Y˟;iϞf~ ?}ܚ'Z'Ζ@e jg   hwj kS&~Qsuo2k}qYJl q:}WSnfXZ3 7,PcO<_#yFlРlT:]qR dוֹ,#1дz1EOFXd?;4>!Mc_<6? e1Ie% ^٬mwOuW_c'x}k&D Q%3^yGzнyz%X\nYr֛)ϱKNp)jIJv򓅦& fLJrRJ[.Z1uZ2<?|]=mBtwfܤqw:^AKj矟[^f5'=Tj _M+5uG}]=[+eItCֶ-aKW>>o#43CߌTI3 2 uQ?]}TVo:3z5t?$u}vPOOj[=}Tsp,5cн=\r[m6E+}xnZv'zkE ꣓WKBW}?9^QË]Ɗ}j f i_T~9yVmax`-q^I -{ ԝk8gjW҄ctWj_ h57WC#@@Ԛ"   @Z4V}ד `>LWXfI8f}/t"_`8$ZmxP/sXMO%n/ۼqRh/woy鶟=e%asR 17ܤ)>$#ܨiWe&-!/ԫLO$z =[[7r܀~?=avGh~[-?:So ];ꕖ냿zzI ڿ]óZ3ʘ{c[Ch١ (@@ˍUeL    3ZI M5:m!{=.أ1<>ա꣪6#CZV:ƙzԝtuJzGky_Щ>l{I:gfvUzΨصUث#O9KdkVxbJߪ#:k(G@@@@ mFIC    -]@Lute){S&U@=Pw_+|LZW5;ྛ5aii뱱e91 5*Z0AWMxQ_2lA@@@8NZ     $8in˺Uk%Z5j>FELkRYGR@FޙQK{Q3T<{^ vћzH~]95~VrGտVV$HQħ@@@@ EZ)r:    @ٵZ~O>(ҵYZ=i5{VhHh/Tȼ5ezhș}QeeYSg2jͼ3WYsTշwigƣ#BA,/+'_TL `     fZi9@@@p@6U;CR/+գ]Env:|<]J{txc:4]<{Uؠ |[C6|λܴa}E+ ֪jMVب5zC[}T;S_G y1_oCGC.\!9ci|HeRRkk+_M[ 1:C -٫s.7۽*Z+VFF9jhDh׳j'_EctVRk>n5S[% hUאNW^Z#z[aҦw*k*tskc T𦘰j6Ό,翚_Ri>Kclq7.n>Sl3UuDz,8ȿxb    @jRl@@@pi]5ۄ|#'/_~K_詡%#2ˑl_m/:P8uߛ"39S#b2%hJ3 OFxtknNͺn,zU`ٽGԨhH]5Lfydy5ۇzr?  UY[7噯uk6-6-F:ހ,OeHX\budfd &jmm!/ժ_C:i-mhuݨ_z~HJwU<͌fx]     4NA@@@$U=Gz>2G9˶~V{lMJvi=_A/8;UH@r5Z16C>i9fFUfFH+.9Ue>[TPκFC͒ CAuTek(ajkͲ1l hk|h<-29ߓH^suT[tgȌX#gΠ\p`LKڱbj 6uNFtPѫګ]~P@KU4 ?Bܸ`L0{C} ~rU/ۿ7ߊ )L%*IjYF1kbJz6@@@@     ,eK>쬍ngBT'.wW&/?g-{|u/'p`=:o_WcPr_W01+N">Q[nҷ(4%{|]$WT} 4Z]r o573G\ =dVr]TZg-lr T@{GGv_n/fFѾ{, Mپuľ=}ߚxdk˕>Oq9_g]>L,^#   Q@il@@@@D2ʞ+-FJ "Efѐ?5pK(SY( R֜PPri{Ky0T[7?'2=愃H&4 zL&܉o;WV"U-R7@=c* LmQI87Y y|šQqAZ>_/?3kmUVW*˶D ,TH#O5 J1qY%p,;V-suyaoyY dGff *vS093 - fp M4>3߷,H//K] _@@@@R`CS<@@@@VLye*-Y0hMkrf+y5݆imx̱YZ ŷk46;ziԴm}kCUGGO>\7W;kgYUPi5lv:Ysݵ&p%wWUM@+fCjh u»̲  $8/e{ZYr={w=-6FYF~,Q?噯.hi .q?z 9%nk;DWN~hl˫ewҌ3;^l/^UU?stf,b@Yj[S3Cێ"vl??ii+ؾOVX~%kVǪq1FZ:i>[ bYQQ[e)lY=TDCW6\ZֶVn]G4i"   4RV#8 @@@p@oɍ*Rc*8ʻCc[`){:?80X>3XES6АvX>%s,o;>!3"@@@@Q&:   xWwMuQ]=F{44Sm4~WE٥&r;2myKCzH;6*NgFsݨ         `Z6(]@@@@@@@@@w rg]        @s>A?         :Z+)B@@@@@@@@вK%        NJʀ@@@@@@@@@.R          庒2 @@@@@@@@-T~         h @@@@@@@@"@@.         :Z+)B@@@@@@@@вK%        NJʀ@@@@@@@@@.R          庒2 @@@@@@@@-T~         h @@@@@@@@"@@.         :Z+)B@@@@@@@@вK%        NJʀ@@@@@@@@@.R          庒2 @@@@@@@@-T~         h @@@@@@@@"@@.         :Z+)B@@@@@@@@вK%        NJʀ@@@@@@@@@.R          庒2 @@@@@@@@-T~         h @@@@@@@@"@@.         :Z+)B@@@@@@@@вK%        NJʀ@@@@@@@@@.R          庒2 @@@@@@@@-T~         h ZGՏcUUU5       @v֤I}ZD@ (о}{Y|@@@@@@@p?UYYj4'"A}sP        |>_GG@t]v       .ѣGd"@^VӛsE@@@@@@@N@@ WFFXT=d-.P7V=;uMi5;V+oqOVyS]KFa) J@@@@@@@@3kv6&*/f6jx*\"[ŕ4LD Tm\E/W$-@@+i*D@@@@@@@1ZNN @@+ @@@@@@@pZ/.8UOzJO}cpZ϶U@J1,V*6 G%/@@+y+D@@@@@@@)6 hevUi#DhHVC:kƨq   bQ9UVf^9)j>-Ԏ?klsJ?nt1=CGn=CN uh^ONiFr硡7/+@@@\VU.MI3 ЪO X9C@@)u}vk5?_ݪStDrF_СqmrVZulz~h78hy>yl>4pvZ~9mlv?    Nz۪BḂdzaύ^W ~Ӗ" `   @eM@}w,>?JL@k hs^dk8e|tT2Ѻ)8ȁ-慵xY}쀖F/֖{ }ZN[+˙    Uo8Pu?ZnO>YON;mxzm hVklxNzkt`?xHJfLNWnԱ]Jp즚ڳgj5ZZN?[ݺdFZܻ< };3;,@@+.MYVʄ4  J95 Z&03 05.fc?9k] hPFf`_o[CZeufTk(uDuآ   h"-ZH-Uiܾ9kwvUiŤ;R{3Wqey^UUS+}MMQV [gԄK-[c_zr4e]ڿG쎸w5[۞z7{*ZfҮ +kҵqgfi̜4qYs DykڸZE(wD}gMmN@+Ë4 J$   .y9YyA=*ok [-I4tC;ZZ~ ߪU+> ZhuHtipEG_?Oۉ    '@@+l_C 'ܬqO X9'XK쨻eg=/U\Jۥrn{r4j^}d׋5a[0 J/[۟I̧u`YuyٲLpfݝ5{4kjgtt'_EcAF^!8@@h  ͿS{ Z:'A^ݬQ^k]ZZXkD5Zz   ZZzwi怞~٬'!pf <ոU hod@+xn;U4,k̸䡄!:-3VjC`PZ O@f C6,YƅsvG֙h Uׄ-   @K \fZϟȀQ>:M;} Wӭ`U}jt@Ou -/;:Tnpu{۴SNRu)[=KyYթgs6#aU~4tҩY6L=әp&_]u"eSn15X>ɍ-Hc?ѱ @@@% *URmfJbŻ d@˻kZ5I<;d&\-:J.ԖWUUjmUFM,kYG/$oVhVUS97x)&joiHqЀqۥ1˷k`,3&~xw5GXU*Z}]5\Rx19i^MS#~,0(f[Q<;;n#z%ffᓣZ~_Ȓ3L@kt8v4K~$xp|)97y_>#|N @@@ (Wϒs쩯csPMK S%4̎Kr0jj?`t ϫ5К_Ri b{szݲc'ʞ=ƫk\ַ5;^u}gcv2-_Uo@+gʖܣE3X\Z˷WkDdމÁsu֕4 ]-v>ګsGʩb!๑ i   KZ_c_{fG8tzFk =ٖ)&/ߨ Ϋ]ᆳ=|3 ݑu_TgK/r]TqO+/0$[~mzy<]Z!wf,Я% 3*URzu¥ks{c.3Y;\|Y/W.cD "L3+?]Q_[wHɽ825tUJvYO_mk4k-;TczG-U/տ/T? ؈   blj{`LM +k%l㙯ʢaD27K(Z s_,Q@sٳbl6'2'Ʒ]:1;+ӔUVJ_l*ʘX";={gF-^8cZU9%Fnƨq   ^Onc [-(zAٽCB+:;Kߜ\?}i fi[,@Gyw=%.MRҐFq jJZڠF.r%\iSŭdsNrP+gزnD(T&Q]97#I;;IHHs>cܹu~qܨf];!|HGdƏ{}ɈSaT3Ohќw&R4zz v5j~x\f=1nΙ 54@ @1hz1pTFO=3;>=SѲ>8(rMV؛?Чʓ{=_7qʌKm{}K9> ʠ:g8L 76eycКSc2 f8 LV2C @|ƛ1N[PT*V Ԩy]){Le%P E ?3o71挙rosB[*QE`u82cfISpxo~l*ĂZ^!U Ͷ-emZWǪLLߺ_Wf->wTZ05gcFXO&РUء~ U*jyjq3ϙg @ 0aZqL)oM ZRA0O>FU؎׺ީ?xyd75u(ǿRphn,nO7XOG~u4:~UzgkĠ[G$AkDD@ @`Zo!&|`P7fjfPRx#ߩ%:q(G~*-WpsQLT3Wk)7lVk%Y/qBݪ]5MW.6bCOTRʍWxΙ7NP~Hu?vL^2SX 6<'#0̤O퓯PJaj8$k@ T>~?sx:L[m4B!@ @`"Gt~^) I{S ԴB5-4yn~ӗBez#y0huczﺯxIE{M;w׈+t3kQQpvjc3cbKm|c֛1yz̴gdt'Ak @xjnLo0RE qz9?UzJ|ۭ%G?ڮQWgᆢΟy{Wǜ<:۪11 Tu0:բPMƠQ7USY;a\c =q0v @ dA+\Tetx=ճ)O7^MIv67#ݠ5X*%VKD-a @ @BfFl}zCKJ ǫAyO轋UZD=T LlySonh >&'~aTAL&~Cpjo' ^tp1iLF6 7P0X_> A떵 Z  @ %06Hr:CKP&O#Z<_o3[3=(v)/c1dkc3=aY cgKwT"so fc $1m,sLqng_ūl;k:n(km>әJv5hAqu| Dܧѓ @)ioczWWSʿUzSo}[4u=QOKz{ueAk}y~M_+o}1i Z ϿBϛmgT8; A+Ά[#Aָq @d@IDAT W L!L%۟ƴVe:=խHe* ?5pCQǃʼa0:7``ښ+57~l@ӿHo>}(?GaTo?AkS(6oӎ녁pwh>A_-ҵ嚙~6)Y=cRæPu;t+=1?_?ѳ&ؠujuХW"<3/vu:" Z#"b@ @ӊ،7Ф1$ej/\Ӛ"ce\Gn(xUL.Vr$@:)\;wtƢ<曰aÂiJEךԕhՂF*Si2&x)-9%[koc61Ǡ532m=;(s+eG@ @p6hl:|FcY,5h2OJuLR]㻦QUyT6 ?KmW^0h%1X `+1 @ 6oұHc vibf8,mҾ5 ' KT+-e6g?+jưO7RWZ=So1[g%׺_&οIueI]ji&Ҡu7{>jZy|d^E.oa9څm%]+FomkLAK /=Y.e"dV:NZ4+vwSUy%UNBc@>T,OA;~1Rr4Sn_5`8L&*ø8sLh>)ܧinVC @4!0 Z+ADg͙4oI尝Ֆ&Gj-?tc?FӺ>Z=Fhm Z&݆T4+}o[Ck~fz :{`JF15F`@ @q][U=j|*?ku+4sm6>M'"jjM J:?/ DATa3հ!gTn-2;UcҮ~}䢶"é9n]{?LZ @2p4sحse{n([~gL`Gn>@ @NU/GҏPCno~hqS]>эie}gMA+\vAzEOfdLZ Sr#2:~wjɺ{&' " Z @ @`}zbysRٮbM`CDqzoYogȝ|5ǿ_4Bowj&$yԵ+C @X Lkͮ]]Y>}(_u?hgSYO~mZ,)qn 4@cסU߭UwjԷvwyK,ت<KE?ٯَ>g/},7tنpC2_yTO]ݿ_^uI58Lӱ5vf@ @ @ @vڠe,:Ɵݥi "ݲxy> ԃ=W(U[=<`Tɗj_hk%=YCiݛ5~e}ؙojcBڲkgt;|]W:WuVXޑ&^ϘF&AkdF@ @ @ @@5gn豻Oe: }>}a&3|V"&]{UlJWIzN>Z׋go<dž;gїRgSNӻWwߜ\7+ͮfjH1I#Ğ8,_^8A ' On u`qĊ֟m3=j ]^ڣ3oKF$AkDD@ @ @ @SHܣeuWwܭ~jbEqS]^~_Hz~?/{fdgo'?+?}wܧzwdmܑH#6t/v{u%w{ q#n@ @ @ @և iD4B @ @ @!AkHMC!l'A+wTZ ?>e%JY^idJY^idJY^idJY^idJY^idJY^idJY^idJY^idJYA+-6@\&xCT? k O%kaN©k O%kaN©k O%kaN©k O%kaN©k O%kaN©k O%kaN©k O%kaN©k O%kaN©k O%0hM LN@`<0h# ]瞛s1@1#kJ5fdSzzM)1_ƌlJ@)?拣טM5|q3)=/^cF6הkȦRc8zٔ^SG1#0hM)~.@ AV KtxlP)#z%Xذ^6,lXB/TJĈ^ 6, *%bD 1W KeJ+†%AD`az٠R"FJa lP)#z%XذA0h%3]ђ_e%.vEK~]슖B/-^v+Z "`WzEh/]bвK/&A.q頢]슖B/-^v+Z "`WzEh/]ђ_e%.vEK~]]z- 0 ZvK"`WzEh/]ђ_e%.vEK~]슖B/-^v+Z "`Wҋh! `вK\:e%.vEK~]슖B/-^v+Z "`WzEh/]ђ_eŠe^D 8LM֠>-衢GP-j[U㭪\:r;W_O?-r랷sgZM]׾6m]\jϨn"&8WWgΔ/Rk,S[ySyXyژz_ y8cN)<1 ydʝ1 ZpϠ5ƭy~:ڢZu^cI˕W+ [ jtjB&뗝+ԣ%Kt+,{Ա zҒMGSБoy^~ulb\6k jqjyk+9%b TQF7Lu ]Eqؑ~ z ^ׂuJ~L*iB3[Ȓyk7cs[:gk@۞ҹ.{ڰv2}6jԂ)emК>WLzefNHg"/2eᘇze<\Ƙz8^.yc5bPny`Jx@`frLy+k Ri =zbZ3#U5kc_81:_o^DYa.}Qi;p{WθF::[M KVŹn5ة J&ʗ7}j65JQwNTqBy\9f 5/O1{3jݢ+z{HB33;AdBDkbR3 7*,6B>: WO_43S?yn޿Lҗ7ʁZn7J c]^XɯV*?g}:RYXcL#,4xoD+kŅasqJ=:qm?1}2'65 7d,:'?l̏'Ujoh!]QsSYߜK?'/Ej2[yبר^pV/o,2^.yc蘇zژ O!L ZAMfeIc)L̗e˃oZ ׵Ó. b @ `tTe )ƾx{&RK}w+A'*_IyܨUCU\+!JZ~℺0hydr *yika8A8_W!oZ\6mZ}b\(*^}b*)\f]jڡtᶹW*\P0~GwV-3%+\·bW,Ѯ%:j['O9Q>>M =ݼ%> 8Ou T;*\+,htJ,z 1vj6O6e Zyبh1Wι4^.y8c}A.0a^CG?4?tlVDd AkP %@VrZps4VFS)oWCq's!BB{ާE1+1nN-5WVS촶jX'0hd Fy4۶IzB~Cc'EB~ v6jJk/.|G/7[*0׮/3)^׫920Z^?|WN}DnGmU xʓXT$Xl0[ ڷ"ףBBpl+g{Z:U_ߠu-VVsU/O -5yln^?o $]tyꘇz_pW/7<\+?tmU⯇7kzytXu6.{X[\ѫJOtO/b,5V|^WjJcx,ҡTBcU?-GV ]4l.8tlS_}B9ww6m1 qJb¿.2SiksmaZg}_kb 5Q3Z4nȯV3j3y86?|O_\Ql4 J8>V;artfor؞_ޠez&o\pY/<1WruU|=0jVP[٪iq%l;ٮ9V?\+@]F;S-wu0Fw |UևfÓ_TYk?-Ѱ`_W>[6EKV (S*gZSiR)*zɸ.TК:| Zy5 .yce1WruU]@ԄQB~ejlgT0ET&H-ՠƗZɛEmUKUgtjIl/V~ *ҟʹwGVYt6軨 Tyl=K_?M3}؇wt0'gsI3?\<*4|*ZdmylJj.k6X:~.c "ݺViY5ama*\7E~žJZn4[Tl ZƠ_1+a [;U!Nu]ȯ8 3*3ewtY$3o_f6׻ez.mӉǵ4[U9d&Z6y* ڦR"^J8c>-1\u_ŋUg{.\UY׎K;eswa-kc.o\pU/W<\1WruU<>[iEqR^vŪĨhi^=ߛht^lLy'>uPVk^|T/YG Wnz0f׫f!ABj;Ie5F'3hN3LԶqb:e{Z_kWיWglpW/ տ_?]mv^*Ӭ}eo]vB/C."oV}׵`jzu 'r ոUOI-62^fڵ<3УTz@/~kbݎڦY3Amn5`͓<*7_zWCr@.}~|]LUH*Ɍth!Qekebdo&]ezE1h98a^i1gJzrqYL~8^D|SJ-tؓM5'| UƇ0gi_!u=m{*vHQ3ETc;zIݍff5 ɒWm}WΪϷhklTҊEcm^5<z"P-M{h:c]̟jkxJeq'όz%Ȭkbl_]p]]yz%90^y8p[/͐cAϊ`:/<^.yL &h4ra֔ @p٠ө~e^v+Z "`WzEh/]ђ_e%.vEK~]슖B/--"Z@a*zEh/]ђ_e%.vEK~]슖B/-^v+Z "`WzEh1h٥BetP.vEK~]슖B/-^v+Z "`WzEh/]ђ_e%.vEA.p-ĥ^v+Z "`WzEh/]ђ_e%.vEK~]슖B/-^v+Z ZvE0h%.T]ђ_e%.vEK~]슖B/-^v+Z "`WzEh/]bвK/&5h9L@ @ @ @?~-_!C;Z@ @ @ @@*XaJEu@`+ @ @ @ @B,!wx Z===4@ @ @ @1x1hQ@` `К|\ @ @ @ p `к݄9? Q5JP@ @ @ @-"T@m֗A @ @ @$AkzN!,$A+ E!$@ @ @ @8 `'@ 0Q0hMI@ @ @ @G "9 Z @ @ @ $ ZNJ  `вQ5b @  u~]Rц̼g{wk67O`PPH1Cf x@ @0hM[@-J @ N ש#uS鮌M-(͛q8U T}9]\=E66#; @ @`L0h ;C}0h> @ #AWVǞCcynҾ!u{F𒩽e/XNsJՠ7,pEj24) @ @`r`К\0h @ @#0ЦUye**;SAjW/ӻMz~~JA%|Ue[Jjƅcg͂@B @  ō@ K`! @%R>^UK+ctnPuZkjAk-G@ @ Z& Cɉ @ @)8_uQVimYbت.~Tkm錙&pQVL!8?:Tk*lU؊gz dLq8U*p]@ @@-@YBVA @ &NmXPXMmOJ`WWG7‡ ^֖(Vk&U-Y" @ @Bք`$Or@ @@zA:_[NUhmGo3G3G/+m+znNWZuHy_i .~@yُ7ݩ^x߷)n Ty*]ŜBG_|nLu:)Uq1y9+g*7ꨜW@ @5<9 [&Aq  @ @`TB}hZ]ΰwQi ?T|͞n79;w۠#jq{`Y_%L1xL1+q j~j% qk<\E{tpf&bkWiL*W1g-퍊e@ @&ɹ qP@ @( ꋏL.-ϹJ7jc'7_+TI5MZs9uj֟lzqz1*R1h-zUiɦ .uky_] ZP\=a.\SLzsϜU;¢g @ @Kln[Fǁ @ 1z%]9\O2<5mZ?׳kJe2B 3sf+=Φ*r0z^Am*: jqjR _-_YIj:h-jAkMڣ9TG}e6 @ $Akar*@!Ak<8 @ @P]A/ Jo5=LD vjAN,k5ܠ%Z{pX,sl@E_֮1CT<'ۨ7tHA.:֤`:1;A @ 0F!.nY @ @бFesUZwhd)\$v-c>ȼϫPr%n4S+6vV~y? @ L ZE@' Z @ Lڴ*L1UxVɟ5Ott^DWőVSi*Mn4_}Zo ]b\T6կ3WaJEm^f:*ڨy]6)eQmYޢF3E Zfk[y3x@ @ 0a0hMJN@`|0hGC @F"v0wA5ƧGWݢ'ⶨȎ*5oE"mzPa}rB{Y_%wxQ;Uߵv}:t )C_Ї-̉J@|O~[iK]ጩh7r:ⶓm,L0%@ @Cxq, $Akar*@ @@JA{\k<%,Uc2tj'L:WF4[A ɧ=/(.'94s>MGc5b[ئ1ՠL̇E1n%URV-^*V&Ska~/@ @@`+ I˹!@ @ ݭz-#LJY ئBxUzjMt~tлC"#ڼa٠%Չ >VxuHgd>@f=Fi*h YۇcWUJ\о@ @x `/A 0A0hMHN@ @%nS/3f;[G9sgL! ͐4Nٚ5_ik\/ zߩ;W>=z_o}ozz7&vRnzj^ʙ5fv/ @ @BV(A'Ak @ @ @  `rPTI5 @ @ @ @  Z $5 @ @ @ @``К$\H0hD @ @ @ `вO3"%AQai @ @ @ Lk4&I b @ @ @ L  ZÑ@7 ZF @ @ @ @YGVIB@t%A.O81pyy]Oh. "`WzEh/]ђ_e%.vEK~]슖B/-^v+Z ZvE0h%#<2s=gW4ZKxB/-^v+Z "`WzEh/]ђ_e%.vEK~]]z- 0 ZvK"`WzEh/]ђ_e%.vEK~]슖B/-^v+Z "`Wҋh! `вK\:e%.vEK~]슖B/-^v+Z "`WzEh/]ђ_eŠe^D 8L]AE/-^v+Z "`WzEh/]ђ_e%.vEK~]슖B/--"Z@a*zEh/]ђ_e%.vEK~]슖B/-^v+Z "`WzEh1h٥BetP.vEK~]슖B/-^v+Z "`WzEh/]ђ_e%.vEA.pA}F'[ #=oCEhuYf[Pr-YQ0@IDAT[Ut wNP.~V[.=oy@&YHMKz^9V{EP W}B δRTA}mںLw՞QݚEM6?qFP~]/Ϝ)_6_=,ͱYxnQO攞˺quUZ۽^5֟[QiOn_Y)zI=٫PsH2r^ 5ş T^>陯з5޲!=f sp]mi"nyXWTUZv-j}Rߧ^Ƶ1pZ/<+Sy8c!?;c"z/ )'Ak@[t E8j+:Wjei4ԺM/;WGK`W YJc_չn+zۥ%*#r%BتŪlԨ:*>W+sNK6Ś:n\K޻L#R+|9dۙU.ӄm z%g޷#[%>n$+zu\%3G=s] azel<ըSPԡ5 {}tY/Pt˶5ķ1JV\=E_e71ʔ7.y͍1rqe\pU/Wn]kӉ<\4"w ?կRYqUu u%pɭJs+F'kSM9/oRymk*?^rVk_SUc*~gպEWoݯūjf=fB )f=[gwT/tqwgqA}sgoTXVm+Յ}u@2'+دify~vZݼǥ/ooD0ƺ.P_T~l:uD{MƘFYhވ.Wט 9E.2Uztz~4ܿceN9lkt2o YTuDOؘMu#Ohm;޼:Cz9a^C禲ȹ9~~Mu{OD_d Z =\QQ::^NYvee\pW/7<1Kе1 ZIB*n:W5_x˶,R0b /̗tm DSkm']܁@lRʄA+ RV=}!f-M.VڃNT@s!;MZQ=;wQE/^.2WB =%w u uaɚcTRҰ¬ pq"hCJRs=lz[?rPT <CUR^]ͺԴC9msIUTa;ƭZfJW= z>{ Xܣ]9KtNs, k^1}2}6 <{ZyOK|P}q=,wUWXjX&Xb%:mƟm.˂'Q䍫c}sie\pV/G<]vaFh~ؘ*@SA-֠6,PuK6+3){9Óv5h~q5 ~SP/k;ߪ|y-NB~L57O McPwWhAS!aIc*[j"pA8+୦imSb}yAN`ЊɞA3i˷m:; Ņ*LJ*dOąl0j֞_<$]h^~-oz?T`]_bgRWs e?:`L+;[%M?~LJ+h5\&BXݎZ'JH.`-JoEGDžjgٮWƱm'uߗRA+ZX^$ZLk~v"|MތnoI6h7a^1W96^nyWژz_ nyخWژx@`j fЊ R|sD[=PAņgTloW/.t \/VC p6E;Xy=L ԭ~ j*YQ' ukW2Si$LӔz?ֺ@&-ͦ+^vҲ-'縚w.v[z5SF IsGL.snl2^ISm //>6p#VqF mϯ.5M:naBtX=]tԸm]Kcnޘz9:^yWrOÕ1֘L9 )"AKf t,ٹl/kںێʥ}!~S%m\jTCWWcPf+3_~Yj Z) hՂXCMƌ5ث/~N[ԽӉi\+Vq,ئƿr~ImbVcc7]ˇ=e"*6U:֤ ? ti#:-jأBgګh9_ffbyWq@Om~H3^\V'hv˙ql}*#Tw䬋2ҏFD}W="-A1M޸4^.y8cꘇzz`yخ׈C<0h2'-*hM5+feg͎#Ytb՝/i7g[^vj>aaH3ɖ`Y-XEf!]۷B3ÁȺl xq_!  (4cf &ʒ0WXf=[e}|N .UJv]s+~WvO!][`\w/'Zy4׮Za-}nm:KP`UR& ZS Tq]5u"#\k\pU/W<\dcꘇzy,.yخWDtC<0hy3e@SH5VÆnIa@0`^$HFÏed?sM+Zr{|AU:Pz$z+@}~?_+†Ɛ6ʑ[JKqg\}Z𹁲 ŅΆ *naL&y[A󋫍/FWO7lo E_έT?/U5iC7%+^1LVSlwQkR[F<;z{j_._f=f3Eea0Nԩy`gTF .֟dg{~'y2˶ VR jx\\9%[\+w:]{*RZI&<.ߺ_W5MPSN-~vLe5f/z92ƃܰri2}5}LP.]ӊ -⤈ UQ]5Ӽz7Mr;W5*S$SzO|.5GS^l.=&a̔W͔CvljNf:n37bgrm+gtz寮3-PkN6^Jlf^/ڒyخWquU1WruU\pVhGϵ1?toV4@` fRKU󋇾j[Buޫ٢e]nſϜkTAX` e((R=7tCz{T/k_r;WGr6ڼ@7_V8K.B#;U1Ơe^n_nV|HwzU'UYf[^~]*\}E,߬kh:SUN+zqVQM[hme %̴kyfڵGA=^^źMZsfkL'3yTn޿?]-d^>{UlޣC>zLzeMcrpv捣ć1g2☇zrqe/yخWm*MltmVDq 03h6Vhԏ0B' -F]jqk"Fƃ!_D̅i\K]BbߎPE5NMrF!}<M՟-,Zֱ'jN::Ra v'B<{P۲Urg,n!wԯ̈́k%uIpUݟoѐ'@{ .kzRx7ΝC56Cw>,t-l3z;J|&!<^St+_yL\zMєi:˜)O !AES. "`WzEh/]ђ_e%.vEK~]슖B/-^v+Z ZvE0h%.T]ђ_e%.vEK~]슖B/-^v+Z "`WzEh/]bвK/&A.q頢]슖B/-^v+Z "`WzEh/]ђ_e%.vEK~]]z- 0 ZvK"`WzEh/]ђ_e%.vEK~]슖B/-^v+Z "`Wҋh! `вK\:e%.vEK~]슖B/-^v+Z "`WzEh/]ђ_eŠe^D 8L]AE/-^v+Z "`WzEh/]ђ_e%.vEK~]슖B/--"Z@a^ͤi @ @ @ iK??n|Ge9;0h%- @ @ @ "0TDY@` 0hB @ @ @,$ABpנNh  @ @ @ @`ɉV &g!@ @ @ @MC% Zn @ @ @ `вH,B&Am}i @ @ @ LOBPB @ @ @ qp@ED< @ @ @ !A+{ @`5o@ @ @ @N夬4 -U#f@ @3PPf̘1C#d֬3o=, * 4ؚ'W @\ QH"JVBʔ%k !e$0?5?5|jjZKkZH˟,e;ν޻u=v>>7 (R! $(ВQ4HHHHHH!iHƬ;&F`yh[\)OmuS^6ې; ;W 7 jWWml؛%]m y_$@$@$@$@$@$@$PPUђ 0 94HHHHHHh+!j-@-H@Yۜ/>5+2+j< _v-6,۳0*3V8N#-aԱT O-} HB-IE3IHHHHH΁@V HZѨZt.ֹ5G +[sR.:ݵRTlUUmxv2 p}}s<&      s$@9$@$PZ(*-lHHHHHH ajժkcRm[eq~IeKǨ(US`TbgEňk0/_iaiюMx%rۮ0!ZJHHHHHH* *9H P%h# @YX4 ]iᵔT>- 7ᠸ%*,F.s!)m)BMK {n6}Y?ogo,I沅-OX$     L(ЪLXIZG$@$@$@$@$@$P,""<6> G05.Fz"q=܂=ڢn3?o9]WW^k[_f6xreiuk@&qmѺIlxlu6MP{1)nEH~vd>AS0KWhCB"MzG} "(Rđ (В߇ (@+/Cƻ:& -A"d"Q3io"L;}݋Qq07Yؐ%DӥP΃x9a:Z9;]q"2mzTM+ڲVeƅ[2ѣUhêQ-1rVd%tCVm8ZX$|b֗- T2hU2s$@K -#     (%h%.VNj"E's1s,\"~S@?MFjr,\  rRvś3ѶTŽk'p]46[&[uDheyy6U TgO K$@$@$@$@$@$@Z, @ @+CHHHHHHʃE@TLj,,0[~ZwaEÃHN0@ {>,z n.GWa0boAs`BfoA! Y Ŏx;C3^u>X=Lv=7i:h +@k8BpQ}Ζ,//!0\'$&4VVr<\Hhg B ʌ(v{j[$D^ctYmxԧ@7(^!     PZJ#Z2y ha2Ll(pUUFY",tJ3 Qx te?+6(on=L~#ƍ1E =#\)F'/:*]ܤT5_@~p="Ck!N B !       JHJtH 0 P~U$@$@$@$@$@$@GZqHMEwݿ}{kv۪LZXԧ i,ySeEPU.1MK&J"㵎 ĭLðUQW"W8hymË) |      CkH Py$@$@$@$@$@$@eNZSs^XeNKOEcoF*bZ[?nT蒅E@4<ܺ*S}^jNYRp ν ]ZS?2šoPB$@$@$@$@$@$4 v/G$  dm%     ( QRbO]{HZVouuݗ@ߜ-Hڈ#/(Dl]MS%5К0kU:\;\ AfB7xɘlǖ-zFBŘ=n{Qq86\D-]jQ=qZ_!F&9#RDj댄_%KX5*#M^IEvp'LFO߾ԑ F,GqB5,r,6x(C      JIJvH  P^M$@$@$@$@$@$@I"iw.OODyT~-Ej^a:I2Ma^W{qI hH\pK8l9!kWEaΙT:80%}MѸ J(Rɛ (В}4HHHHHHdCZ49/ڧ@˖ -;tZDݜu;MְA."!m41B!j˾H_"G0m#F#̬D1YWqfoAF-LM1)1{%b{B40ۛc\>IHHHHHH@%hMH@jhI>O$@$@$@$@$@${j5kP`9N۰7c7NRuqyLd:DŪIP 4gf9ڪ cq)kpCH0t{F뉵HHHHHHH2@2xc$ZRF @PU"\L$@eGc˖IHHHHHHHHHHHHHHPUQ/ x@IHHHHHHHHHHHHHH@h)DH@ hGHHHHHHHHHHHHHH(2`H*Z] @@Y () JJIHHHHHHHHHHHHHH P>$@Zr9ȑ#N֭+ZK._\䲖\r~_rZ/K.rYEE@.k9/e-%" h/ZK$0 r#<4ߖJj-%/K.rYEE@.k9/e-%" _\䲖\r~_rZ %PZr9wex%%" _\䲖\r~_rZ/K.rYEE@.k9/e-Zr֒ (L-*%" _\䲖\r~_rZ/K.rYEE@.k9/e-%@K.Z  P%s@" _\䲖\r~_rZ/K.rYEE@.k9/e-%" h/ZK$0 r.\䲖\r~_rZ/K.rYEE@.k9/e-%" _\䲖-EkI&@\\r~_rZ/K.rYEE@.k9/e-%" _\䲖\R%h- (В˹\_rZ/K.rYEE@.k9/e-%" _\䲖\r~_rZ %PѺau)}he_N*FSW"!˞7aMc}Lf7+/LjxV?r`D1b J˶I+ -5c60tlfyR:N Y"e|+:*d*+{z,p > |VQ*qTlÇ@N?/ƒP_گX!4dC%~,xO)!7y/RpCi9b{K=Cw}u<(r @POQm0fS6/Fj* wv,V ,~ >|IRˑFDMGhcTkI0|Gc~GD*ӧⷊaH[J*˾w-: ׇeթ/yy;TWގDL)σ+VNy,*=}+>*}yɎUW)>s~aaSP gdyI:ƫICLk>/\/˲7g2LO@VoY7B=oި硲P_*y/uzw A?zўბ<[Dϖ=mxOsEb6HoDtPu|N(_r0VF91e$V7[a=ZyUm5L9\*oCi<~~mв8 0K6l߰~|])?]DCc6.lr-Z(Wֿ<F,­* TA| x|+^hx'(8(=HO@vz3rLzn0 8DM];}?Ptםn(ClU_ǗCH0[z+KuF%u|X;"h%-|b^s1ch$ˎ;l O"\ƪEB\T@˘, @PMhgZBCWDK[w^_0 GyYk1po;7.fۻ^]+V8}T[dmmQ{l,h u:߯<=YYDKJiqד Mi#}!"<#yd}p*K3kU:\,t? <~ZjˑV#Hu&8,XL"௽k'pƶpZgcd̋qDv32Pe%~ =Rqypԗ@;nUc#jz2fFn7K@+mu??W )f2"Z 1V^6)\– JaS_zG`h$Mf5i>c8Ճ 꿬T_ *kХ+ 褏cMn^+pd*ǡm=I/ߠxiD'T;5()+'N/_ބhyM(s_Yq땏gߋ5 d_ř7*y/<{K=UeY)!\*Ae&H*-FЪߨ\Mw@d`]u_|^|8Tzqc4*1.-˦∂@yeBH99; gh#s^\|v1ͅj=Ա t%W!*+{ə vN0lXwF U%^qR 2; {$;d&tS>Fue v,/D%o"IM܎YDcCc~HSe}kμ-/P*"RS5RZ(K/UqN(qE T+žRuCU٤䞇RuCU,*y/_|@<Y& $@kѐ[E 2DDX&",7" /|I6]wjّv=Ǹ )@WIfw7"9bCR&r?aV-EpfϚ5P ض0 6}D[Q 5[":NRs0ϢY]=#aq+tXrSk~Ⓐ <=t_Hg)"YӺft 폌8&_񗐧bc~Qst6\0B =gjl!Vx 'e>| qPzڱruU=kި硪(ǗUP_wdP_y/?G!u_A{&D$@J@- ka xFDMMDhMoqt֒H%DH?5hzeDL"bT<845R@@~Z՜ۼ Gٛg! /KC7W9[Vxb!J-x{S=7{#zU֯ ˀ!RضKD\u ƒd'H/?e$@ʚg`n=eN?N1,5 hYDi4(/_m{gXhlO[Į 7DtY%Uۗ_=r߻"fp@IDAT<6MΓ7/ Iv}g š h=>Bz<9r`ƒ @ kg}}IJdRxS iң b:v4^F.)u%MGD/)JnaX8;Eb>XLuHt GtH'%҉V#rN.a؝Nv0%}!eȶӡSȷ_HvD  Ӂ NkN:~^e{QtCYyæK=e%旊{*'Q=z>U@qO$@N@9 j?XaEQcXXMA\ZT;ڈCxT;Z(THæĉHП:AS'⩘p%!)/LDԟ"Z&(T;ӞCb"q1,muS#gX޽FL)w,LQ_@VH:F$\SHR0v$Z}Q=-=Z-el{ z\>fI{(w *6!uK4u k ~/#]/KWgM➇;oP_P_ y(/硶_ևۗJ{h^O `* t\aV:udܮGƂ=K\V_v[rO;ժSGʇ6~T󗿱pM5W ƨDT:j݆bQj,4\YP_<+wQ/-Wpda=eE5ӪXה87jK_LV׭ r& <**~V_%GeJ{_[LRaσ 24H\ThʴQ\^/e-%" _\䲖\r~_rZ/K.rYEE@.k)В_H@ah\.P/e-%" _\䲖\r~_rZ/K.rYEE@.k9/e-Zr֒ (L-*%" _\䲖\r~_rZ/K.rYEE@.k9/e-%@K.Z  P%s@" _\䲖\r~_rZ/K.rYEE@.k9/e-%" h/ZK$0 r.\䲖\r~_rZ/K.rYEE@.k9/e-%" _\䲖-EkI&`h)H$%@$@NRGIHHH@jhI>O$@Z8 x%@W,N UQF gZgyUjJ+,Z(R˟ T$I~ٛNQ;l/↫kG/Txwvl'pcW}%  R'@V#eH6rGN;z%qyH=\qE߯>q7]XŲpokjTz7C^!%@V:f N? ؃#yqZ݆j&-P0sglȷ;V8S;cT9:5_$@$@eGc˖IHD(* ^Wɟa[n6׆h~)]G7 _<8=,?ut;\G>~܀GzA䊖Q۷g~_۱m2$}jоۯ`m mo¤YWyYu_.__Oy<\Р6\U.,z<(:K_M3|9{࠰߮5KjQ/.. |ZZARߍMgݯ71k"ne7>D̐/"A$ M|F"z*6ΪEѬѕݖǞqΓUϯK/mu/\}ҙغm#UPڷCooGH$2 T.F$  rS{lEQcPi2@뫢Z@+bZ{VcG>㼲۸Gw co2 4퍎_D_v`x$:5>1d$@^$8rweψoQƆI-t&; sE …@+bZ9۰r~w瀛0>2s l~c׏Z\7\{-}75^wAU{Y&(Ъџ܋bIk{J .4dB(mB5bZ'2/#lvP{;)jOYKbÇ;=QPs<׵8OXpzGIGc͞H@Ey"a޳}G`Zahl@ݢZ+:=|4)/ .y:g7_7|8^<~Ӹ%ޯ<[6ޏ_dj47| $@Ju "|(В-F2S, .^ c{pUnGt{p-, gPh̅N!BHfѠ9+Ӯ÷?Љ'VAi_zF 'MbbE yV;<=Fd:_<}olKƫkt={顢Z0XWP;Ŀ-y! ĉX,!BHVXucWameZ!KB덣_!!kGѷcMD)O@@LqP@"~Xk󫊈տc׮d,#Vј5ars8Ak" 7OxwF1:!d !Ya֟X3>SB'bmFD⋴d[D+44 phh @!@OæwZ>=|ujNw)n|g&]9x3n=Doǁ]"QP;/RYݴt\Ӧ5Ğ4;X{X=9@\zI5y%R>XhM ۳;&>+`@2'Ab@b N";[cÈ)OD|Z0sjzsL'ohIxjDhpb,l99'݌&i:v,߁Ŧu툷"RuqtsF|spy%-OkjrX7X>1"9+_ jF䁿xbF\/DmT4 I$!@$ f} NƏFSWAc|z3 ݍ>L} w~IԮ+Cj/ٶ(:w%4\Mֺ"<}56]vLW|W5}B/9\1[qlуxM02qU^|}=бebE(hgB8=]75 ?1X}C}1􆫵Z8;ZoԸ4iZ1i;1w0nwtT_=M7U\]d1#袪co2oHr½06AHcbvL{yL= ̄X&( JUIH, PUt˦k-QpgE|ٍBaRot?u9Nh+pdWW6ɨn$>ߤ6 4Uc5EN?lf>EDcGL.dmñm^1P ,d;@K6^ } c܊wbp(ᕈMvvnh+FfUc_fUf}QkiLJqS&S_ ; ;[D%h۱~CWhMرd3 $@$&@KK%ƜCA:tv Bˏ@˨_ q=f S;Ɏo鈎ju`ؚhQ[;mfDWD%j4 y#!G#}>Q=)ژ+دxН5RMH"PU' O +e&mH5ҪS"о>, q hQb 5nqmDuCHVK1P<Է@攏"Z  h5LL3HWM+t?mm?q S~W;v0 {.jWժW ;r;b]5.١Iplk^kMM;J_A^x[%ϭ8zO0CսӋj\wtBG/оYoWuD+KDˈef1FVj'Bn.HHZ@xX1<#ItϋtٟQǾNÔ/2--]m!I{^hRXo8WUVmzㄶި&-g״C8[sR慸h,gkbaCth="\ݖۻo1cB߽#OZ HJFbm (3h2i8wSXNw$IOw: S'UM N{KDzh)3Q[G6Q>q^rj&`fKyEc>hr>vmSwv|74 FE_L}1Ƹ^@V!$2@K&oV Y]ܩDccxDxemtYZj;rLeXv"&슽Ldu޸(}s)?ؐC"tuyLPYǸZ ׵# @@ІoI${wHRw["IQHw1&߽ 7]Zoh~Q bwb#sDcD$lIcgZY7N݋2ohzIXNHTy齻E__}]5Db q[{ͨjTI:n{&ͺhƭ,@@LQPI<cwI%qp Nľa -Qj8o1(XjZx{瘴-3T`= ˛N^o#[ {bꚄbF{6l]qN8oGvM|7n4 }=o?.k_[+֧5,H) , Z%%$@$PF(*#e졵.Q0 Qo?YϮ[,v7vXO;AqXEQכWCŘ'n!.+KSq-p];N绾l@|X6W{hy*Y $q$"͇K\>ifޞf*#ZiD8v0X\(VWU\WW19f&9Wb$"wxF#WĔ]. ]׎w|u턷"}}7 Q  ?(ʍn)Q$p̦6W _@kk| THO;XE#3ѯcns̺MSnXUL:9]$6یyba7qX"hy0+YJZ @!)\(!+csZik&`F=zTSq)J*Ҍ0y3hZBQ5Cbn˔0Mm"}F;b`+^^|=cm{fD] A \"( CuHHPUKȜv5Ik@hygCi65 G~!}YÈjU|(E(-<1C-uzp5Qpu]bcw§U}lB0%k67h3Ep( 8 pO +n_LGV1{1sxnjZxSaD*߭LEԛ(l"v AMPFX*7>zWtC料* ƚfqU˯A}""բ_g-M, @%@V}]z@RdMgNqh$*(5G'A+jԡϱ A#\ߖaDi׽Q|7 A#h%к>_rxLi~";>M[eLu bEjH_ݦ_BZZnm].εCkQ,By;Qpᄌ/Di0g*Io7 X Pe `R  {#nV+@*IGc@RW4,zq܂1B0uoEP&V>#@fVsqVѯ+Z9iPIW|>N1ܠ7Z\[efL,@@ iPUH06])n̐/"gZB1ð3DzQm -SSB0u`ʍbh Zr̢{Ź4›@+'kF/S9Fc p|ҷ+, hë$@$Pn(*7ԥQC §(EVDT"RV`ʫ@O!mQ&̒Zn, 9|vamlZ,lLz#X,նS^Z Be;IZD'n1,#2|H*- *h%pkg+ЊoLe`u;UJhRYm+J0UuLmJOX2_.GzD3.{! T *:$ -:Y=[ǶT pJD`JD[(*U\gyLﴈtQSFY 3FIH 5 (Gh#sdbwgmZE;* jtՙ;rVWڦklg~5ሳAD-;Xv'6vE*?G7QX|A yLTqY=+Gt-ϾY;<'m>l|qÚSj~g=MEADV}?Ocڟ1ͽ-k-YyD$@h LT|{힕@#g߻ۈ"Ԕx"3pOGP bMDJ*f31[OIU"?d+nBEIˇ@ hK:"ZxʎZeǖ-J&V*>kY"h1*s L,i.*8rߨY)jgM > Z%h/\ ;==#YaEe?ϓ Z  !@V8XfLT&힍@nٻ3MO\W5uQx'µAUHq%r/J0uz9)aL+9ZYY;'eMg谪xsL[> $@~ P/ x݂IS[ ]?8vz\UKqE #LI1e$ G>J%;*sDZw5fVsp+ڄZ(/KR0o&FUnK틿**ϑ@\^!\, FFTױWpvBtGw'"o6N>HP*6 ֻ@+rjXDRzh^0.}D d y~bn[!JۿHԙ|,^ΈC_ߖ7ZcH(r`H*Zĝ:EPx{H7Tf~ǎmmnOѽǍf(l"h%d:xx}P!b3vNn} 4ɻѲi-X|ۈpyshKu ŦwoeZ4ʢq9:@ S%h9 T"+*Iojw8rZEIgA˗IttzWס+qxkGZ@snע*,c |Hl۽CӴV\ZDď뵳3ߍD|A6mqnsc עoXD$@ P eSљiؤxPPOYEIgA(!Sg;?* 3n׻q˕Ua}1yLS_N;/?p֎+V`vh3y\g Z\q8= 봡(\¥5$Pf(*3l%GDqGqC64.h?MjNN0ܥ 2EO/.ZoƱI<[u繁þ .e7.08QR"h5GfMr 0!Z76l tAގ,i`H$P "$@$PN(*'ХM~t|8ED%ڼnh{]b^paF mZVQRYe!2 MEcpg%8,҄` ֝@g@NW;BdfK |<Z:&@IMHT–ǦF|FoO芑yVQRY2ѲYjKzjnK4gE_\!l|9#o1B98#^|\p+=p-˂{1;oQkMKLYe!%=buxk+f!K]!J6Z3{v>:w}5#t#WOY96@y@<(PĻZ$*11;b ڳF^" F+.VQ٤8ԅLF}hYEwݩ ѼbH8QOh8eݎe JOJ+@$@B@D1U;-!hWo2 qJƏ%7F(˛ZM>]%P%h# >LAÆanaMP8S{3ݔNW(v k&s(`IDJ { KT,ǻKыp~\6è`[F%sADzRDZ c/H7HxKn;vYfnC.6L$@%'@Və!ےxc!aD3eォpoj)`Hx`MGrn,TDjB]mxdƋp}Ұ^ho¨*VKETAw#Rއ}~^"EbKz'_? {+fqvCfe >\ěHL\<o  qw{}fe(hgBJIRa5% w}M{pkxk'Md6Q-% jHMO [΋p w=5;fЗh+Q]Q抨VѪ{ۡV*8_m)_輺8cYfnCVԄ=X{aH@^# r$@V9.nżJ-!,\o&D*-TB45S{t"n["-DlM"6KȿHnH ˆn[EZg'xuuE2u" ! .& xFr`L.A{CJoI+h9lY>sXH6N݋sYd\^عx =0pfwe,ݗcԄ̚0Lk_b {Ef#DlM"t)f{StH(2`H*Z|NyP za3 {>n|\tZ \?yw xb&-xGj?M)S2o,iYefaf&p}6ؓi cѸ\9zktf# 4C#{{^:/[5Q`a񛬓ٶDlCwMYA-aP`?xXzahClq f:l_m'{ L+4peLE1T~V2'ê&\˃ \9Řՠ6Ӷ\O$@F4hIH Z,#>Gl|~ZS > 1lmkdh>ġ#-€/8d==8k>nJھJZt:ck81M弔+˜2g2qF 4hI!$}6 G63-|}j+*A7HOS6⃌8<65s(E!mY6 ‚W׏C MiYb/djU"߂ٟ3gم0&kU ט**puWVʲG V/l1: L,8qӰLK#sA%-#Fk6̕!!.!mYG LW#sD;0k{f_ð!-H߱8VޕoD11\cl5&!dƯXE|JW-3\E Z -wLNA 6z#~ivǛasк~عKH7IDAT5ġ*_c,֖=~nJڶ^%Oʵ7qy;\fi^T5ַ0\eY ZJV6|% Р!' <"@V~\Iȭ :mf"xu~%:2Hĥ;Tn+Zaa* Z5G?y8QPI8a dT:"~. [a .+ ZWtfGгћV\ ˖~#EfsWgF֏=[gcI2I"WUkrIQz. 7f  @O P T"ģ{QTΒſ"(Q*>^eQ>]]v7&<;W*)s,R"v'- n_ C|],UF[h Z"#b)"xh^?ؔ-cT1Nع%ʗBC2Vn64DETD"bRE?J~6Ye-ޠ~wR"V7,븗G];سKHH!hrFqHNƍp$@a(],*R%4h5/)r6b E*FP!\RD? @ZPĈ8ψ{'wŀ⒥L1TUU*۹ |'? w EŚtRA"Aˡ83pnyWn_Cܡ\ڨQ&fp=6A˻*5h\;;.V52}쑄7NkxP|mԬ\%HHhrD'݅%`5B-ndrR6 8 ZCi%FnР)9p: ZN& A@; -rKѠs@khҚ 8KoH oР7yyР%VԹ Р$ ;dW ZjT1 8%Rvt-`NTD-PH Z @h8LpvWZ4hI+'hi@$@'@r$@$+4h 7hi54hiMQ ZΥ7{K$7h<<hВG+Fhrn{ Z+IH-5˜H ZN)C:}wKo_W7;!"@V~qIAou-kM;b 85Z~vkkv/nTOG5V\D$hox| G!y5L|@V`oG% hi@$@*!@J0$#/X +$a/2 Рeb9 &zL>bb'#& AKe0|'ػ)8 .R%x0@- PH@i[ )(M HJ-*øH ZN'9;L$` ZVp1 8)TxvH$'@2|   p0 # A+g]hr>P1 Z.Cr%}5j;6++g;?s{T[r Z=G Y^Ow!ꕇp(y ^zq޸Mz=nŽ?r,ǽ7 ;v˱<hz܄  ZvRI??꩏$"aEG}hR&"z}?V3S ! z J%ͨJ3 e'(4AK%BƢ-G|-,? PǦ^,?A+???Z~|s<'@#z=<ߘz9G: z$|y1 Zy$ hвEKiQ2^u) ZjUr\|mZR/*c9.eZR/*c9.eZҠVe,Ee.j]JԪ帨e.j]JZp[֥KXzY֥KXzY֥4hUE$thВKr~\䊖-n%e~Q/-K.hВK/H\䊖%^4hɥpS/-zE@h_rE\z1Z  AK.qiТ^r+Zҋ\䊖E" W/AK.h ^r+Z\zѠ%^|M" W/%e~ɥ ZrhI4L-ĥAzE@hiВK/>^r+Z\2ҋ-zE@h_rE\z7\2\䊖%^4hɥ%0-%AK.zE@h_K.rEK/ҋ%e~ɥ ZrK.rE^r+Z\zѠ%^H@dЊ?VB(_=Gr4f Zc(4t35^j#v CAel)P@xx)J;9VZvoxT}}[GIiU2Q߯TBTGÊfg꼄sZ moJ̀fd~H\-\Etg0> (-7_Ɗ] T-^ոtƤ fuTʐ().vqGѠucA|ޛImg#%KNk74Zŕ)&#l9{{4[DӶ?Oۨ`^*IZ,mD9'QRM<Μ{'[VU4Z59)])byaT 7TM|2s?؎":"GEUj|tzЏqHk[>>(SfLæ! 4 dv'cVa牽$uA:ЦO{˺{/,mAO٠%=V# [uPRlm r؟wTzerHn_A}/]AO_+|7nGm8q3o"Y93e":EV`hFfJƞN//Ŕ^]P}4hOFD$bкp"~]؂7OT?,me6 Zpjv=<%:]o&^ ~淜`VJ?"76Lu2hsFn ±wU2-nҞ0~2k϶H+zw, K6ı95+cWի=[fuƦ~0iE/8 %7Ԃ^ϡᘓf9_tŘf,6t-aVE5ϔPbNơkzrY|Q-I+--)\" $^xQ҅Lbc@xb(>tBr#WVue.TuGR/oLڼ2lzGE7{xkMuRCCA؏v<f7cזXrL Û: ,'0me7x7 _2㛡_&-[3nWҳsDnGb7X݂ӱw,FI iLz{ƩO0~h{ୠUV&-g\;ز V\K]A+cΦWݭx˱V3|mAL2! # Vb(voHs㖈j>D5Dnk7~?ॡ]W=G֊G ֖`5Es1 h\Xyxr.~cx <\f@r(RX5-d:~[r Mon~6:BuƘtYn$=w M`GM1i)LNա gb2/Մ^Y(z/bX*'Y`V},M'ȩmgQeLJA"OJ vcϱh9CF5Q'*Z :vE;5Nj7Ğ񓱸~5l Z/qg޸?د{ *r",>J=]C9hSF\G&C(GkWMAJ, sy0}!M 9M$ՙUu;^qGN`[7l,4}:ʯ#1h[p3ShY,V4hҋ @ЂA+<^lnbG] UeҊ(Rey8T{I;. ZF4ze:|OhtҎA+ET$_ڢM9`BϢDH;@2JT y%"^U;5TI z2 w" ENӠģ> UD~{խ.^ 6QaװR:t| MՄ^޸/iصjX^$A+.~'0Eљ4?υdI=)orZ`Ī87ë뺠:26$3ǟX2_v5Q53Q5Jwb4h\ùn/~.p1 @ZP~p9!iǠ+űzf נ;5.G%KY4|8 T*}w,*+[LVgs-IVT>{[T> u*Xs~A+zfR8.p6[Jե:9DH_g/ j(z瀝oj|:+6+nE}Z+9bXY];zIa2$_{R"׵C-ͺmqK s8 Ħ3%0pJ#(LBT6C ?J<)a7A+)0^ k~xcҮ \^_7 =Ed\:"ˣZ{5'(*'[+ygҫeư~ώm]6#-_-= !ag5OבTW+2s;lz{V`ə)/= -+!H<ٟ )W18`W;pf7j7$(Xae3h%Fa.h(7Ama;ev72e@7` AH`Vؑ|Pd2e-ͯ|U)W卿~;FZ%¸ejJ3y(*-cفIOMnW!1ܲN-%~u ג*O7WwxdCwirJH ZJi7v ո WtEQA@1zN4*cmI iUY|ŰUkv ZG^_boѧ⎊MTTk?\W?fh(~EI;zL?/펞㍮-iquYNPZȯ{g/0+jA S[(YmZ++;AٗP796pɺZZ2h[bfCXˆI$R$ QIToꃦ5y)'I&Y2h낗{(tBΠiDEcpyEF+3 O/a0 Xr8a#ԤS垈~!~iq_S?ŕ*Y5M }8lCdS; %!!Q[2hEPf``bɠemf;P2nв/,L⭭|X=a#CDu*SLzYˇ_)w]?2g5R. Zd5s]z0} -^8BOL]% Z;'*O3zO+C ll]~.A+?$@$ 5V0v0*d4tS,wV+{%Qsnj]0V@CmykB20VӇhZ{944$(-K5->$~h% ZǦlG4_ w1axUξj+![kE:ĮM 51 2Vxԟ/c㙢i8$F.~YLc}9dχhA/3lɱx.QU Y*>aZ73hATzѼV!x1_kaCJՒR,kQD+t m|lz]t:Ů|g% ZS>.éz>U%Mŏ҄:mV!2& |jӠQAy-Y6(>B|VKKzhS*s2h=|D+Pzbv?j5vo&HFWW,Q߬yl&[ k10i ц^V_/bAo>U+9vNĢ`̙8.jcYDHV3IH h͠eĘr 7–ŢVWh ?)#E4oi'3*h}AATU zH+YQgDEW4ofZ4hŻ0[ l*JoEsuSjrl=A/w4$表(4|0о N[ EuqfT) TqH-Xe͞xb)X&]UQBgi[xu3i}A>1(wo2a+LhwhAOisC쯠= 6cK7i^}T9K $ u뺕9Y3h C\]3nkLz5-IE-N-0cb 7OɨÖIt?:xLb&_b.piTu6 Z9Ub-W ɨJTЪovxpÛ Z9Ԙn * a1uYͼU}/_N;8|ax Lfu39^ދ Ԇ^ Z9}Db=t5:a:"~.fI. Z 83-֭(YʖTID#<7,깚 Z02XIo(s 'yPջpӀG zʓ;;K7i&fк}:M5HlM,SmؒUuZx}/.]Ou+½ΐA_uL~*WBOO!oܪ`oG#ak/)qu6D؜P],-O *ݡhT4e#>, @gaNK`^ \T9%7T;~W6@fwR{MHЇ?!2_DA7ġw|-u䱹rC!B$EΤkh^;qΫSa$?P'WÀ[ ?ΤZ'p!mS25N1t3ɡ/YqF^ک"&pG 7P2zafXu}eșب0 q8$*|rjY".K@jMQYo-塚$t&<"?6s,=I8r1 Iv{Z A̛sv=OV~+ @-l- ?{98r ZIqn!4#*1뙳G4]e3xfÏ3Wkg0kq Cz 2h%0,8?Hȥb5}8fW*%{-I/k`o~U0Z~@ws0s IUZ"x:lvUl9~տcW|e!7@Ҡg ۓ c"}aUg7y3 $__ѓk3/5*?~LHn`JK *Ɩ]p{.^ 6>WɹEQAX3+-^ cJMzȩ1^KVgy>vͬca e?WoRZK߯JE] ܃|W3S%,ӂ^yM_ 3p_c*PL-腴8|u~ʼQGijJ}/[ϗS<(T3şB^ü*+&G D;- 6$3Njޒ(OxZjVwtoYX~}rrNsA#0-aC?/m;Otg1aN=T#ZY}YYΤ{uVo%}d^YusgҫБn: 2 7ůP c6F5sc;זA 9?A+{}*坰0=ay>Z0(x =ݫD|7i*fiyfz+^pF@;L Wōwr$j%g"\iI/`BV~Mxu, |._ :ʚ~6-I/azP@qٟ ©:2dU|z8^#a_ 1uf<AKg?c4h<> dЂA i8j3<[">/~Ҧ:KAakc~J(vkHa Ze&uT$P5;F'_5F ፞UdzUбd*P(.1aH>w*aJHr meȦ8;E' ,ɐ,7a -c[a&J_ ]*^P^,~ȯ^NeCh& C[FgabM j1襓#: :l{)jcҒA 1Xa3Bt҈:u-qB_ook*wp℮W54(Dיs DFxkI0_-5) weu{]bWa~p~UFJuràF658^r$Y[o 5MS%kȯW(r.B2+3-骒eZ3h!,~hD Y#uaP]T1 m<-a1VWq~m}7}{Ƶ]|nHl2nв+gODCgOk;1r]FOjp3zϕW7Lz+=?ǟzᛡ{| <~p*,w[BWJ/CO7aZ, ZjMS%kȯz<モ`gF~Y38Ѡyt $ 7'qph> ~iT\3)g0_ӠN 835 auI+-S VV0fpbWam4e*Wbl@%Mt]Ҍ^Y)YT&Р:a鷡!60g麤a;b>3N4AOeql 8E :dxz>kr]g~&6LߎbVUD[X硫D9aؤ4C0 g`uX4WPLKʦc{l4mˡ8/t!u\r0MnD ZYX&Y &!|KзK6Ӑbq*I/6A+wv|6B<-*adv>ؙ_gql2ys<)+S#!vҠ#w^ز}V0=j/# vhr0PHrK@3 )qHNHWd9U 7i"Sn:.6Z˔_Pd)U;7V Z黌hjr*=e @RU@a2WA-UHUBZOJEI~:9dθ3hױHí/c+ ?Ƙmy'Pt"U|0dHz\+U/S~D҅"A˾HIEBZ+Ŭ[QmgQo+ $vHOB|B*"ˠPt\szeMQȑ3R >x2\:^9*mz򫘸)!shTH Hh͠uf^Z' WhВK/>^r+Z\2K-T?ZHﺔ-\Q/ҲAK.%Vs붴\Q/%e~ɥ ZrhI4L-ĥAzE@hiВK/>^r+Z\2ҋ-zE@h_rE\z7\2\䊖%^4hɥ%0-%AK.zE@h_K.rEK/ҋ%e~ɥ ZrK.rE^r+Z\zѠ%^H@hВK\\䊖-n%e~Q/-K.hВK/H\䊖%^4hɥpS/-zE@h_rE\z1Z  AK.qiТ^r+Zҋ\䊖E" W/AK.h ^r+Z\zѠ%^|M" W/%e~ɥ ZrhI4L-ĥAzE@hiВK/>^r+Z\2ҋ-zE@h_rE\z7\2\䊖%^4hɥ%0AKdHHHHHHHHHHHHHHHi borC bŊ!))I;bOHHHHHHHHHHHHHHH2 .]v%$@$g}ɓ'Y- t欩Su7h5:nH$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@ Рeג @ РktܐHHHHHHHHHHHHHHHHHlA6%                 \A+! &@m>\K$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@&@VqC                 M-|HHHHHHHHHHHHHHHHHrM\$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$` Zp- ZF IHHHHHHHHHHHHHHHHH6lZ                 5r m4hõ$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$k4h7$                 hв͇kIHHHHHHHHHHHHHHHHH h5:nH$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@ ?#NCxOIENDB`opentimelineio-0.18.1/docs/_static/OpenTimelineIO@3xLight.png0000664000175000017500000002500615110656141021661 0ustar memePNG  IHDR;g pHYs!7!73Xz IDATx1rHqx˩D*rD`˚E,X:&`^h9ZV~ nb͘-$_}~Oϳ,̲p"{e^}k$' Y'veْ k9`pfLY^T^]#I/k?kZhg& V]̋)8xWf.'3>ƠL͋MbӃTk2 U{EɋUJG ?=0rL5cTk兦IFˌhfsq8vx\z(0x{~aGb~Iu=8+iRUr.*x01 u|g%ZjC6 nj1?:͋MXag~.X Ҡuii^y33lz?Ξ_}8UCw}Dzno[k7cVe#5F McQɋ͙t)mpa0k ;o@Թ9mi3kM S>`U^lnչ_~(([V^ Gwc_`#/69s[P;wU9,t^KǠ<߷%Y`ЎM_r1?9}NH:7Ugڽ/L M /zt ZۭN_\xӦ`zNN3gZPu&zQw赻dZW}i~tN9G)\?w;A>203u^\A͢^&3r|L19w81쪒8^%@z*sfRU_ӡ0q0ڽ~z P\!GyJ&O~?y y<(]̨o:/kuy>n <}w;XqO٩'D(]VKl8lޙD;ҏ9n,%95ƶX~&~ އ1V _l/g!emhv QwySsJizL/*Cȵl~xtjJwzenq1Hv]CS]LaAVG1'-v |.NޡǏ"}=i7n^w Lm.s95?W[ۦĨk ]520뗏m)8Svdʎc೺Mo^lVC϶4=F>G˩P7 隵18+Z'3󁪮2^M$Y籟ѝVU_#ΪJAPΊ4(O:۾5Pէz E(/6W BwΎZ0#Ьkh}wHcq/԰GYT:ᔪ.0M11ucxMc B7 ӫ^kvpjuݏ[Q:p95MncGx` l/ Vʟ_vhHUocꀩ5Nb(g=O1tZ܉/44uEpVzXpVehH6rs=MI?s<}5XyX76>x9}R@ƶ=ϻY@ \,gZ06gE%COyIk9*Ns9%a5N; e' kGg%SirEc Jfi*u(WiqЀZ YɜCШgVxr*e1S'&n>_nLdv}/Bl+P IJDYuK_ H轊!P<}:ﶻ4ܜ3tS J=]I焙U^%f .b+&RuV*(7b&نg!Regdee]FU351SNan^HpdAjNhS2:əK1gYe3otJ58+8w%/sB ZbpV:ݝ>&zm?g$Bv_}ͧ Zb<+`Lg 0}٢Rk}m%hQ޿)jK/La.1#HZgw ;+P;h&@[aR9ʥCs0m Nf>Ѐۘ+9107385U4 6ުJdۮ<esX1m@ló) b[fmhbb~dF[Ue٧YpJ33uޥcȼ_ 9}w,,vrKv) yyo9w!Y%}.(*KOR@?i(ǒ @lNimD/#j,= u((ޘ “.ÕCWy,N|iw㱸 epT? DX6Pyf .4^d|7zYʁ nXCh]}DK^4NI\wc@!uΎm5:nFdjI7lGPg#M[PuulIDrԑ.X@IkB̕0hVFESCNEijO>ړ-?:|Wvy^6 "Ug%)sS7?t z`{>ai~!߶w1]OEl#k׶@Su7r}?m\s!o,3&gV;}ejƝj05-#z[!:w>eN87ZA) 1eρ7Xӆ ,obĶjٱXl;l?Lea\ u:}Tv'Q<``l;~b oF5UY7J>F# ~l1zfJCt5m:⍯an=thBWp1TȆ87gLJFt}_d9L @b߮;E>ئ'6m=:\^)|݁qT9Dx` .^٘>ks=”M繯T݀븹 \T m!4V3N?kSF^|PuZI*#V>3ۑVFN_ׁ~[o6xABBUh6E/Fr81OOt:gƢv˒bc=,eS7lW=; ЯPH2Iwb1߻#wfեz\Itm7uj].ܫ10鵸Tԅ:+^fol1*W*aUڬUa:j`B>c|4l~ח>ztP}Ovn0Gx`L\6n\aSOb_WsbS`33n@v~qSǭiEC>P՞f5xw*/6(?}CR謁Jؕtۦ ipq6ԙFSBUe1jce{e}x7WejwԉoZza)e(]jgڃ7{Uh]:GDٞwޱ_i|̈́g̱,u.k4+FS<{gjv>*ަƢM`{0$$ Z_SuRm{:%3uS0+@qe]2wM(sc!y$\ۢS>R``gFEakC6EpYl;{ؾs@?4)/+%KN0MLS4 #CM.h]2ڴRcRb#P{`)hzk4{v$Δ%"gJݽ'X ~ݷ{dfW23Et+}حpI|fbs>墷QcT@b[Smty5%D9>wXt>܍Ȝh16ZfCcʍ( Lir.29Kk_z|J:m1eثtApm|Z}{;h:%;cw^4}SF IDAT0FFTZkcOYQ-ilہ_[58Fѿ~UZgM,%0z ѭo ºyʽsmsܦuݢX?bzqN' H]h bk6u^kh}*_tE-mg-]ThQhvH4루$e45l֎#]\G4T/ 0:3FJS3=4E?Sr*gFMAoZFAhzs1ݡzde6Ao ]>t9V.Ӄ>^pg)NbaD))9p\$JZҥ͹qnSsm OU?ɺ u w]z ׻VegS_޵H_/ sbz:{J^,::s:w[@IZ3)1OIc1@*!ZAagPFYL(mIOkHlŃ]K{K* 6CJm/eZls1~Gͺ6 +|;QJ'>1(4\;w^u]Naiub ^z~]1]w ʏ *S ۚ6mktiufIEQg%*VΏ~Ry~R ]3󡂉%'y HcCiViK]lC 2tyC<qhZOW $TUZ_d$@upVRu~P$Ӿ=ޱI2R[4Y_ELYI; MEqW{ E:- LW3Flw-w9/|wg&M[46;yR=eDەkvA;f|萢J(v58s wdX܅ itDUR)lcoŽXm1X4g3S0ꟑw?b4;a&2_"\vi\4faD~U}P} 4HU&tEVCrB#glpIlX8p cekM6H950ei24mafDUIΕgE*>~_jT$mSC鰧ҭF t^ oX@uArZ?lym8T(K2fja6?_y!vSbv<3[rumXy^lL9Y߃g.䗱Om)׃ӎFg໘ϓ9+`GU'+OuyV;]>X(ۆj;GP6QΜ~-{']]=b-m!0\TFu\MRS-˰qbGîij0hm{bsV,I<`B{u^9LhtZzt5? á+CvѢEpjqAH3֋廟4{v'. ]XvßSY7/6kF˵#}㹫Qa.ǾpUy ЏjYy^.M%0Ysܩ}xXi5Xi#TO>?^iGUP@xxTĂ g@3Ѐ h@x4 <<}߇Y~ ceyt/C |ye qDGڄH/fLڄ{Zb&Ѐ h@x4 < g@,֏=83G 1mh@x4 < g@3Ѐ h@x4 < g@3ieye/v?}w|cz=meY~E!IENDB`opentimelineio-0.18.1/docs/_static/OpenTimelineIO@5xLight.png0000664000175000017500000004354015110656141021666 0ustar memePNG  IHDR)i pHYs7]7]F] IDATx1rW/S}W@MLx# x%N"W D Dn0T^h9qfyzu2L>}W;swC]oniȃ_ ztIix4W_ knM @渒[lp-֬iP+Apd;OZ Ѕ'^OZ @Eo^]MZ Ѕ jΧ@~}4+lR7P+A:w6Mƫ[8_mÇ-oR ^",9@i*N&B}ݯ 7 Ww ``0ٽ_O5MB{|ݯ#}@F7Ms) ![Pu7Ms<Cф-E[ Pa$@$Kb ly%|>|̯~y4MOP|wo p O|4e4 1!l (|ݯ6ˉ1!Q G`T&v"L LQ8 "hUZÇ^EFi˦if#})w.x$oF\t*Mj|$INGw3 A30ATp@ *0 tNz!@ , }`" j續N>|2M Cmi{xB B`BBa!L^0;!_|i~i^?xwo 0AƠlfP[u7MsE( ܰںROF( y _ TPA TD*"@E"PA TD*"@Et0鋗iSro9.po?˻ "PA TD*"@E"PA TD*"b:l_w֭ܫ|v֍--}?G]t@El T)3޴y;糃.?~ۺuBQZ,WO}<yyrm}|b_`gQ}Yj͟uzQ'Muӿ4~>HS1P7yqֻzou^AHjK@`\-H?:Lb,|k;!SLPɽnkc?iZZo;+#&c@жOCru*z_,Z(11ۏEe@͢{zӖOz#0Z 0XPg\sr.4B]?T}]aߦ`c }N':9.=w`֛{=LbZF |7 RP4(\,Wi牅6szb 0*7jS lJ[^,WֽT:mց!d>;X;^wҺ X,W_ǠWN)bzE!D$v_o"ꑭQ4,%hm!LSlqxT],@6 =Pmm:.!1 LZ`nZ@]yѣ1? `6 Vhm\5MH:^zX#y йr4;W;u'؋kv˦if˦i~ֽ5o)y;D"Vd]m}';\: A`'j!\D"1o~EþtDb:hfa{3-4z=+w4Hb|v[O0E(hC{,糃{v6Ms>Ђz7}v^eu /HI&  H{I}=m-zS@"~Zo{uO&-طi麟VQAz$ip[!XM[Xvcbj c]4=M=5tk\I=;X 4-bRU] e#ޮjd\]E7-z߶ *%h՟Z&ո Ci>|p^W{(';Sru4LV."dqA7m y [7u10L(| @O֝H̯2}UU.k؉#N5(b: A'V)^ީi[t糃A0rZ?8E' ql{Yl5{u|XyxAv^,+4 ^jjKvT}G{Eg~%>n/: jZpϦi3?]j@{Ee|% 0^~l&>|vjE QIX3e`X,WM}6`=t&V̛Y}RkE`qbz4,5y?{ydz}xAwV m=<{42>1qb>si:4x/>| ŞXN!?yƞ.X.3(׷Bv%3ok>Sq>_/=޻x=c{=J~QﯫƼ\g,`yg_(SԈ6m כ8Kwٵe7s'y2ߥ}nA|,糃υZG,H'|v1.NnQ泃(=q9ݺՍ[%ɾ rL㬣̭Wg<2ڻ^8c^Qzcx&0^=ɣ+o?:م0M)&Km}LR^Aq~m.&cCǧbZ98蠽D4{EMAtʼjQID>Wp莎sGruSUK5v'}ӡRD18~e| Eژ?μb4-5|NPq?ɸ@1ޤ.Kzص10X;?[w&zkܶj("s9>:t\!~$QmN`[̹j)|s?{% ̧W&_GT p,Ca\;Ctt8+<8 pZu0M'} s'w@v]#W@!`80ʠ?fVZ lhaCZ_lyv=f'{kq{?';|G@n\Bt{{Ea0~;^}:"~.cSʌ>^OUWM(&LO-ob'`8Y,W%>$k뽍 ε43~m7&0F+tbo>;Y,WgO 6p\LaBy$cםjȲ! տ3R D0*[*-3@ %E"4u=Q+ў2Y)xl L/cXylϵgZjUE"pJBLΎ%̵G5* pݴ }єe0 Rx7s9gk0=&`_Meۀre1 {'+_Dy $IL$QY:~0YL0|w[YbO7ZPxltNNZ+ǾJ1}gXi29BxmUхq_@mbgG5}G{Eo"`R{jl?Zқ|zZ߶n-W(V#8R>].(nkrulHruL>M~9%]*g1r #^7ɓ=ΥjQId*L i=;r!Y|w>+I, 6=k5epeKD)׶^O *UQHP>c2ĂH_7(IwًwؕXzq̾޺9 ]1VYd;u #jz p@ JcEM$ׄ'rNuoW,U30)1!}F s ZƤdN90IϙBpJCkkTIL"ko=@N>+E]rv=rzo}?4:؁6Q`L^LJޔ˿ktk^\8~aރ(na<)NT|]S=6% M$vY)*2Ӟ0@{s2vػe??+d$/0v*aI%W֓ҷ D]uz4q!1=gQ8i Wi!4њ%z2L;4NjZ]ji'㙬sƔY)Z.C9:lkwKbvPmE]/\X>}VPq!R| HE(^l-- j1#~6"^M|LwE$ڼiNS $!V|3ES 4|JZo!QrtR-D d, v45/d|vpu+CQJ-u)'x!:APx="TlEM;(@]>\6Ms_!Ժ,qoQq:di[5d=Ϭ-Jq[D"øP&$G|vpd):0ߏɰqW[O$R$ǵL>ʰ_C8pI] :@b:cxQ( usX^u qx9%zC]j! Q*1q߁jdLI\.x_kث]k-t.G-m;_cAVQR fCH4_d|EۢnLh$N ] ]=bU^vh9xutغ 1ж=8w:^T '=*&ۋ1A_Kcc@kφXؐZ۝.KQXTY,WWjUGP]@MrD ˖丈h"CKx4糃*ip>;Ho-c_g~,+]MߟNcL{m:/adxx.W*D%G@+`9Cl;[s{$}VZ׸l9:|z@bEe’mʗcp8ߺGb^@D>=i2it!&?Ʊ}U@ۭ_^š>>gq|; l4:_<ی3^9&wkG18jro6+C@W]e+r=Ҥ?糃!]g֝Kŋ'|uW/;><@\_JR?Pί6C LGύP/-WV%LL%V/A+ʼ ++rۑhsXE}ذuR(=ِ̏7(¬Շ ˖ԫ.m,cCv+U˧;G?MLA'2poJ'/ IDATp)f:vC7ZF!xl+Tkuuc;vr] d[%4IغR8V}}Et Z:ڹ7{6]'#Zm烜pه` Ǧ.;[:Zk@:ޒm]ޮn9wתmk3j# ǐԗ[751dp8)ͻX[^\LƚZ r[$L\pBkkp+J FXKpD-t/ڊ`:۹û.o0;sqt;6y\eK7\ׅ/Bw-tGeێi#IGi} {mY~9;z9Gt9 ;Ťx|]NpqeKP'#[m4>v;M2g1SJS]n]ǰȏq>!&kw01\KAwz=\clϨq1/%U(wbrg+ˢPڶSuts1LE(UrDZmun`ҲwA74^6MPi\r@Ob?泃QGZEUK9SMDZ ԩP +'&`UL 0B90csiVJe*m:"x 0at:*@yiu =mYVBg|f>;@+{<=U)`nc0_*أ(7el8ۘBU%veuE-LI1qv}k>;8k;`U))k*<ߺ obvli˞]INm[iŐ^՞RCq>lojEE+A`UtyunjH]<ɻ]mb:b ѹr0'e\'QPbJ_};C>ww8txIJ2D8㖆8# @e" c{-tc5b+QhB*Y!,]$ #b{eH1Rb;JSaYL*D@>a-J[Yk"|#5ϰ-XxM{$^i䜿wg8S @}kִI9tr\-x,ko>Qior] Ӳ؇uj:>KN0 PZz x˼8`a}\669.s+S s}Ck߾mJ㪋# 0֕GBkMmWD*[U -m/;,t[ϟE˕mpOڻ)"]lm W]J2L6=CyOK[s^u5.PJ`qZJ9H&o;;PW _g/O.ȥ m0Vm/'vy"7mW ƏWoFOqD&JHzSȖ@%ntuǕ~Q{%8)޺%t$Džu }Cd@3R~\FUt%{'y2KWDR)EA'}l16J;/r]{A| C|7Xv/] e!zH۬jT$yn0g8s &)(>s%m2cws\~m]8VW3 -dNcLEOaecx=v `wm].̲91NoGc Ƿ[%=AƮگts^`>!3oKId @ۋ)o]lw p^CQ16Ե'j;69a"n&^' [iAҾ+Kt~<8>/ @e2M+q4 L].|槴6m3}_O](]y5@ٚM$Zxf[x u&ՏK:1mH*G/2nQxﴹr7*6{% tG'{lp U6TZGi\l qCIWV?/ @Ҥ^2>cZN;b\>n[R @tUUN*оX>lȦ'GPa5^r{}U_޵;4OBGSb^(8yA2{DXks.`(t P ~')U +l%Ӌy1{LDOz˕ȸ8^}^Л<{?1V}V ~ U>2Q`%njrM Ӓc;.:wqA 2.9h{,pu4@Q-<^zgjuyx&u" qi=8_3jxLMqC`&EJfWP:Ai]+JоܺAjwXaJc3nns>rekY0.m@5!*DZ,mG_) !>ZN `&5Vv/If$U|*b2jrܳϿ5Um/w}j2-&{9.;x @r_2j {R~X,W>L0AZ:88N @ #qJ فC1Pʱp}C6KD%v; []zH;xGnP G`o\%ҝ{E* xJU`\ k Mx(BEg-@Irz˖cק -1p_gm [,W3^gx?jMyu %q\db\H~V`;zAq3Jd/qf0U3;eh39+#&˕(g:^ǂEɱVW*X,Wo3t_,WGiu};0one\F0mOxL[6lW}ufz+-X1ӲL>+@g~ ,j0 9hbzAE7 w۽`O֭c.N9Ԯ͵q.糃ti{`5:i u+}}7g1j[|#@ Oab560)G؏ $^ mζnI5{렂ep]@/wH&~ϣ<ml^ s|^iYU=Tnw`:r SRTkaPB#]К@] Gnv՚P@9 J r9s['~w6ru|vpkOA|J,&_ftu0|˻[6jJ\UjpB\Go%&~szO,Wί}]`Zr%Elл\] hQz.6X[/u1v޳RۿL_T&d>;|j{<*M ?J*>3Wj{KmCOOtV-3ל@J rjv{쳨Aұע[pgBt Dž=x=8X1鋦i~^,Wƚ@_J rfbG4Zdq`b2w8pvD5U.[?E7'KL6n~Vn2@fxbYVjE@.㻦Z0W2w+t SI[=/4~fPߍ{U`7&xg}X RK$!/RzPj8O'W.is#giJ-U&(J&g^U?dzׅ)Srd|vpQML5vHkODkOc%.XRg\ 3lW|vsg I;Xڋ 6arKM+@\\=ݺq>/}2'n(ydatX.b!fqirD4i{rj/0Wrz +^|&0g DŅMUlr gchv:N9V-7twSױs(xvh}-RrWum0bJ;r0Wbb8wczfj7A4L9 o6;\Hg,ű=֬2uh2*EZ犿Ri2}Ks6/U plq^:r=f~cG}v流1-DŽګ6r-r|^EV64pmӿyLb)dyAO XY]rWQ}Zy0upW5V?q 0=MTA&vz9.sn"2j$/GOA!}Hg-MP'b8g9F Wք: Ere>vuZ.Ubf0@tzl}J/3ɫ(0gXB!$ӵɿov$V.&ѳOj៹%mcb@L e 8~~q?2csu`>;8`{: .ڞʹiSe[tP0[VT`ܫ5@. :i3\h>=E`bc;@.g<0WF1x?ۍukҬ~ZoX8n-A{E0@\ mC9?|a˱p}e"ZYR0WFAENu)1c `~{ܶ+氣0@}jŘ2x\P/l/ Wm d;vG!"x]c`]j;[.Vq7+bUuM>~50brZo?7V["ix]H_bu|"׺pc`XupO@ro.0#ھNX+'q t:#j=zϦZ~bbbr\}?tj.$~?q#g網'x oڱ[]L˩oUnc ZH^D'*Wz/TS`?&xG{SJm~~j6ص%job ;2.rj?cyXczѢǽn&%7=λj-~t{whZ+G.ɛܵ^A^= D:1(O4϶~!Qwxھ/9]}oЫhG16isa `a5X|vpbe+?>܍Z\e:"|V q-acPTQFֽ ^w-–Cر?ZSXcz4=EdR?Rqݶ@.}ȸW:ֻQ0:BU}EbFci+c7=1Ae[M6&k#Vn$x/\G-w,cKca(á0@:YaLWO{䨎z;ۨZ=Z=t{]>:vBxbdP" O<_F[Pߺoz{{aixѦ]bp@@thc2k)P&QM!C{Qo٬gtL/nG Fb2}ڏ媉=/}Ήh=[?Uo?lxc֟_xFAK:q8b?]np5Zti;/\z.>]T;YtX]PjDE!ef'.O'5Ӏ:6]փI 9ӻBZw*O`Aցcq14߸H+ܔD;R'[wm2BYCq\#uIdM: 0@vBXw(Jcb~(O7zHKTv2V^0,S^M Z%N͟)N/&6C!M E֢Ca`mhpE|vp{v}gq]xI3J "};lUiG < qdh7g7\[zNA\#cȶ7cGuژ@Xv awl{`o;xHQD\'!E>(|֜>"cVV/]ߍ}k8fw)0 [VWO']z]*ѭhޫXRl@QF [vU+-{(45Ҷ㳭{*qjje.BkE bbakՀj y\#O!y~^FocuRE1r\=1V3&%V/{u4{2mQI ko}=Z JN WXp$ūx&rry:CzFۍ`(ǐl\Wo LZOM6ֵpÍ1IЗ[ ,..x$.|?Cc GlL.?ٸh~oZס:\m|K>ԎtZݮ.fSx}_vsB-C}J=Uq㺪qz|yƕכ0.|Ɉzɥun.'Q6mz%T#YƠqmݒ߼ͅg-p &V3oO9?7q]t X׫}o~cEn{fн}W.Uߔ@=A @@="PA TD*"@E"PA T/^i8LՇW{ADrk3qy.c/Q/}͈ Ok$(<{xxЍ?y/˲Ye9߳,x @:le VY @֙gTUHYwF7WM$³̵@:;M_Zv73$v؟|]7Gxo @ʨ<؋nRFpGx@/H43g; َ(@#8Cxhg" ˲^:g|,[fYvK5Urc~DYd^]S8c@#8@x430g4@#8Bx4308g$@#8Dxhg`Q .:|g`О=<< $gY&6Un'/^<0g ݪ{af^vh '%,@FpPBx8³l@x-43g=;"8؂g^ve[wuMpݳiMڧ*5lAYOxm^SFp`ʳ,+²] "<j0mAx <j5g@ 3P懇eY6y8hg z\Ug˲h =Xa&P Ax <j5g@,˲l< QOoh,gG fg3H>{*X Zg~rYyz$'cm$S=q49Ȳl_ 4dO㣣Ut,JX NsJHyU#1gm . ο<|Fؙ<,mʃbtcq<˲ `(H)H >N"<5j \f~ȲlʴN]lv=N2A'TB@" 6yc~tE㤙#i3kI+QyH3M>4ZT xg'3!]O*ϴDAS5|1Aߩ b@5|iɛ5|mF&`DYpj geUFAҢ!J&[kowCd,+'WZ*Pq8@>}88 AXz ո`麀.⦡Zk?U 5.<&3m4'7 6=3Jh @`~TWtl<\5;Ԙ3y8g'zg \w<4h+ں40'X<1A'4y&'˧k]4gmYAsǪsNX㻎>8+hXi Y3}A@2 Bbqy̱LM6Rs8ɴ7:O\*F:6gz%'g?Ɔ#m`*옾 D?cAr)W!49k;Y{ _c( ƥn=-HѪt Y`u|yv*]ocFAq)oC%Y`5ЎXr8CAHwUg˓ʣql Jq:b_ 嵦[38|qAi:71景Aڙ3-tkƨ\N]qjC&\#}~V קt{SV ;؞ceuD~ t>.v _UX?H!@xy F?<;\+{gXMU/isnf#}*6^e>5j~˲C̀e`E˔ǩJ>+-Y.MZC|M s9+PO ϖlwZjƏ+3dk+6}!7 XqM1'D(@U_6~FR}Eh}MP{.f{M[`s1c="<. >V _l3˲S4N[]y>Y9O%@4=aWk !G6k?<95%;@=yٴ8\$!)pE&f0MnFwYLB9/N>Ǐ"}<7x[)B#!OTF:~Msc?* djd\J_=%s(fp@H_ۑ)*n|2lKST9s1N2sjg3@]ok͎2]$Y~ G{08+)_#ʊJݝ8V*L M7e\Igm>py]U[]Tih<\)0 =8;V'hi-f] P(Du= }ŝSu=sS v5` TuiO}!hcM7Ў«7Kz:>S .S0{)_Wl~mp?[.Xg8`u v^qXӺw1 Q' B+g;.fPӹRh 0͠^C(‡mci]U(B^k-9C؄q5qͳ?w]wug(ӹ*2Af-6lX`lsE%^Ik9*As1%agkd,зLɕQ-8+B\4Jd_fg\A7b zpbF)8s}Uވ%p8i5qm->bÀq"w]k;u|z.ʶrjPtNB+U_Ea@U  Bѥidks mwiޅf̵̧{ $ #]NK48+*%m?v Ugbb3+mHmx-UV}|}AQֵj.x0W7]/Yi $#ArRYV]R s]ɋi/okmw>G󬗛QPhۏkIx YZ1bu]@; Pn͌,fYrϯY+\UO!g˽rtdžaZgKpvۆ΃kA;pR5UTpB>܅eY!V@y)HmpZhjm:-c cѭ0^gY n| Ǚmܕ~PM72E5W.-σ~gggp2򱼀niS}3lZa;R l54;e~̩<2lؖ{4F(ڄ&(yX\u$2ӁѺ]?eYvQv(\9g,;mӁ1*~s 9}q;X ccvNW.{@ZD..0j{CI7 aP'-m$<2)CZsmΠ:KOcB J|6TxCCXy8\9T~]?N7v<n9\ypi9}akEnJ!4zVgD_¶7q@ @h}[Ev^uVpxqgAAl!7LKru[$T&BMٶ L=O˅n~vڸ)|Bɒ^O|ZmxfcUW WFx]naHWӂ>o9 1b y-S;Hxk:V&Ĕ 7tB`M&3;ezb͹cgb*2qK+(4PqHxs"U"<is**P\G3̺ Pq7: k_l4D׉Qgf ^À&t筇w U!!ӶѪ*].twuf}$<طN`!ul;O:DwZcވf$v}\bY݆kR Y=I} }u!njGYG w+(t{ R)7UѪmmVWc3)@k3'=]hF'[{P{}/3cpCTu5*R"bS͝Sv|a4lh+8~e}}gRe[9:9n?u浱K>1Y" v_ge&T=Cb2L6j2*$K>%@! g丽Im+c:CSd0 @l!7 ![3G;}'Hm?EUmޖ}]cOȲ+cpIgg(dbWuf6W#p' qqbqxS @mIvFqPwop}0xley**ll΋\>XiɩÎc³ԦV g/8 7"Z=c[7L?HkDx uq@lWxz k;l;WǕG4`By`m}6~QYgއgd[iw]L̘ u >[ʒUufQFr?GmCm6"tku!Hljݳ!GOՙIg`ޛ˝qwRi: ¶=WZ{3+Us7`:n.1Urt߶b> @ӏT$9T]K=I厕N+wN[ׁ~[g6񂄄s:grU˒|bP՞f5xw*'7v풂 ʫ]9L׼Y @ohΛaG!݆:ݔPUgY̻X6³urBhyd ^8Ԕ2vpFԯ 5ux S-7UhX\?#s ϻXԾg3}sX&Mnx>L}TNEvHH趽TA|)eЕJFMu5؆ M}p~HK'<+ 3\;Ih b;6;la>LnE_VK='3A0LS4 =CM.k]2RcRb#XybO)hz*O[=5Nz^ygʒ)@3wE3 ߶ٕLyŕ·w'6uIӜ3Wy>Wnbۨ?ӱLSMƼhJXyssXtw>ڍLO8>ZfS* L闫r-29Km3}u ЮbR1nw4-ޠۿ*l5>R5yom}' IDATʖ7Zì9^ Ucm>m$p̍WeApqt^ҝ +j ێkY!vmu,^W,#֪Y@*c`.MMf4]QejkY2gzK C*}-f}Ӑ#yN0#j}̳`ſUxZcIq֠ϟz93 ߣYNNhzǡz`qm6ۨCm M>9V.Ӄ>u^pg)AbvÈHSR$HKkH3)vvO{ ^4QYЬؾ޵,Qpvm1uM]T1ձbq9],[ZgOU 6ՋECT@Kg\h7`Z,^T{j7B!Bm9/O\g&A0%fT5[=^\*@9Qc$0+Dm[P i¡{'vTάg*BbZI3$]T("1 .v[cg'5ݝnK$KƨD7BlqZqV <0m֢㼯j LiqcwZl'S5589ѺO7ZxԬ1oP2ah6Ǡ0Ҕ֝Mئۖuxy!nvb dSMZ㤛btiYFx`hԈx8:]vy<-f4eҹ7iM[Zo+ &[ǎҨL ؆d*|kHq,PA(h0UIଠI>'Q0 X[4YeLYAJ,xHSQǞq͏23CxˍFpԨKnzBpV03#]cdG>;3-M9`b^TmvZ3 "]YRv#- 'RTEݜ% +d ʉRބ?Xm:1w5-4@Xj ¨" ~TEov0OdݿFtsuJ4sՇ}UQ g C巑v].u.׿՝6SF2@+:;@F8 |RT΢4i603^" VqRk%5s-&lySCݹׂM遶? ә9v:?:/zc!h@Ow PlJL!4'3 OxVZ >J}^L_qR ٻ{<?LVkiOEqmXQ:UggGL>(փӎF9"Lc}P :`P<e(s>}$7 {ԯt)* jLT1TyEAyU{crGtCorW L\wEuĬ+H7Tu4NZ>_1Ϗ 3m5g@ 3P Axx,;4tpe?{it<ϲ̄g9qmB$7ӧ~iʣ 1mAx <j5g@ 3ye']HI圣` <j5g@ 3P Ax <j5g@ 3P <˲,^n~ Th*˲5©m5P$IENDB`opentimelineio-0.18.1/docs/_static/OpenTimelineIO@5xDark.png0000664000175000017500000004363615110656141021506 0ustar memePNG  IHDR)i pHYs7]7]F] IDATxARW7t_ꑇw$/DI"+@(E/0G-v` ?\y2dDpoZ2NwՇ*?mUU{UU} ~;13] VU(㬪o~*A:!7n.iJn[UU T ЅooJ.|p?~۪Nz:q 3#_L?Z5 |o{# tBA@ Tp@ U`@ VP@ W@ࢪ-!B^ Z/g:ՇWf㏟K7=&3Yߎ, B!  FY0  J  NY e ÇNTUUU7?(@`?UUiUU8.B@ql +濪/!HpJr?Jek?}6~{`X0!               ^Ӫ8w9/p?mO0!0!0!0!0!0!0!0!0!0!0!0!0!0!_;ݪvUU|ϖGS[Ň_.:hxcן\.`8VUu\T׋'UUmϓ{E_@/k '1[UmjΦ+B>4gI "asO<@yJ<꽓 EV wO쳀@]/.:I?:W]/N ;N)&iΖZ)֛P{1 oۧ=uP$#JwapA`Pu6&b5f}޻ D`޴nZQFSc05E):혓rZ .-CWUu0}YzE7-z>^^\D hOBLץ@0Vj jNE[ [ /k +[Lu،@^iNpom~ !jO>7k@TDbB#5 P\RC":D24mQ}R{%$FyT} d@gi @;MwN@ku8ߑtt]׋Ht /UUm:Y4`OxVUxQUs綪6~J{i?V@W{KzEI$I4uX.TBx^lk.11Z84Gm5@#j'An Jd= pbI]_.S4;  _zry|,#nUU @zof[k I)ɛĀ(~Է30ЂzO醰?}zSؽg2Go}O1(f suv9@ez O,ֻ[׋Z p/=Ϊ:X.Oמ)Po:~OǨ"0]uH/zxc~\V׋'{ok\S _.20\1X{n{)0Z)z;A10p/@!TkcWU]/luUUիץהUU+1Q\&ڊTgõGIQ _\g1(<_.S3)n|7N!jkNHR_Ջ\ޯf[ _!*;1K΍?a]/Η^&p:T.2ZoWۋεֻ_׋8]z¯m; EJ4vѪ?Lڙ6 >קi:SOlALÜƨVUudZ$B'zږ`{5p]ꉂb ޮ=F?T+|.bWqZe7#H}x9/Ⰳb[8{SUU/@;y\.yqzZ?{E'^l{ݧ3#Q]/vzhM ÛY?d;/z!3 Rw[fi[؎jQ0A+(X{t8@l6 DUU]e%S+j#00| fPTkݭu#juW/^e+RU}6`3t&VlUU]Rknz񤪪՟/9c~<~?K&+ꁅ^ wٗV6sm=[=݌=*30GH^,W>|b [~UU3M8@zq9XG㺘(َ+.8gc*dqx,؎GVޯ[1y21O9=Y{W%^)jD6׆kw\ %Z;l"۫߹8k'sէEw=)qB5Ʈ{Ƴɨ8 OWwb( l9sv"1~s e[uTWo\_Ŀ.'B߱q3>epF g]`Xe׾5{-\ͩ U!n^H YERWȦk<@[ \ ݕ"ƪ{] H;,Le^11qrp _4-J}߯l`+ Dtei]،n7aE0zanA+w ɼm}9WY=F }OD c11q}Wv5-G#::pߍ"nC30Gr~iƀ}܂0CQUzeП_ Vj\v+sy;l[kyvފKम%_}6)I\2XB֞ɝ0`--n24vN=fS['mGAXe]׋9L(dq_![7sYVA]/rbWf&V=BVN{%E#w4u=Q7KM{=xV>Bw}JI1|(V{4kaf.vbХL46vF¼Thݙج*Pbrv,aPޙrkB@YnR)w:}+kZ (gۨ\][:  'j3Y"&*rONQLDE&:1\Moio)DK6Y 障:J5Vd}b?Ni29BxGmUхfBi/`jbﳝ)}G{f0ܽ\5,n^t>qqgok-WgQG DǍmV `+SY9yݽ&Oқ'͘ciڌ.ub!e6 FkKbLAYʱmWpr olē7iV-$D|"竊ǖ3 qFf̅g9Shr4@]Dܶ͟b$&1 w~b]rv=rzo|?<8xm+n&2)y]z-^+]^Obʅ7=,{WMbu*>?ljkx桄7~b]rh/r|uXƮ7]#N=Zo_ZE>, iվu-Fd{6{Fȟ`ħQ؟>z/\ʾ@#i5mKdڄ#\ jk'r|eʅUӖ9nDo6붓EY!t3V-8"fgTژRy&7G5vQ`܏{6(e2ICUÒ5[)s9Z"2zqֻaO[[\.OeH9\eT89E o6 ofX✍<-jE&6 {/DjM6Б_3vK qքu-GJ8i(ӗcvCnuZޢ `Ӷjj#,>CoK>OKTP-LzO2 `FrJX.o,eXG169J!f"1E uWaCW>|^Qm;hb'NG|,=tsx<D[&չn#~_-IE!&;V yL؏eOq?m帇|l娵`s,ӦUԄ-ǩuUIs$O2NmQ7&4߷Bס"zEX$}fVZG89|N֞)HLg]TK:?F@+`9C;[s$}QZc\N>O`bEe’mʗcpH|›@D>=i2txJƟ*Z x‹CG~h{}|uب8>C|l3L&<z%Jە(_zҧq LWAZAr 7Zە@6bvtjcC΢Gry?乫Z{R :=z+pݵho7B.[/Ͻ^{.<tm5pCxn;6j0]9  cOŀN"ObrnC2?ʰ}C#mȸEkx+T6"Я*~=J0~v3AuQp>F=@婕v w<2}2ʇqv$Z\y>bQz:6=3J[7(ӮWF7,[iw`r`Kܶ΢q>8`ڮܽ(i5w18{#ޣ&&ތ“Z@Jk IDATf8ʇuqI3!-]EXCn+TkôuC;k;N*uQk- 4Ixq^ Л'&o28:7*9r KZ3_ᆝS'2uxqy]"Wqh{$Ԏ@꾵\VUe)k*=Q?4e.u{Yh0Mm[iŐ^M=!\EZݮ؏ev;;[tQV]FǑW~~؉dl_?&g )ۊ^lw@ѹr0'~~; ;z,(8 0mmsY_=褉^=ҿ_tJ^dlDpG `:Tڣ*xceX~_vדlq{-J&N'&bԖw:ĉ:\~xpҪҝ)u;ɗ>Vމk`rKruJm"3OGJ ounbϨyz$WAכHw`iy)E{MpIt7ȱ`пLL?52M(ƾOpziB~LfbeR4;}N{W2}3ryB?Ģ?69/Һ Q/˸a.@01؅1w}*r6c5b+QhC*\t(P `~.b)(%}T1;>IH6Ї;" %Pi+kM$1e?6~memJ^^i\Ϣqj1X*NJ~asㆸmb*% "mn~}¼4!p}5t ʮ@I{C)~VkYui̓K-;Ĕa$0YۖD79m99ھ֡S߾mJ㲋# 0֕;B6+m"p}*nm}6ݗ-Ϣuн C;*/`q3ݦqET.d\wk`>/ĽuUWi qUi :NQ)'ILɻvfեktgņ6ZizWHW7ӳ h;\%)A4nXv/ǴM2U"mq"'sp'=>>Ùs4IQ̖@+i{O:KDZʝbPLGXo!3Ac*z+"x nm 0|e\u`x)X-VI c}ts^``zCf,ޕn/PA7sޡk46bm8k}O6LyP䄉ۙHbz/GnK,^qj}]4jy{L7k2Vss3?7i[f|N-On3DaZNyPDRiǺ#0C݇ rνmEmۂ(qPgky4))n.l pTXyyۓQ׋m ^*<{"?>7vcS*ڃzb|H u6K:Zn\TҹYC d +Զ7ł(8yC-2{Dh9g}0Qrr~')U +l%Ӌ22u?Y 7녂ȸ8_}Л<ŝ~Cn,&Z}v05wWD}3=5KZ* ȸ ##dGɶxpm%rs|K LSqVKa-N0A1ڄ~?=0)Wv7e1 ֵ G)HN_|ogls>[u5@wrԉ,B`5#uq.KW{#CO`*|s0NN+zߌD]/doۨ`2]߬녖Q#}[sf~ ^4N6ݱOmL^0]9گk5=~8sg! cV49Dž0]]&%knl9\ƉG^F]/2u8Y.o)3o3ZIўŤLj)]L ߬q9[{qmhSv>WRc6;Q61ֻoN2v~* +)a:wooK@қ=.)<`"gnԅ[ |R{Jm Zo3u~}{WgPAkUI?z&eJ*>7L xLmC/'gcKzqYAQԎzO?2w}5H:LʝAGZu+on&DW3c  m/]-~4>}QU/u0Rbk4+z] 0WozG$s];o%3+fƁ 7;&*&/C4\=qO>SdfI+!+IK0@fxVjE@N㻦Z0W2w(t 3%+x]y9!ܠq<_nU/hs+@>wK$k!FORzPju\'+4Xg%ז*3]r%ERoF*LU+JT06jA\=@2EIƳ4Xɫ"՛|-ձ)yg7W}>*A1`f.iߣwgkO<Kb#5%," }zqH4=MN(x{j6x mX׋Tẍ֛́8_yD 8oWf*nlr`%k`x\;9"X/JAb'xvh;])bS,U׋t~V.wPL nL#,Rw3ߕݻ&3)秅PGHe,űYePe*T;tѭ"s_R說2ھ@GJޥkg s9IuH-MK(㩅_X%gAA bq1!xզU^δXYth%/mAsj F8kIמ$&5}n?lbeu]B`_2j~~ؙAaGnƿA ߫Ga`a:^Yѽ :;•0WbH.uP|yz`v҄qzr)J :Зix˵'2 Y+C@!=YG ՛ݸ=mOc//7u؎wl^.mW&SzS6Vߗ+YA^s&̵ry|ޜg]{jc ؋6{4^<[7iű<}ܧ4viz}]O]^=0))5~ZέkF~`@'~_i?p_ڽVև&PubE궂O n^|_|>];뱵^[`UQf!@?{0ӹ `E#S1H|wp)uڈUO !UmU-֧t_y]R@ sMCd_EЧZ_CObBmG dgry 7 \7\n⯵}kyO 0K}\3?ab񪃿ۍukҬ~Zoc,|f<ދAFnMw-%/z^Q\ nڶo\t!ׂS1LTd!s(!4e"ZYR0WFqENu7)1s `~ֻ׶+d0@>bLZ\׋ړvɄb2G!"׿fao%'xw*YHvyu𫊅}zs-n'mEXIý`B'/Zs :N,\ۅL* oqS|Uy]Ybq@gqMa<ֻCpz'(& &/z~wI+Z=T.$_Ƹ|l3ps[ׁ7c<=$"b2i/Ux+fWq! N<_Q=SWUOa͘U*s-Q~'cHqS"7pǺ\ӓm>(0+qeatU "'wWёz{ޣv y+ܩbQVkԝ%mzr;F9mb,k &_CiY.mһ,{'VK5]G/qX]$Z } m=.~@!A!WuPh+zVa<nQ5ԏT\-K_522_ֻ!"| 1қ* ioL>dTB[{׵'/O[׋`<U\:9|o["q#xP{sv(Csbs ;`V}Tj`%4LL4~X`w/~&SeGm#XO+S;áIE4#ǁa<~j7@q퉇ۍ@QxO"l{ên6q3~tx hi|㫾dЮZtzƶt([=w(ڈzjw? AVu=z;_# hfLE{̟Ѷ{+n~hiړ{cƧ[%xNi+@:@D{'R]]ׄ+KЉ +B'KOfny?V#, )[j}n,~RZ3 b{gߦ&OgI i ߩ_gLz?m64fhk&a[AD#urxdN\D( k#v5 wZL:M@' EѶŀʳ0J8!*=I^y]/Ncu g՜ڠ \t H&ULBn 4Cat%Q y7 ߮p/&$+alL䵌ryޣSڣZs+W+7NnNYa6/f@cgVu)v6C睂 .92I,8^&zұ#+]O]kҪ9zn 9ߕړNx4~X._=UL" Y?~,[~C,io8^XdpC KRh4+ vǃŎ'?YZ \~Q36 .c;&P陵 ^bTzt\׋tR[1n~;Y1)ۤ"@oRH^,^&щkW{ȷyu<ĹT3pPzHXRzw&G]JQ'u8^&W~DgQdw?^L$yV#OW P9NC `!V3 Z O {u-O'Zg_EMg_ MPtqI5R(M) p4P4 LO^/9w;ryK\+bŊXUc,{Z~.vj 3uT ;{.ף~  ^xpuH.6?&n.VkŎ?V=|YkgVŚ{¯pk1I}}:IčO3`*sV'+7]G B3P.W>%ǣ&4Hsf9V7+ޟmߗ]wRPRGYŸr_U8mL)P=^k:52C'b=EmsB׬,SW>|&fs+O⧫BEH7?ڛi˵'iyy+!6O.Ord3ǰ\[rţsBf Cb%5þ`*-hU!J%n8$A?`π/Xb qN;XF C8]+Ϗ r"yeBG6(0n' 1\i$O1⫢py,VԨP+l aPօrҹZ| CdX2O1ʁ q΁O6@2-Ix@4/"h2fڀ h4"<8ƽqO<Bup8@b1Ds p-)pCitb̙ lAҿ7q q \N? 3q瓰nIwH{31>ĖaX;v`š'ҕ0:[[l~ y %+`U1*Z3ctFT}w≑l?7Y;Kp#l(B #`q.@Apep/ x!!4h! b8 n7D H d"DE Zd3A~E#gHrE7'CP7 Gѩh&:-FKЕh%Z@3eڃD0)bfa,,K20!6+*jkփ `q" \!xgf|ހ^|JtVB(!II(%T憐H$j͈L!fWOC$IdE"EؤBR)iG@V$AT\AO>I&?#+(+(x(D+pf+RحЬpEOaB1xQ)ٔEJJ>/EEECEwI|Ņ/(*~R-,zzF3RiYC:nKs Uz74b z+J ʦ,e|*ʷT***y*+T\TyJR5U T媖R=1,qѧFT3S UV+W;֩6>KJzaJMOt[>n\5}5ye4oh~bjjhjzk[jOҞMx9WձԉՙKCgHWO7X@wY= =_lz'|_0ՙ~\f%9hcb 6ii0lhf`#QzVAc}HƵwMLLL6753M2]jhL,Ԭج9|yu EVee+Ŋoժ˚`n-eC)հ]lhj k&Ojlk}bf7*4 MxNۜn;3#::qquԹnq;}{GB#zxx|>l"o ^;zi;{| |>>||{|Ye{o/?:t&n|dT4<'t!$':goHnR״_o ?ZV_wcce HƬƞa[=f'V,99rg2vS.\xۥ.:;NΆ+W_mu̵ۧk篇^|#F̈́oMs{;>~uVz\zNv<{t1''JҞV<VyKP_,x9}lϤϕ_,4 z$od-dKT4#7{ tK*(E?aL*.: a5j/@T. GY.z-$7(@<2232|.I;$[(mߔ pHYs%%IR$iTXtXML:com.adobe.xmp 2388 1718 viDOT[([[@V@IDATxxTOPIƺc]ڪT:"jwU{I OmJ܋.XXȺd ݆($&1y~?Lɜ{|d9ssdrKz@@@@@@@@@ U         @T*@@@@@@@@@ &@B          S@@@@@@@@ Pũ        HT@@@@@@@@@b$Tq*         18@@@@@@@@@ U          @L*N@@@@@@@@@ &@B          S@@@@@@@@ Pũ        HT@@@@@@@@@b$Tq*         18@@@@@@@@@ U          @L*N5kG?t@@@@@@@M`ذaSO$T@ N2U        &pRUkkkJCBUZ9) rMK_R5        @4MKci"$T@  U'O̵@@@@@@@رcH2(X@z w#@@@@@@@@k$Ty-brF  A@@@@@@@ m$T@b b8E@@@@@@HL@ #$TeB@@@@@@@T@$Ty9z@@@@@@@w]؊*@BUD       H\h0 U ځ   +]3d gzzzdeWeHY!C.SrBU*}2_-O0ZZyZ\niyVzK,Oؚl%㿦ZoտZlqkqx@@@@@ lBᷞw2th,Nr#+{G;pMI;E!c7Er`됎/ϐ b&@B@@@ d0jmR>vKR9c`Kj啢Iڥd,y;vWbun6_R{pLmJ?M푵HJU{m;X-- E) iKf~[F**F\T.ye   ( Um~pY3$RߺI&3z>qЊ-|նm@\ eB!zU$T] Q   iTBՔRiZ$#lw(MQޓ+՟V5%2cE,JՠF M}عtJX^Gd&lPSLKBU29s+3ۥFM{"6_,G/)O$sr<#zN*5/eT   @&T>kG&PR'k5Y>V U{BUe},࿔CmGeذa&I3-hO%T*o*ˈTVIa   ~PB;e8Z,t4YjJhl]t{>Gjd햩B2mI,˲=dKeэr/ U_'+RmԪc USJ"5e_O򦉞GV*6a\)=j(Y(QSc5rU,[I1^2myi>tٮITvįVT}wKg,7S,-PHܥ]%o$FIr/@@@.fO&T˱?oKQ!(^M#|q#*>lɪ UY2@@@/ d(TMUW2U,S<#T%JɲPe$T9tľ   X U"uH7a  *   SGOdhS)Q9P5Wv~V.%*kDD}DBU}z6-@@@zlBUgU avR߁u O>ŏ<G?5*?E   dT UWэsmd^²Pxd-5@@@ x#*ZVX\%}:BYeQ~ŏ*3, UN   ^TBbWT9ܹTF'2$D.KGW椱2D;( UGdk?wA[p\1Z1rtlSȹVU/Qe_1B.GS.S^5T, UU j4{5r!}Vv?+ͧ? SÞW\!^7ZF:rJoS\Hix$ j@@@'ࡄ*[2.~TƻQuYq4ҰHF5[SVѭQ6)]j=Lz7NOq$r moE-   a%THK;)8bWsC,I5lGdifGjAa,qoBcfU 6ymR3RAaH2kr-HMz1yW"eA6$P -uT^.c$ieϪ =?׾,?p5p%TY"<5c\H2XǝZ@@@<(ࡄ*R9cШ;ϗ +řoԼZ>Z$s`^d=qTd&T䀹I`iv)3X,4KRe%seOS%Oh/5@@@/*<ӱg4oY(7`LB?].۷ƭ%=zM9%[G[9şJo&2,{fϪw[b@Rbf)KTd@BiҠP5(T   EO%T;X%[%srmc 6QJ_32~OʾeLJR-Hɟ[*i2fXW6..5 77:   ZTBr^MF: dnM>'TPJV[LN?8Jpx@aJfK"_BUJLdUX/XdKkEBUr)2%K   h?" 4yJsE!,v&$5)٧KTNcMXvecc&J: 9ROo*ycfk9SZTmS fJJ-_l^:1~~@@@GS U**s8&&>Rw^L2]zd%7Ќ lַk/3},5mYX#FlYw^"Al@PJfNS:/ȯeUmUbP ג|@P>BUFZٸE2/A lܖF>S#:$)zH28!  xWIB7O3|Vm?*_?]Mr󴧣^-GΑRW2NDsdΣ2'?& 5- 5{NY;'?Z7Cc#'uYKD05S_I~4xNIt}jF"*A =(DN5h4ԽCYu-U0EJ_-{]{   *Ʉp6VP)-{Q}ImrJ{ʖJdVȴ ;3"ŵeCygmq$aH2w}kuHGDwH[[\2E^~h,aUmeSh"C]n볌YNjHg"Rw~;֖H6O]Y!h'H: P5hT   @O埛N.K/DF~dlލ2d,6תΝOj%W?ñrcޘ^ c@@@."&TIc~_VP:VR-t'tlzG)(Xz7GCdĥqᅲgiD-.g,YR*i=X>kYvo%qgg?z^FL8+ͻ-8fbK=9{8-6dWzS       pB-zS$#TPS[GE ٧sԪ󜌺gy{FLrkGJZe+G$]smd-|sL.P,>}- y.fkKmq UN(ֳ,@BU@@@@@@@,x:*sh,h.ZZ7}O-DzGQaylh@? {ZJJx^ϺFYF 5SڵZFZd+Fך +sV>zciWs_|!r㔻֑GP>~f_K='ߒKnp$ϑPdb=$Te@@@@@@@ȂDNʊ/ P YXT:_YS#^id7x2~kJxɋlsxC6T@nxJfL̗afѹo U{qq55cI˺iR|$T%lP e@@@@@@@@ >H O Xˆj5M%-9BU 喢 (ՈP VQ9|*Ֆ-6>Yb@ K{Mx{0zeo UwV A+Hi]:cl:~CPeذ08$T ;"       I_$T>z~u"V:?$z.R* U?vߙ%k&=u+q]%goN[Ǝ4t?o U!5њ[県1MHBaP58Ԋ       @&|P%=%ZV3촑9PnQIY*Ek`>ĭk6SVBU|VJHdnPeZ4($T {+=|ث6 wYyyt@ҩWYJf"^7Ng +/xe85tjf,yt@ҩWYJf"^7Ng +/xe85tjf,yt*r:fRO4tl[0UPUE[òMyrB/e;g3F2wύ* UrcC O?zbHʼq:kHq?OY,eeHxe6C fX! K2bW`3T,l%^P+C*xe6C fX! K2bW`3T,l%^P+C*xe6C*JG[1Q/g%B;UP?=]@jۮw7p}oD=rl4|dC]ѫ dnu4o Us|Aٯ)gV[@#?ہ=(kP])7ݠHߕ~ ʁkP])7ݠHߕ~ ʁkP])7ݠHߕ~ ʁkP])7ݠHߕ~ ʁkP]Caylh@6;H*;WlKn)RY"K&??F:ʕ[p5vXVz:OHOܒܦ[BU|rm&6-%^l|1YFJ+Y *+i(iJA++ix2+06ʬDœJW(R s*!^iJA++ix2+06ʬDœJW(R s*!^iJA++i UG˨;#dw$OVN7=,̑:GqΤ05>kYv,$}/_ypو^TS}S UCfHʬoKJE3[ʬoK'^ly+.x[4tn̖G2҉WE3[ʬoK'^ly+.x[4tn̖G2҉WE3[ʬoKeBJZ8Tl+.Ѩ>і>^O @qs%:]lY-_ԫCL}(?qWܒDŽ*tO'_IXҲg 0r25`¬@=ʈ׀ Z*+#^&j+x 0r25`¬@=ʈ׀ Z*+#^&j+x 0r2&T)SP#E'EOFRXVəM e}k;ڶ;;~lW9en~'{Q47>'TVMK< V"R ;w}ǹ )GTgY,:> 0ˇ,:5@,N >|82#^+x 0ˇ,:5@,N >|82#^+x 0ˇ,:_'T( Ἤv,u $!_c:_$ uX:yt%RZ 'DE.QdNuL5)>JTB؉eJlEF3veP 5sUm+΢/P֬ 3]m-yv=aytT5T(=Mʛ&׽O5&3pTsN>|{ȵ5e~vrYɻRӷPMPM[dI6e)^6sdjϻW|f}-9uebZe7+r}{M=ßm?/;EOy gZ|R[-ڊKErXەPEx/Ⱦ7߽̞*&uc;>WOޗoC\~>,Cb'ʦn_ Pqwy޼xNKY<<X@B2d2sG<<^{y:^>x)m|z _C瞇T$~$FHr8{BHZocj=@Dp?^l~y5{%ue̓yY5*>a+Z (e[nV߰?"-ogd${niմ*!l TRVHׯ)Y+ޖ)ekxK_UzN3]2{4k++er\/{G//{I#cuJ4l-u/Ww%zlms?/Ikܱ';Odܬr5\YAͤk_/?ϭIџ[9vHֹc~;L;Wcqp5rc-~'lznX+f<Udx?9^@%u\_z">Q2KޖFaI>7yx=^ɮ?smx񞇟{~_yx=^~>Bh[OMsG9JP>X/X9'O CO.w;uO!erYrкY2qiuoDzVj@Mm JPuj *U՝ʽʹi&"ChwoiS|\RO_lF{pV="sUq__]Ù%eZk+G_\_Gd*&R2,OUiiN|T%[[vܢ_un_Lxy՝pH1C122_j]otA%‰p Keut>6$"&yXNC_īY%O 'OVf߮FpRޮ|F~-~19~Tb 8={U=r1?$v.\{^W\ޕ_3)aK-~lW U-l힇Oy6^Uj/</?oy÷=/ƫ/vH O~%(YBUhkzO9rOI^)_mdX1'Ȃsّ +%皭h; !.]TSnME{!}q[>%BuΣfFkٳP6@VP%nȲ7UrT3qAu?{|6h'̳PnT!=Ct~~zQըG$T9rjU/o)wyKaW5L^d\><}(3&<.>2F})B̝+lF<9|z,u{a40M uY%KeŦ햻z@0h/e̜4?){/w#,}_/=)^?/SxIdܱT["Ԑ_++T~W'lJzVKV/KOߍ7GT?icjF@ӫ|&s.y_vO%gHH3mu)_݀o8<3<Tk#~xo{ 1{^W?χ2}v; U*iݶP\8)m:Ls\OU},KJNy9mRiU="Ld;&"k־v3б{Aq,_7!ke7˯>PY*jkBUdYyVluEjDkˁ`<))/="sjifI,!/%TK͂qb 5VN(Iήy  K] UG_\._w"~~zhY_e%"h/OF xMh5uO42_ٖޖe yq BB; jZqi.[&rM Sew<22Ku`-~F%٫ɳwu@Nve_Hho},~W|SE:$~S[v9~gdz.ΔǎU;ʼ|T~ב bۢ=6gznLMٺY2|'Gk,Yd1Q\U}çw9wJt_e잇{~o</X=+Cф*ȹĿ=sZ~Ey^z7a47.={Vηu9.W^=RZ5zjV2g9餜9{ ^zUrk7 _=NE[o U#kmCwq/~MJⅵweŢ/S}TE4_RܺN~(kޖCoQ_Rk7m o+gD}&)˛Fpji 煭~ׅ#j虥 T#,9z//sS]H-٢tސ+7_ O3֌&J~l$n?erb1*\$TKߍvy#x1^nEmq\\zŽ|J=<|/kzïr~=ǫχABJdHQ U,8iψM*=5EӇU2ȋS!%v$[/d:e荽Fgī6$3"Wsd_nQy"^#fUT{lۿ%SKަGPSI|H^)oXKm" [e>кejTSP˫˽ ;ϏcͅFyRx|vh~>5ju7Txgsn?Lo29seg½뒺ЃDe,{,zmx~5ةt"?AWʓ'/ԔonX*\)I_̑‰:֟o:ǞwicCĀ*Aؔwcc*5#v*!^>M|$ouDp*)]rk)2{0ioWY:mdDZ"˺ʵ̷eRRnd=R@fŔIɐZWc1C¢[k[WSUBUJ~gùHy0ozp}jjGdˈN^O|yaL^dj7ϔ]OʾŚk!^]e.w[$DM L_-Gմ9]zMkGb^mL%V@A}pTU{9~WO7 u)P)P#鑃ՋeN^IMi4͋KJ9g}U2rJڝ~ %}\_zcx%nzï\w>x_{W샞yx=^? U '@~KF <#器/G~AvEfO_,Mtľ7|v585@8 Sam&Gd)x;Hlȓ˦H>7i]|aQ=TBQP/~WחJV|=2l]_Ws//O>)S>//NZztmט_`kKdohgd S.ijclYv_ ǎUd} %_G.4fU|~~I/ϯ/w<9^?d>;kW?9^9~NW~xE}$T=<|/u}o㥮/?so~xEL%|^=_@KR=ʪsX@e|yoP짩qdl8K<‰D{՚%KBTTo3#ʗ~$9o/]M"ӥFչYCB%/4I_)b!WKeѽH k#u/_.ykW٥XLʡsMODjTO | OH$7GllYoO?gīT%Dc0]^ٹZMķJq9+zzSQ/7x2<~xE}/S =_{y:^{yx=^>?tσ*=<#}cBNu^ +zD5~ g+=\2Z#        6LZ[[Z9 Ui055kȏ~#r-0@@@@@@@ \uURVV&O=KNB݃5@@@@@@@@HO@@@@@@@@@.o>Sg'_|i^)W)]7ZƌZ         u%TjW+_^)u + šGrcIꍋ.$B/Cc;D\ cruKr?"V@@@@@@@@4 =ay %C%aݗT%vHf (uF d<0L?q4        @iM:zsN[q^yi*9*yH:Q6uLHPuR 6Lښ7;cF3X@@@@@@@@)%T5BnSsbС򛣲gC$ bZ>@i" k-dBQT~J-f'@@@@@@@@AHKB_ +So+q)A6<`YGomd;̑J6_>kJ U}*oMgo@@@@@@@@ 8-zHm[GwdA (Nyw}\6V|!lA y_DwB?(U?/'l$/         :55Rs؟Xw ej>&AHf3>dtdxOBţCO@@@@@@@@? (urK>;~@̻ܱ=59v.Mrkt* z5"piN[+SdȵߐG,ykj-[ʄ&)*tNL?-;]%w}HyKO6ʑSr镣$|@6-F@@@@@@2*0*5cåh}ŵeCy}Z- yrZe)~)gϜϝH\1Bv괏tvV? Sbnyu$7p/QDž8z2<~v^Ź ;Rrŵʘ1˰t|i69ukei2zH<}sg "KBUUC:3nȴ=+.^7AWʭKێm*4P)-.\Jڛ~[d侗Lozyqg8侁e:Q{mjQ3gOҸ$,Whq~yi        OPu=mb*Dkkd_z)sZyAeY2͹r#y ZfG{)yȄ'.u6nGȍU! ٴfl:X+ Cl|yVN9g7E!nؑT<jd-MF|wKVI՚m ,%Q$n79LXHG /KDHѽ 0_fMK>L#v|5"uZ?stFz Lt7.]]5WnܫioN~^Nz.f5'r焱7ON}!?HJ^˥#oz_GOU!Ʒy>jMʗorF{WWL7:lsc9.2)<%A.7U{dϖMG>C,nc6MW?/#mq_$?\G | |i_?.FSEn%#O}kQϚ,)ըjTH#_" L鳂      xT_`ǵh?jD$5RK&YIھzz8UVUkUUZIa1 }Yh+c$1qr"TWkժ`#eUDGyk#69dPV /C0I 3nݭJ ʴJL+)gU`Ul40牣F+AZC+IPl{l FJ\14۹༕B-TQU+CZATwt xԘMw8F1ω٨i"0|m#Tuk{Gϛ`5J0G_jOGl)Ixˈvf?=''PR"{]E|im9a9JjUWFjC{?uu%$^HV2v}%%ZU{hQ&ٯ$:ff+7'׎ `VklD@@@@@@Ф_*!>}J<($ >ԡ&H)Ӛy;Zk%GbJ8 `&c-dKFO&X˷-Vj g\Ҡ\|/tO2:,I zjkӒ97a.dn#iCVhG5-d][Zvܥ)'ϞJ< h9U,I F'DQ=xѶ`NQ>͢zqf+@@@@@@@Jr~rݤuŎc5frh;R?ꩯvK)䘸5".)QF]zD*ig, ~T5ʵ.$j[zXV+h^ j;l'Dx*b-T܎ gl';f”o.f_2       @/ݪI65X9Rb#yڎPj[\@Y= Q^D )65gpMqGliޯbG 4fz]*qdM CZkG,vq3鬡*PeЭ[3ݡ5[r;ZP,Y̜vT_U fz_Mٽx'-G %b=2߳m,hT  dH஻@@@@"п*фܿ Ù/}H1d|qnIRp&7c&%,%ب,%ݭ//]߶VwG A%TFʞ~9աўpBF@+,)Ӫkwk ǛdV,"Ϟ۹a&ŵ=nC*pڔ f>ǵعƵL '#'@4(:"ilOD2Y_ϩ:Z, ;"Z팺],,_oYrߖ;Zk5%]s4?ʬ[S#v)PpNݠFQkb$}_& 7}I1e|qn``?ʏQMm U9qڒ(M9>ZKVl K5şJK4.h&Gm5)%i6b3 Zmql@HsԼ̸>]k}Jj5򌥌VuZH?#fN3hd-3,_o㠄UDЎ몵2Ϡ? ߯I꛳-{uO4O^VGu/#)Y[2*;Jb#qV'={]A`Oԅ8m7yZwwe @@Ț UY"@@@H@4<7T/N5]TU*+"Ɨ ՅFB4bOYFgv7ۊ0\ד$źlPbl=Դ3/cI%zF6#8UO3YM Xߙ ch\eGVc UMV21+|ͩ"Z*v4mvbfbC#IFZUԭj4z=5lQXK=uþGke;%k^כ`YvFD*F,#}Ux 'a{Ojk:Vi'bh9lzyF@@Of@@@@TJ@e|M: jΑ2$uT>%@*8d>RstuK@J(%ؘIJQ^O_mwNh5fhPS;˹aVPf&Y 'Fӏ~BU6-vn!{oI9i-xXvIzѥRzna&DɀH[=:B F5ҷ$hLV{-p^Xݠbq̘P￳a Ԅz*kQ c#ϪOHbqZ< Ԃ[SYv/#)Y[f`VT#g|:YFs^ ^A@@%@BU@@@%τ/_LuկHL-2~>YrLMu'LG_I)c&)e'zֿ7$ķSv4OGӲiѭ9ݡUWiA{adG4kQf\<ű E$le2H_C)ЏE)^Fy]hԂ$VYC,'t&T>SUǟ`H۫#42*[@+m+[8U]XV֧ 2]=:ڎ~%ִ4#t:5ejJ5uRԒŬC[?(`f YB@@Sԧn@@@>?NyNܳ~\AY"W۷y*{c~n~﬐r dov+<^Y"K&} J}60vXJ!ylDlyE:"K&e[^-^Y,= Ye| dG.!z[ ~ɪ2g[;{:OHO>7[ U6cdqlg :^cBٴ?7gtwaYW,geɭcIgg|T{E`)P`W6vAh+T@i?Yu])wZ%<D7 %j @!%u-PHIM}ϙI&! 眼9=|3'i+UWܨMG>71_h6ېC;?'# գFjP;xMMh#5tP-tMCG]v^c{[jn :xtUc}M #B_vϝCul]پSzU FE_ mw /!  }-pmv    -n AK;Mus4sVdJN4kAF09iׯh t&jOo@Uux]xpv=VT)vuB=:c7E)l/-ƃw7fR8 TQ]C-zɻt$څ]nVSnb=;Z׌ҧ7;R]aD^*VaBfqVQO<^u}8UfޮWm>tΜ?& c}馱9Cm    Pe˲)@@@@Uh>N2UX5Q# Vy- c1 ^)#Q4jx'jQaAY}s)+hcJ=\u&LIexWH9'O5if-quGt-!L,VΔ$^ѝ, O\ ;5%:WP䁧"ԖJ I޷$|@b@@@/@UƿT+ϔ$_@@@@Iz(gIne#4Ĵ;sFdl-vYsy|-oi wƅiY/w̪]ͺ9:L) Tog)cy3iBʞYtZ3)G ib޷>gr1\|ƎIך7O}]HO̴ztf[dSeGy:YNV }Tqz^   8X @U[j}qw{ uJ瞗@@@zUgU.գy19%E)Ldmz66ժU}ML䇕dii4mmQ+i)I.TJNV.4WVDOAhmȺmnoUfY/{@@@p@Ûee,TV2PTI#   @ \*szscZSNJ>Si w0WNN<ҌOM:( tXsMRt$'~ڴ1Gs(|3n. 7 ط%1-G-Gt'jZX_m[^ԯމ-nl?ޱ)3B}d_ow{^=@@@.Y#9Gբȓ[gq9AS_B|T_4}V_9Qk靏ia]Uҧ[5pp 9};5%=Z\}a:qQv[n.WnIUsmҀjԉki2gOWn]tz?   @UDEj~?~q:ߩ9ը뿠4sblMJ`Y{Zϩԧ;\uu i11vLԟөN-J#F֟~1m~ߞ;&iԨ!:{ICFuFNCH6q΃޶ Td   A@ _֤UI|+iӗiEUSo_/f`CQOcrKO{2B [Tan_U(lIAm^:%,|-.PU}fɏPzaӇIhAGC' @@@@r rr>@U%     p: TԖnW}qypr3z-o=J5#ie2y:b_ TN~2M7=lM(xm(}e08s=OiWժesSF(|./ga(Xg7?|$ݾ>`DviѪо<6.?D@@@n]V _ w@U    ,Peu4vVeUiқCcU9{鱯Vk週ǖ{ҬM[]n6ϳHV;5eQ- UN.@$7*0Tz$PUjRMZ Hyo^g^efجL3VW{FM_eyTn   (@q{{z[#   \@g TVjFL"$߭ß^۾u]gF߼-zMZH_lְIsNUPc v[;UjZL5=}VJQv(LUP+oUdmonoţ-UhsF +5*=-@@@@\NF ^F@@@=ʯ8'np<-t`ީ h T D'7+cPfb$^w T4 }$G9 =)Q6Q7#3*vUyNM\e{fS些G?[kcb`o:2"t>(ZWN #@@@@\Nc5rMC=%@@@@ ]TUiқC=m+si &Ukc| EOXz`j TY3uʸ\ziR5vLXk?UȟheE_fhÚ;lʔStw0T4sV! +s    PeÖm[/i6 ҵkb.2/     ] TyrK'T6I&dLŕ[Y Mڏ~E, MvjʈZЄJ螛}s:IY UmyX7 㹣o!O [Vq^{ @@@,@աo    8\ @զ͑gSpPfeIFn׀#:vsMM!KUYfUνAGZRrBhFzZ+(Wfv'+OִU1/@@@l+@ʶc    8_ @Uԫz56i9"^ j0TׅX ́zf(v5:LY3۵=   qf/    KTKV@@@zQ@U/i@@@@@@@p*Ռ#    U)E@@@*  @Z7ь(ch6☕ZNSe~C_ycmޮfұ iʈs7Rtl4 ˸N)*ׇHSU#lo#^R}uƌЈݰ@@l+@ʶc    Ўv`XKzvo8;ZQfӊwҶ %&7iؤP /c)XMN/֚*VZ)LmܭvE֪}|_[I7nj,QŞtu]9=y(Q6kJ7EKmV}^4>|fZb#  T   . Psx s5iY2 +U|b +h}pk? TݬaeRVv.%Pz~kF߹޸9*9*+9Ѭz]~_s=[M"0pɶuJO5S=7|KYga&̫C֒=y1עU唨ݞrI}E@@? @@@p*g֍^#@ |#_Zyr7nU#'h |T_4}V_9Qk靏iokIJ0U#LJWUMI|khjؿ3u\ڵC}zURƌ]S#SvF:;zÏevï_d̛ ?0h߻zi1np3"*qدjb`c[ LxwpzGλO:PÇ|Ew >]e1ErmŷJU%e&ǹֶ#果,)_=pݢ[ %e6_Ynē꠹o .-;|ŁP/x.P]ԅp]KO1[*YvZ6_'|'N/Y5T ^4n2x;@@p3|!   N`*{n Zj?7>\[1e(ѓFGo>&G(2bQufnrgߠS;6jŋd@IDATDm"S%\XsÿFF4[n<+ZiREQ֗'l)k)&%ŚZZ~khUؘ(/i {rTDҮmZ2M)B򋾭cqQ9Ң-y1 N-g^-,1QǪzX뒗ٌrʈͿ5ՍpPej|wdpT'5VE Jo(cd׆j:2^oad#T/ߠ5mfMov 1Xt#  U`@@@NI~O@7NF*ڍ%(+2QNk)35NCUQxԤ"W֭Η%*WqZnGJ,F9ioAV9*3hQUQdħ*"#o@UPQGh9d\ɘ-JGz* d12o'1* l+9;REܻkFַ\U]w _Iܨf&8&x$#IU_?ɶU+O/v^Vؿ*  8Y\=   @Pm!   v PeǪ's0HȦ0!Zp p0%ɴxɷOom',򼲟UyGKrlV{a$AS}YqZ$JJ‰5>F0ri>씯DIx@1u$N~5F$=`۰ ^%4p04c| {]i߼WYVy<_oI^D]Ul  8U`֭: [n    Tٱ* \ D;.DB6$ONMm;f_s࿆}C߅r)Cea֥\"RxlCsx ;kTH"U,j;$1hաY?lO <)-;W$vR߉)Ǭe_/?䜙_Z'O; T4-KP@i Va  8HQ*k6@@@@ keThNQsCJ]J|PI}/ ws4 , ~}7m=K|'5/ ^ TyrK|u, OB+h-i `5Tic*4%577N/ O'+i+mЙ(WZK8 +kfɊR_nf4 Sؖ=ȷmwcѤ_|RN~y9(ٲ#  S>vThU6@@@@ ke@5?4F򉌠5@N M|'rpɋ C+3yN8В'mwcKG4ʬ,d4ٵ@UDE*+HĠ]HH. vd)K_INT*sd$^<[ ߓ*SVEni:PNU*=hX.y  .җ+Ue׸!   v Pe73H@QVJ= Z*&nR08ce5co'#ۍ~ڇWz28POm'fL XeF 4*zr}eQ[${PUl擾¸aR_@eph-θЗ1.jJݫDIVrL .T^_lWLT/7+{2/2:ɺaƒPp*W6pW/ofnqxD2)   . \aa_   Y  n Y50XFЕE/ʹ0j1=ҨFzH .NuvrN?|~nN6 A̱h7E iqIū^f^mTmL6[VmjW4hѕ;iX ͦUujo>ʵR_[}5l3o|94@@@u~slf*믏Y@@@@NT 8@ (faKuc?25 j[Oޥ;ט$ ;uk|*ӹ'55ҒRmڹt\RzRIOq+kR]o6ɶuJ<!  @~zw tmi}v   tW@UwX5ھmk`'j:AKܪ*+oi;Uz)+7_D:ZF[C 3 *TĕS^rΌR5*0JU{?2k3!Cs4e$i4D@@,ۺu.\'    T٭"@np*zùEZ^-̈]7O!xL!82zW&QN6vTڭ{@@@@@@@!^@U @:7       N P崊_*۔        cz !@ P*"      U#^ae       \VU#T9z@@@@@@@T@UD4@@@@@@@@qW2:v PeJ@@@"tF~j  _+ϟߤkt4U^t2By@@@~/@߿@ !   5czihW Os&a;{ؤCfi-ð'.B@@@KEc@ "@*b#@@@zKMKr+=n~Mmƶ2:&_kQT8=YeU TՐ   qf/ BU.,*   `/4ߓMVϦ.к{g?Yz~sꩽǕHJUe[[=*.     Pn±   ) xu`\$*j͈yl{F{=\`x֧ULE&P@UJ   M@U8Nj=&@(   IΨhdۥ 'ig- 99k:Ŕ @@@* @7Tu@@@HE7G֘SVixS6Z]1W^Ҫ7_3j9sd x x}m8ul:}ӏ_ZCSt3ҔFx5(Б۠A~x׾iǒZ|~Xp7v\%@@@@T"@)@֝F@@#@U*-BKk W}Oj^4Zu@٫_nu)cZ=ONfu:DZ1(\UfNX/    @RUIYXt.@s#Z    mog*IZ'RtWU4 v/k]q.,ZWTth ]XqbM^<~v??9wT~h]kBZj>~~W::|br\с*Pj]ڲhc>L4И    p).Eu@_ @@@$z^G*Vtjj. 5P({)]t*v֙@Bkߩl.vdk{\ӓ2@@@AU=ɦ@ _h@@@RUϛmmg_ӌk:@S6u:eN ,%zo\0Tf M5V'A?,T-xBJ 6KV:O>ЫviFxԭ6L#@@@.TY#   U<ۣlSFN]6!TjSMU2O_G5y4|r(7m{ T<4NCCdu6_SIn5dZ@UƼuڶqݭښXd1@@@@T%[B~&@E@@S#EiP:)&q7UU&P5-8UZʘM4jػ?Vh+؇f riH{VLa!   =E~-@_G@@V(^{FR<~JʬWSkVZU]j1 ~Β>KV_I󿡇_ƠjoCR^!-lTbF 8ˌeVq#d͟xoԨ]ˌ5>]@@@HA@U H4A J2@@@z^bm?}hPPs#oyt{E7/Ewhб:BLʿثcei#mezDO#K3B@ޫT]6kcVzy   *@RY~[z@@@2 \_z9%z]_LvH.W^}N~I;^+S>'RKW_%ԫ/i|e䗴CuI|}2sK!KceUo~?tVio O[/,gz9KY^pVo9[/,gz9KY^pVo T9^l$@FH+|LFMBWW H6jBlTBR@QebP/#PlԄz٨)tzd&FH++$5^6*F ]^) ٨ Q1R JFMBWTD@ d*]&YϨW2.^MQd*]F[d=^T컌zٷ6zFwomz%S2e$Jbe˾I3Lž˨}kg+}Q/&YϨW2.#Pe3*({|@SeuzŁ)yG@lzټ@qݣ^q 6Jl^Q8?^6/P\W͟R/({+O =bSeu@UO@TT*ev|@GRJU=j/WRhGQT{ARG;e: ꕪ=Q/{!^PTюz٣z*evuH+U){^C^J٣GRJUT٣(@YE rzE%r~Q/g 8_Y-rzE%r~Q/g 8_Y-rzKY `#wt5m7{u۬oin#tŕ(5KϿz@+fmq]ގڱ5_4sn} b*17իn~ԸkbnW Cʝvj 1/9kSsuz@ŜM oi4')wD3zoh~zO'/zP;+ZҢV4 ʷiFZ̙¹^1uu0gyt N27ի*.oL?|KP{gCz_Jw^MKw+4Eq ,O3_l]ߧ\tq5+<\]/^puˮy^.z!ss̓@UtY}*I(fSՖUJtujvX4>%?vU0gNRyOjc-Q^5ᗓ*C/xSwv/TBWZY:uX:>_JuX1y;w;Bpn:5|nWӑ">朊*@^9coM~ߊ, >2c/wsk+6UڪƧ [:aMե7eXJ ;룛]^#Wjfi-a|ny8^7nz~ms57ˍ<\/7^pkzu̓@U!) -PuH;2)Mve}W|@1u8c:zOkɁڶ,077b3xD P|e7)YyEʸ̱}9_̹]q~5C昚dhkt3WziS/fnGMtm藝&|܄G5uCBMi=^-s8^l|ф=⬼0aE3zKOcF]{kNWcv׿D?KLj|[nzХ<\[U[y^nzkkNWW>&Q5skEH*0n=1s\aj@y8^7nzEs57ˍ<\[/^pm ᚇOL] PDd UM*yhVKrA!défڿ7;c?tfl.ј٫ Tu`t_j{/N?R?hsEp~yhV?oFޚz93#մ^aWfdzLֳfk0}4i^cұcUʼn4[b^ZOp~5+1՚nM*N7jnmu2u/;x|uڐ6M&M&$#^Ms89?BU髫4ٜWދ9XfD J;z+1akcVzT"a7&?0׫iS{]o,!]ϑ#¹^Q'Ya{uogNW|7 r5׫ƭ횇[z~kNWǟ%] P>ytMmyf1CwJ;>0BU5EqNuk}O;5J+\;}qM5}R>+Q~L3G[^!3p8H9auzv1#t9^ֱX5ey|鼪kfX]qzH7SkK15#4yU;_6߽ E)*{|yigu{eʼn5t]ڲh|(qRm:^AUɹ|Jq5w}<\[/^pkzíkNWu̓@Us@ Ewd,@UoEU([ԼW8Wu 9}&+dV^вΞz:ԫ4OߦyOl 2/7MjsGTdݻ^MG4\Pvf7ᩦ:hhbUԬqŴdn5b=81=JԢO3 MެwCb[<ͨ}z.q?aGЉ9z?{Rmv^ 8~80S?k&Z7_oR#s?ճ+׶PIx˙NW{?[˓ T%{'2.qXߋϯr5+M<\/7^pm\zír5+y8^~>t5U1g"O@be*I./w|=vjl\`m\4a*Ѹ^סO3~ܚj4nr9x, w[)}%;.~EyFc evCޠiYWF]r"xZ1BGPԽojgFY>YkZzyv^0S M=DMg?pؽ[U{f>J,I T%A 8| iaHH.CB ׅ._Un__y^nzɕ]s_uDbP+]5#g8wD7u@&9UFC>sXך剿wps7K^HLZ1#kF1#瘏T񉲬/N)N~^FaѬtL{{$GEkft`s23S@?U~G?m>8сс\G7[ҧ=hN3q3j ^*~XkM5fzͨO#;s[jfA {kaUMɿv_&mn|ḶίyOvcW׽i".xDSGB™QKor9zO\~|fݥ-;nE3 LCMn~Ӳ>vUV#z]4Sizdf5 -0߿t' P?[o!׫G1+z@Q nwTvvz} TykW/l.zˍ<\[/s~񚇛kNWT;͋nA*XqE,@9u+r(?@X9|Tw|@}/38[_^c qŨo3KV]{sQk4 %s]GnWk٫W[zy[/χ^gЉo+5|v~9ԕ>Sh]^ɮӯyz]z9ͣ?M?j1jҥ@=~̞G)b-svR/D3t٢0c}ڳVmYv_53O>೚?qP/<ҩ]fwz?*##ô)53 !~dKSxU9a֧< ,uwhRR$Ӧm!EkzBE}i=Vb\ۦ ֚NU˶ /0nJSUPϡk;+Ջ1t\*-^BѩBu1rKjt Y8|3ŔB-eաg6ݨnHnc&+T5Z>u]2ՊURSl۫E.%e)^ Jr uό -Gq]ψ%vJu1uQY*ZNw  я~/| z74sa9    04T uaT pI-Qv~P.vFc˘pT˳9z;kf}_yL|jΎ6vyU]Z5v K wh0c[5ox  0(..Vvvz-wyt @@@@`H X@Q-bA[8ʂuUjnn+mlZ{VQ rCp- n d[eugfYq:}p*o6qXU/2@4Vo`[UiwxEV2..qua~oμ=  CE`ǎo;T8@@@@FG }Hi);wz bGհMKx>o9o9?.FۗOؑfs|ho9i߮I{.>}[P٬8yo'sj~ʟg +8s۩9fsw58 #7 tۚ7 zJulWČ˽}My;fPk԰S+k櫜c=9׬vtrKnNY  d|P~{=M2$@@@@F,V{ [' 4nM\L&&3GLj TUq,١Jk?s̑ڵfn,u*eFAI݇/Ɛ~j^i6_ 5o8tu:3q⩿C;`U@6<䆓"@ *RzV>:IʷΚłRf^OSFByeuZrfxW{#EO'fZwi&SQ{/l G@@`|՚5ktaM4i q    U#`SKG6/xpF3Bfǩ\ݐG57iFmtm0O@9^OBHO2I T%h@Jjߕ VF-Ug:TuIcDcٮ M4 pKyڟWUKtԉ?u@7;N|@U4d4ߛ2?q] .i ͳ(GB[tjoRD3ح>.Peʜc9s^ Us]b[Vi/R'|Nxup6cUm+3>_8J$`UG'N~y[Ǯ_j5GCMVI?>Ǫs%)PU PZQM-mevm9,w|n(<Ub6g+VKZk`N.EiYrcW%xrkEf>=dcTl(s#@sMUZZ7A_h ?k7; x@@@@(@$5 L#A ; ʣTr"#kJjbĀDZQ*;lU pY ڭ~|ϑXcm%:JƖhQ YVgCS5=#nM48³ww(3PV.¡;w'*wN< Eƞ[N\λ)r@@a#pv,.ߥ7UՅ|+R]Kl+ %?wڜw@ϳj?؏ǖgLd~s?@@@@+@; NOݔ"a; Y \mcbm}j #EB.VeId7@١* xC812+7!e/5\UuaYhݙA`‘ e[%UV E0 Rvo*œ? Ze] ڛ*|.`}<Uoec+`Yn*?oLI:ꒂP.>+2yǍ   K[cǎvK&zD7P= }Ti9?V`Iiu6[ȱ#ʝ-qkE@@@/pͼ@@GKc34>3StqDGZiZddNۚ:i;ɴnFoclh^ґCji4eD0ṕKmGllF&ǑСuٓɡ}zc5keZf}ƌQc4}CfBTĄ]ƪٱm܏1bƬ$.Нw'I)@@Q/n:}՟Ah#MRfnO]v?UffljJPOCmn\ݏ>G1WlY;I1qfԡmojgBn%scnΘӎ    I Pu8y8З1CZ5<6]Z5\Ȕzb7Q[Ws_P秩S   p뮻p_(E4ώӔYssOgVݘUowf~ʯ-bDבzZt4]2wF疞Ujg?wLMQ%3bg,e׭ΘyNPާ=Wy<=?ݣF򑙗hlomt χtyi.UeӤ˴%HrvMJum4>~rjn-7Ph_t^>V iݗJT-c͑{s+.2> u-=M:P-|8`u;T9R|E@@@!,@j/CC tQS{:l<*#c-y  A[oճ>w}o5AU&Se[d_oGrd'PG`AmTm0@ZV͕XZ*kr晠y%vTfRUVMW05ǣ ş_sQ 0 ٥j-YkqqP&Mb `9-Peaϓ   G@Y+F    ^xANLm76m}*mֵ=w3=!)rnVTJ6~a"-`+45} T9ݤ T9a bGUS-3V3F{7}BG_fשURB5{ -rE]9%0Z- x>!    P5rז!   #V`/zOstw2*򆊒*;TyLN3'Xԯ@'TisIQy)yr6ɊQ7_湊5;ձ~8ϙJkoz(@@@@+@z    @>B c̮M=ldUCNh`|5[QG :}v7fzn婮sԘݹ|fw.Ao~y;qVn!    'O@ɳg@@@@ ,]T_Ob!winUGj+TZ}طXWtwJrE TGh}n>POsѓO>Gi?@'Puܩoӧj]kSkh`>@@@NN    #0{l͜9S%%%Sg!o"[Cُ }qeVzUZS    @T    -\c=6]Q?*ѦN eێ6yTߟ6w@@@@t J&@@@@N>1-X@qB@@@@#@j53E@@@FG?Q}3?@@@@,֦ &u]w "    J@U*!    Y#GhҤI*,,Ԛ5k8     P5<׍Q#   VYgyxvmց#    q*    455oocz뭃 e@@@@FѺ@@@>}t- Y0l@@@@*@j B@@@H)PWW;O;wԊ+R"     P5P9C@@@8)5555k~I"   \U#wm    0"8 ϧǺG     Pu@@@@`Ӽy{n]s5#    t/@{     A{/VYY!8B    0T c    (x`g?ӧ?Q(@@@@S@`R@@@@ ?~쯗_~yS@@@@`t @@@@a'`LxbХ^:π@@@@>@@@@ A駟֒%K+ /LG@@@@O@4    ؽ{ۧ .Nw    #]@H_a   0Ǻt͞={͎    lU'{@@@@_?n&x1     M    CJ`ΝUWW3f 1@@@@/@j!3@@@@F@QQ/QGGܙ,    |cz@@@@HC=UV:묳XR    H.@@@@V<577kj @@@@` k@@@@<Ց#Gtgx@@@@ PuT@@@@$6;;vl1cַ-566U{4 "   "Uh*SC{x\:׺| LgW^4jixW ]=sΒ^ZC}վ6՟uLҬ5~C+<}Uں$ï]:MZ(@NfջڲC}Zu˜|E@@ &H_bޝr):uj| w    @Tϋ 0BjY3W4yvӷYvuSc335AUrڜ@*TE3/W6ո{LjHRեgn -FU{ lO'aQO9vb[~Y*Z*Pեݦ@OJj^ s1S*.V|3" |v}Cҟn'i&}>7@@@@JFzW+/7{y:uo {+3t٢8RK=kՖ5vj<*M͘hU!ze?K=^22Д5[ͦEj_S?}YLMQgĎKWwt=~.5SZ4leF)KÏVJTuʽo댙i c~\G/6+^9^yZ}Wǎ駟I}5V;xjcj+?321t_[4'9cvSW9IZZS[CE5}Be̔ʲKu.L1MבCj˘ǻUWVL79%u~H6=r,59XuGѶS&)T#ھhVU{~$ݝSq2Y4^  *p-G/+ջᆱNsNRk.     Ћ >d_UF*ViywV瞬`ku[OQӗk r6~}yeVgtU'\ !y_>{uZ_^3@*ˋ\-m\V(=ʭ&BTK%u.[%Tum;+TScY5KN_k^ttSauw@A@@g}6&Le]s5i_Y jFmtm0fq:]Oh칑uw x  @ /??Iwhɒ%I׹     @` Dw Cə]] ʬg)sa5t[:;hp弚|wlٹn6;%N]V*kpn: ѱf[!iQ((aǠ[&Pg&ݾPUVۑcUW*ɉ(deuq ikPt/q^meGwrw*m\c'jfzVe1;IGz.Y+_WWlw//.ϝq@@ڵkSN9wʳ:@@@@ (m( c'P ؓBE A='P YuT p`EVj+ÝuvvZGs s]֗[ 5n:@=SDZjbsji㏭I<>2?r`}m|0鏗C{ T%ZhȤa:[ZZ|>?)kTչGEt6k]e}ߦ F%~t S?P91oB` j(7v#*/(@@"w޸06lˣA@@@@LEC̱e ӏvP]R׷KVXptWA;O9A%{^XvmNjʬ`AgU,J HEw-nfY,۪N?u6F:h߲ E|Kq:[겠xf!afnˌ5E))yKʤ:$  s9.UnUݴ2     P507B&q& &%Q"Gع⥮o3XubuJw_J T啧<5GY)huuI4l(IA/qUUB8ƱVŎs?|aswN8PKTY-U9rup;coW/EKUHǤ}u@`U]r3")3[wp@@D{G*ߟ?XW]uUm>#    p 0pK,c*I$s+)BG#=uvhlZۭ_ᯭ-VKxXV0p$䷊CVsk{+VlLU,yuֹHAxG>4awh@I=VD*ώ"?r PKޒNKVq]NV^^U-VsK-5F`0vC3v*1PrRi>1cSVY' e9 ho[l.un:;c94"_*<O(H"!'H [XH_k:a@IDATSUA4dvfʷZٞsX()tW+-8Dž(2=<mK{bgaE XC@@_y~=Cc@@@@*@RC-\evAH=Z޸MiJs WMxSTӇ*l;:^ڭL8X0?VeQ^R[_vUZU(ܼI5T٩(ճuVaQvv&;Ī./pdR]P?7`*- u*m|`^5ZuNr .T6{-V=fc_JO쨽 ľ#!'=dSyk_XmoxPk̍u;9M       S쁘 @`D tȡfwIД5vuБuc3&iݵL6ik3}UʌvqZ̠3n0:iw4ó4gZ:LsyfĄ"}:m&iSԘ;QGnӤךZj U󱣭C!      CH@Z  G1se߆(R[t23OҹT@MOh 3g7$}e$6;ؤ)WlrJԹsڵ*Cv9%u~Ì>Vz:PƹT׹Y3/O5&Ȉ@@@@@@@T-@mzⱧtXR^:c(g W~#>yS?PV 7_%(ޠыJUܽ >_9dvޥ?Ԋ˿`5!u){YUɤ@C@@@@@@F22 @TG<5MnR[Vث1c i`!l       H@0Z, 0.9ؤ>vLMLKm:ixM<q ąg@@@@@@@R J5@@@@@@@@F3i@@@@@@@@H%@* @@@@@@@@@`T Τ@@@@@@@@@ T*\C@@@@@@@@Q)@jT.;F@@@@@@@@TRp @@@@@@@@FQL#p{U[[43E@@@@@@@`dffδΖ@UZ9)CM`„ ڢ0@@@@@@@$`Z[[T-R@UZ9)CMSNjCb<        F˲XM"PVN!P        @`ڴiS\  ݈        7Um/ UCf)       i P6J !h P5V"      Ua# Aa(       'U@Is1v@@@@@@@R JU@WU@@@@@@@a'@j-F"@j@@@Tc:zKcƌ`7]2Oq}1꒙7@@@FI@ R@@@` UWfk?=!-֮7vӎ`?T7~NԘ oꌳ>{>xZ_Clv_];a.`B@@@`t ]l@ ҈I)@@@!*`F&P> Lj0Td5c>Ϸ˿uګԷWQ#|Wrg,ғŬ =R5   #_@_cf$@j`)   04wք53'bzw1~%˶ĺڷcEPU:j$v{Bͽ;˦7LC>8 >#   p@` k@@@%Х3F}Emn_uF{fyn?_'(Puu垿$ Te4:_cc#7o~J;V̎KG]jz}uɭq]bUc    P5*I"` Uj"   '\]>}Ո̂Lқךǜ/QIq]9{/5ޯמ}z{ɓኌ@gMx   0Tf VUi   0 9 {1Й4u+{^w:u|@vx[93.Syiv^^׫Rռٺ.iRNUG7b8sN\]ʟPGUg35 5Li֒N     P5Bi  Pu@@@`h 'PuքwKj7)z`Qg͑)6Ti}~g-S7W+y(1R\eRVj=Pubͽnb )i}{T/ZVcdT{<mAoZvn+5Gk͚xBʑ+   ( P5"@TϒJ   S;&Px$Pu.G/ Izyϑy=ʺKB[ evzevJGX/u/O @@@#@j%3A,@    . dTUmo.7V\$={G_Wf]=/KV>p:jv݁jƧcϱZtF3;_VvUלF·v,3@`z怒.ɻcr=eTx_d`ߏ+>Zٌ{EtܱF83-S&P5tpHzg/k0@@@!FB2 8N9="   -4.+/JPWӚqmYg#&6l,h"j|:,mAz~&ƅgؠ]/o%S=?XĄ愲aERH0z)=   0Ter 0S   A`K}m)w;؟8^v; /yIިey9BPʍϞ|Nԩҩtu)xZ6Oٙ*UܡoVWJ1^S1\tkāRp@U @@@FQL#@*=TA@@TegW^~^o~ߨF>hl,Poi}4OiSRSs|`*zэZ1q҃RޮV_0F]u֒j TMY#Tuo@@@#@j53E4 J3(@@@U7+̎P_ǚ^Q?/Q7?+fǵC. m]6#VE]iS!=F渾q}5J:r).ܤXkteQ=w@@@FLS@`R@@@`8ąz ązhd9.^!+͜;W.?UԖS@%K'@<ό?ۯ4O iZQ#yd+:   h P5V"@ZTb   P 큪c;}oЖ@zR~D>\VL +K魭;@3M֨R=Ve{oQoԲ-3JmLvtubZ:ݮ>UJ_nf}FtI_T%p@@@` Δ@ =H@@@+@YK6W3RgW&xp6j|:O D%u_7;E-qvrwJTaUE2a٦J%=^9V󯔳 %w%=evZbvŊDƤO#FR 2    P5"@:TSZ   Q 큪Oi늄#5*Յݹv[YX!< e]熲8O/=ZS=Z/~v!Pe~%˶DA;`Z%{+u KڧxR_₩/q>cN+/~4{f;1.^=-Ywh7EӼ)CفkJT\B[3}}+%Ʋ)rǶ]Z#Q;ޣ%u"zY'sG@@@`$ I\@ :t   0zw |iDz>g:)15Z̉g&VoVfi8f8G> RٵN똚j7?ҩjG?O¾Qo= @@@ @j42sDA P5(E@ٻ;ޢVڟ]ZX+^`h蕮ПD/kݠ nî]V}K&?Kn]P&{~33L&a 95>$93gw>"     0$T +?G0 PQw@@@@@@@r Pۅ YH:+@@@@@@@ U FPHP@@@@@@@OdO 0Ha       #BfC!@BPO@@@@@@@W b'O&+<~pU|֖x+ċxK \"^Wm9WU[/.pՖxK \"^Wm9WU[/.pՖpŋ"@H P0ʷd?G)2k#0y wi54 k`^]x wv|50.M;;>p&^x kKO5ܥpG``'^k#0P50/J# U.E(o$<$^z/.pՖxK \"^Wm9WU[/.pՖxK \"^Wm9WU[/j *@ȣ*\P"+@ȣ*+!^ FU!^y  P0 )@EWGUWH*B}C\4Kd.UeUeSϼUs& K`x%kms;H[c>'eέo|>(_([/iEPW̼N_V]ɋ x(+7U:m nӺ%Wg+qLo.RtCc>Z2gR=2[xWoI;Z8kF-B$uj>ҡ9qF3) e" SzgygE g8ɢcooDpLue~N>^gl|-zm;ҧNXM͚Ξ+4"}i+ܟ}Jo^S}rD#r_yD:^ts*b}WDR_@`K:Ҙ~\ykP'UEjJ`sv=J]_TGz%GX>B}n٭JPI@ןW&(+3Piz ujzvY+}Q9G_ul^Z$Kw3OF# :VX.^g[V]Τ[/=kR[Ԓyo>=dQ!k6SϼYs>Uлd=^}6>R+nA0ޤ%W1}9J=y˰~>ǫ&}QWk[4<(yD9^QjxP *5@ o%TzZ/S/ݯ~\ﵾ~tHevG]~ժw^_-;sE'AnϛUi:1L/2~zߕ{̹*H_+曘gZLևϼUE}DjSj]HxOdFo^{Eヲ}K-( d||)Z3/-[BB'0ˇ~00D:-Ў{KIͱ7_֫uެ( x,&»԰^M@ݶ(iw0kR5CT|%SF!^g#UIg윆x93B=myO>R 13M(T1j PT6J99T8 {섪y1^7QjsQrxE# x%>4?ʌX U9ND6!D+jBw>n-3ʹφwڿ^dA{G$+aFc?+(if~ff Sjjx Wfdt^=v BBfZ+ /!SiϿ%8u`5uPI0]k];x]/_OK"xn1=4iL6$wPmRN+u=BU3s^%N#˾XZR#TJU6 :TcW0-ڥɾ(x8QMnXgc=m=?ӳ2ߪS pQ$K.rA_{Rz`GvBU<ΛyD5^Z<hyD5^a</]FG>@``QKJu,4Cw[NqȿJsԹ(aL0n}Aweo_DYvk|9dz 7Hx]zuXmhhU|a9UvD#D'5#y侕վi\F!^i?']f{C>rӸ(i󷵮LsWn1y:5s͑xLL6SkBS80=YHwuryʚE  Uxy볱}Kzr=ʳwsFt c ~~qy`\v|e=,paW a{9oxE#hGT>+J#*}aٯABU: @4d,89e Uy>#|:IѸmZ~k]Ι<ͫ(3D-^/ɜ(=Ӡs=ӖīL7`>/2_94 UMOwDE'NH[&AqfӼ+MckO;к&ӒE!^{Toդj#;晪mzV89OB2ϴyn07+ FFB?NFkNoܠٓzжuH_n~hݼݱAw]Q'~V}K[ұ c2Nxٞ+Q\τ͉~oSs?JϯTO {Gy>(+}WD<yD5^W`{z}>2DV@PU?d>$_sA4.(3۞xEO=x=ھZ='S٭bv%μ7>obH Mr;Z+殔G﬏"bU'8:}>qytGaz+;QױҬ,KSa[wFueh"gj퍡E!^{Όoκ#:1:J}yatďUEsB7*j}!nz(1 *0l볱} iIHH$T C҇tU UQWBGT>˜Mjx/Yxa}FW_m޷j5s'ØxeȬ[.qA[{R}}a7J}aW_MT</=q_<wxE#/st4<XEσ*Ld @0]0=7[d' q$T8ע3f$t펇kuߢk1"OCշiogԽs2|6-Z [k]YnXDkGr_3$,pm;Zk޷LIj2 U߷zx.O*qp~fjdo(Ljnp/D'^k_~fO23׎»{ϵ [3ԖkN?P*4Wfa3zH~aW߰ǫgbg{URMS Ӵ?m-O\xe{rA_{R{+}Q{>ǫC1:}$Tg"  P%C[f*Ucmf׵+Ѹ@$T8עS32н!>{&E!^GMl;Agf~BS\i7's_G$p"!q-[7 O(+5훏ݡMkte;w߬50{1(K (0N0ϼUsR 3rN9\jC;;/a NzF"^v9hzL{3Gù):2ּ͌lNf )[ G3:ft01 J{Yg}ǜ0f Էɀ$fw'3f^w5Γ:7=׫t7יߡ׏Qz:Sf Zu˾n^Gy>+yE#jGT>+}>ǫyP> T j U:ӢoH~y&3U= ԡюIm4.P2}ud Zduڌ2pey7Scoh)x%|l$랙svꀹZ F0uF0 Uo0 9Lڶj_RAڒ>>/*7;ˑ?rf}'.R<ΌZx$α>aZjKuӼeWQ)3 YLC\{G\vޤ%Ni(/:eҌLknZl޿KeH.4<x)x?Ǽ;#?IUar}9JP>ǫ&}WyD6^bGdeί(yD9^Kb<TׇΨyP8"\BH֮Z(3~|f|Th\d>MN拳%,*FىA4kQ,*ҩUsLFPwdV$&E&^9cFչ܌CB%@ZUؒqЊD#֎Сd{-ot}S&9vUNZ3dJPieݷYdn…e_9swF&^giu:'y):xެТթDaw39B7\rA_ {RfS G#`G>H+}xE#yR UNT P USofXQ>qؽ =Q_"^p8sZ;9GǍ ,=/m1>QϳK3:uLW4GqY,rr[ͅi٫z]JęS0!{}\꾧hs+Oԙx DkˎxyF)}#!^V ^ FB+}#!^QaHWlw92L+@@@@ $T>4@| ܸTU5uh1Wcr./   }뮻.W_}5     "F p>D)`e:b$w=+2 UHa@@F U#,4@@@P  Nܵ[9߷.O*a&j@k[qTtMOP}ci};ٺO~b_V!'[հ7ؔ/Ǿۭ_V29y_ٮ.҄Ʌu];u3mڳ[8Sg&}Z_(S/URmKs|J`j|I7 }ڛ4cϥڤ ?%=]O?W L)*WM{;̨ Ԛj9_7_;AMI$͞1YH%cc>e)35ڿIkxK͑.)>jR&+j-[.gBU*.ϵ)_O~ypNy;   C+@Bw@@@|ߔ="@:ӵ>gci: K xfx"gxEFrv:᪱Z$U6k4_Ry s5}nU6jiܿAc2 :u*z-Z;y sWhCrj6.%اE7ξ􋵷 p:-q_Dio.AcP*m֣&S8ޤ9TԮۧ:^ztӚcE156UtZB,WDT\[xvܱ_L%gS(+@IDATwiQ:5xS>讉dzGՍ ilk   P5@@@@!@e &3B~]*󨸼JP{v-/Kiݲ\YUYm]vῨڜN^W5jiSO/e&(WBt7 U섪56"/So8-*Q_,Ԥ ~︕{M h >u=WݧfN KIny~׼ϖЪ (͒2zŚtU^=[U}[ƎMfuׂJjoى]mE*e$XW U-`L/Էg_ZXnnےuӀo    P5@@@_hvHyu*fH5T$ᏬsWWUbeUTao)nm밪m&_8TUܗIJw4Ug?ت?~Vƞz[eNJ9\[OܪϨd"jJ-v+v=ͮKI푌u647; vk+5[[w^TWrh?N|quszG=VsCU_ooh(;̌<<^t}IuS;kȪؙԶ׋B0suʯnUY@@@\`Μ9?7@@@@ , KE' 04]VʪmnbMۑG3^wu[tOe2KBhBUU:$ĬTښ꭪ zej&zzz|{[Ǫd{7#-ftT[NJjjc Mr% Nji% MR!Ƭ6Bv"kgTR=?^*Ǫ/K$9KEҋk~Z^prCn=YF@@*@B@(   -?{D &^_Vc*.]Egݟq'Z˯_h&nJρc|5S-5SmNO}#4ef)ݲfLX`OZOM?Y.2ƻP]7#TiG89f?m[9K؊WcrIM ^rm-;N JѷL[et}[(/Nl'VHr|rR݄*n2턪MB27q)*N$3/Q*UĞ4av*)?)Kp5(ۮgdėfvlN2@rڿ   @H@@@@`Hp|@B';MԳUK>#TlF멯i=صIzuT^u֘O݄*3g%!m]^E&WBUVPe'%umuܓ-TwTln4pF*;Go΄tAR{zzHh^ '0w(袲jU.Nѣ̸^ZzAq&pۓJJ'qn[YM\~6ݎQ+ق^[ ƛywWBTN+-'LjFo&mZ>1ZoZg=k3ږCѬݾ#   X@@@ns#*h%dXuGzaU2IOV䲽^Z{8e,wj0wXU4o廚qShJ=<һu9Myݑ^+ICŵVcՕR)6Y&$y\*aUbFʼǩ_ʮ]{cӍꫝɝv5]ReG2kYǬXi#uV,ZԫVsuI&VVo~k*:K:pce;K_[i=p)  xs̱!   aPX*J=@'TXMm^*S[S S\d%IU餜j7Gtb|jnO*IOf?v};U$)n69iK]VS]E*|gj{uV[W:uU[V._b57J#?^[z%T5LRZu8fXEڛr&PeYVU:MYU .٪* ĥzǬkrcүk4j-*?{U UfGx$knۣ<R8bU;IptҜ[޷W@@@`Hzc   +on 0Z̴qSi㜛ɮ55ܗigͽd_Լ7isLYS+Sm/tkajnfiҷwٷv)첝6 .+2u;n$3˞=܋guqbE渻u Mv;@7eIi[ueus2umetԉyLegO8j/;WcG>bSMj׹_ NWrfǢJZ)e)kF\:V63ژЮ5gOVޚl@FL9 Nu  Ad|yZ<,-־W}Tsߪugݦ}RӭҤƩpwA-/iiߚƎ?/uj_f~Z]2#*ӣW2=q3t >gY13{ y|xj_Ek~t{Dnꌦ{   (F2@G:; 4zɹ;oNwl-3A]Vݝ:ޙ*{)~3cŮc]Gs+8:yM]v%M&\2^2s'Γ삂1d|gnhkWct%9!O tŌcڿϨQ:e.[*R cX-Fbrb::   7^eF+^onmYZ%f$a<{gххp٣0 ZZ0]fDBr=d7XmKb ꋫ䌦=5f   Z@!C%us-:d|oD_)ә۠+uº[͟'_,ث]wW܉   0<CP1z?mi~c~81>8*cS!I7j495-T$T9"E@@$Tn{j[颋8s挮I9RMh~]7V%SU/fyqV]17@@@ (*qR_٩_45qTx,]kF-N-ƌP5ŗ&LQ׾:aj&]=S6P}\v.҄Oj[t{O`23Bk=*TpU/vV0yNܳ[8n˧y;Ҍ?GM'.4D.=+cA .)_(WN=y$T۰ 9T,n&SH+   Ha DJ-Z$PXT4wX|`@@@O|.If&mۨt56"U&f#.ynjSSmjV2geS%GBZ*.if7SUZ㊴cft_[NX{K:Aُ+.V}KLnBYvݒgIo̦fjn3APӒ   x B yR{N?^2w P4J"  #P Z^`'n%UZI:p~`cP Ux\/t-Z.xvk4:kn2WIE.цZXKr%T=`eeכ1Q&1Uݧٓ KeXW7iSM]*^򪿐i^ZdʧV5uhTgԶKn1S{;r- fJBU!+   Ha    +OBU˖嚲$NUخѥR߭+uzTO ڶZNzP_c֋رIx Kf+ƥH%NUr|M*u>+e(غu>nK\GIfcfV]%eFڤ6Z3 Wv\^5QkLVQۖOsτ{fXNX;pR<ގlA@@@ -@BO@@@@2'Tuji3d%qB/n{5s4PUb6$_4wfzofP5JJhZlʕP历eô%Z%tGiT]u,Uf)#tU\lF*5F\^cZSiV#Z{nB= '*ul[.[pʞ^P*Ҹ3g:OZ$[QiƴtЬ}@@@ P2@@@@] *gʿxΞ/vR{֩OioԘlLM&7 ;Etv^Lr$ӑ7}aia=zDu.Hc@==]foU~?iwFB\Pe1<-daPuV,   FHi&    !OB3埊*Զ^euy}hb:O1?acAźd{o <6mqLBUI2FBչ@@@( PŨ&@@@@"0;T%Tk iٸTSVlVFQ@@@( PŨ&@@@@"@ByIZP0]u(q۫jj|}գ[/>xjH:?ډNuU0fF]LtG.(Иy?]s'   a *Q    Dj"   +@BK     U-@@@@`Hja     j#   RB8    @H C#   Hk    *@Bՠr3@@@8$Td    H jFv#   ^;j    @H |    d P*     Ugɞ@@@@Pu~9     xse_2/\P7xc6V@@@@ P(P@@@@" _ZW^y>?l;׿nY:}sh5M@@@@ P@@@@Q`ڴi:pD؉UW]uߟ}    @ H D    x})kF>O>glg@@@ UAuA@@@@ "?Xe%[}HWWi!@@@@ P.@@@@Q׿ʌ:tH2   ME     iӦ\uUڿDZF3@@@@ $TE9 @@@@ax}/Y{Lwu0ֆC#    P@@@@@`'?|Իᆱ/x{8    pH:@@@@#p뭷&ۺuf    *    Z`͟??r    #@B#_@@@@tO"   C!@BPO@@@@@@@@ U F@@@@@@@@ j(T'        RPJ#        PP5@@@@@@@@B)@BU(F@@@@@@@@@`(H U         *a         0$T *D@@@@@@@@P PʰQi@@ (ڷY꽏j[U8&(ztߧn&}]7NdhҽvlSiҡ/ho&t<t*t>Zti 5S5m2ϋ`FZ!     U[Q@@K-7NΣ0wV*ޠfђ|tߠWIJuXC.s\^5Qkz;Huy^hB{6ޥ+}x7ݟwR`5eњ^Wꕚ'@@@@@@o@@` ߸@WԩQVNˣڲ@K656BWcrϐ/ěj2_s'$jptϣSu̢UV~~{6ĵlcU&^$S5z.z1`d@@@@@*@BY(-`O+sɥ}^su #oB֩wǵhnʽڴr -vhj=ަ߿P'\˯(; tYA.Ijʲ&_IۖOO%ѭ]0fFGI*2 UHJ=AN衫&>3:UyC}~. Rh|Ftjo=8>3jVT֨{>1%Ǹ*Vۦە2]vl 05R `zw@M >xi 5B]Qx9ç~?#2S2ux +Gbxk,*qB{^xCܼz)2=j=wďLP1]:~'Z1%3Ϧƚ(V@:Ζ=#@[15+59 zOf꧳h6vjkţ]+Emd=RIB:S4ꖀ8GwMJjRy]0kk.+6|'[w#-3Ɂ*=K֌1jנߜ4w Nꅺ|_oii35T>=6~I3&'k&r*(&LչԼƞh٣;OEN9_3&{ ~\Nܳ[8 sH]4B<͝z[]0޷UvUvB͙.{iLY&CBU¸5>6K6ۭ_4+ޒJ<~P_mT?^g\q^_Dą&3h٥'yE,(PE4 Eܩ1k^yY&iܧҼ͚Ѥ_6Q͘ŒN$3g ~}Z02cig./5c!q: .pgݝ0p̳$M0ISZzALv*٦LBU1U?b%7:Dq5؇ 9li0^Rf?-=&nYEE\Ws"mbi^S2fo[u;k"Td^/dDd;+}bVUE[&I+zLZ6δлu4ژY8TT}/9WYeEn*j_5C=Yg+<]˒uOe7fN۫Ǫ+Mvc՗myI>1~_kɱ:סpY]!uGOnp` @4Vœ0fz<e?25|t؊u4Urv.ƥͪLF0C~'}}\\Zf{Ruwͼgϝ@AC>`қy,ۚ~x3{ߒLEVYYiF_Y~}=Ǭr__\QqU^Yo:  @OU4[2x@X miwV XGN$9 CΒʺQ^Bc7Er$[rVQiUW_gUX+*j꬝uթDd3X$>J1ƪ鶚W7t%զlUUt. Ulieԧ9Rɾ/VlUYuVϬ֎}S0FUZӦTvg弿+_gUmw7 /S!KR7$uY/nSmURQc5 ~6oKv*`YMU+1Ģ72E@ թk"3=tU4&k|Lp_Z1аl*_顓?RN* ׉`~C/xו·<lXz/@`g{V.gYVבzdz|{\љqtʝN}SۚkbG돶RØ>+  p~\pC.8} oG70S /TMq[GMiiR0}$9-= ӦWd&SKu9wl̔NTR nNM`ljnru̦)35&r8O˯ֶ4\3k/ϗhiS%.l +SKRo5q>_{27``O?WJ}USIJn'tf/o7mJf7m]?~df3ϫSO/effmZ\< 4}Ub :py-qU縯=5f:vn^Z6.Ք&Ejԕ4]VY @l̗dڶ|j~sP\Q ܨp}k7)kdOݼa=jhۤki׎&${ O̢'̞?U7qUg;^MO*fVm77=4qvzsbݘ>[:.X֣JMh"˧kM2_hifgQqʔ_>M޾ci)۞hv@LqW`3;t1fG4S6um/Nѳ>Ѣ~TKR7y9ys9bi$e}}zQEv{ޞXB@ L6 7-!6]cuj|9:G .m)nzN&{V%Y$bYv? r|w =QTwQ}l^ m;Av5}*;s: ,p~8 @4?%==_S49ySy0)=KjߥUX2Wwqcc{ущdz&tXui2<KiO̮:28IŹ&>žXnׯ&N٘3OƔ{q~cOЈSGm1:S_[;|UmR%5e!޴\*A.ߪr 4UGoDxs^_9coE_Ęҿ\ҿ+*OMc7hope%F{gOFrl՘z~lUkvϼ:t^)jtry&F͚*1=Yh9c{/ǬQ<^j=bWEwd{Q gEVczŽc/5{d+ @8ڋC(Nm>8Sg_oJΦGk}%OûS,! ye,ecGKsupU=K|skq_?PecfaQvWHv Öf` f s; LsUVZ~^\Zf)w/Dhrs;ԲhMOgUWSv9(*v[վbΔk338/jꭚ2ftXVĪ6ej*b\vdݞTbWj0;AK*.2Si_{Iy p;٨~)2Kp;=KΊGK,PTΨ4 b:bu1C7L}q_bl?)wYm oҺ#>-7܇P%T9txo=O}S>{j?~߾V4OK^ɇ8SErIâwY{뭪20Z{NFz_xmrce{S&oL.zr/~rrW)[zէiKX5LO[cedżZC*تw{= =]ft>T0vu$i;4XA@ Lݔ CϻN;DC>Z;g3EWt@eFN 9WζGs=,*Y΋̾{rOqE1SWXm#`>h%@ PQm(VZ46_F?r˙PUlZm;xe旵eΈ'Nb}sޏI)I'gslKf;R!U Pwe c_Kj3zzצG vMv˧|ї26-9_zg2mw>ڏn,̇g~trBEmՖG'n~I$F&g6f$gf,n!p^ 9gRg{Jgc0}euvvy-OhXE%GY5٫W̊U^L6z=45ݟ}[%$̮u75I.?ʩNn/~LE6:͈_mmV{^<8eJ[F?e@-$g~yy/:2ϼw} Ǭr<ŖnnpmA>t3=\[^ұ~̵VL^f=qh~lqTo\׊v3g{LܓU1m>/h^廚^%I[/u5[5ׅfzh$)^IY~XZYV__f›?P>g}]mZ)bL)V#6  [T-N$`'D.Q̓/͑@}d~CnTJ<>r7Vണ ""emV\n B.;(GSPTUMDk?㑣Gj;gf868}$MsXLj]bbUT'᥈*T;-\YFHg8lKFBMR+ZU3oyn2uPk㤈xӾ{o>J5Վ}tFh*/K%  !5^8~H~cKY/y碏]IŚ9ۑ_X`I`- ^ok } NmWK 4sQQz$ XHM'ߠ,[Sz6E SltYv.*VEU֢JN9<Cri. @ӯM<}uvM\ZS[顕9WtU82,nct.Z& YwZ\">~^9K}Gth/nyΒ9KS]>]{;Ǖ824;kC>F9Be_8-j PPeg6 % 75M>~Ca_oxNiof>]Mb^Nr|_6iNݵ=~,\<Ӆ+onHKq$L爎rߴiv̴1Ii4L>ZL+uJ1u%3K$rlHpog+-k7n,`}zFRu} sw:RLB͖",_%7DN{ u]}k`z\'m&"iV{ӾKo^ZkCSSG%Q# ] H *p-ב~g-Ʀz;]Ĕ%w$'+G#c]d5<-/qޘ 7ϴ(-H),G>UEkq۠NӸ_r y@IDATѣ8X{h㼤45U  ) t~Oߧ^+hD"f~j]{?Oezh%"W B+=!!8׎[Ϯ"ը^JlS_*E $`}Nxr-Vg~bSq?k#%)1.&ZZ tѐч܉0GC_W7Is,עaz~#s݋ ;ZQ>DkhC)%Tiziͪ?yGQYʕtj4Kcq{yS2ኊ)*2 ؉Uvm![&1jvo1٫n< uXmǗڕ&9öǗD!j|r$[eOVEŧi>4Dڒd5%'+o3c}LSjD*tޜb'ŝVڢ<ƫopf#ސ4ڑXSeT'#3EM#8'm]{1m\: Kq&Pˑ @]0_g쐯M{>S?k^j]: C& ڵx- q՛h)lqdGrOW:Vl۰|_nHF{(9˗#L, |g#f4^Zd.kT67TW+?:J ,زʲjGuJ$1z6}l]\D19NV恒3 <_ET{Վ1!pɨ K$@$@ @AU0(  [t20bs~f~\'*ECsru*t:uE%5411J\&!vB#;7Q"R:?"rkQ'ss^ =$}3kvM PIxo,nb%Xސjzk)Ƒag}IQNChT5"EYyTpFaUnSʓS$;r~ ^yE'e$wbjUd4G*iŵܱ"R< Nvƥx}%  ]X|]xbIvkeiyxtL]EvH;쩨˷b[jQK J,<YpT~GALѡjRSsŗxw DE3YHVrXE[fD8,BoK$@$@$P(SHVd1t}RJlOt^7kxvppfKr!܋\EMʛOnFclǣ]iu*SR[MP7nbLoiH3RLq1ߋ;-X)D$iI٘xGzmB@c#/C{C,Oj4[Dh|`woOOwgd826dhQӒľtdž5 휒sL΋\~.ljs .wy]PHW_FJE^ b,{T s Ƒi<};6RZ K^1N9)"[< rHHUuvO$`#'ʑ&TWh >7Zz%u}g"attM}*R2Ts\zzThG6(JSlWc{`,RQi)\cR"swRw u;+%t<Udgp^?KIrj?!͓S&7QͺoOMaMQb_tPtY>JYt8QJ#%Ĺ:͎ _]tG3і&b< pdtt}Uhqj 4/ 3Ef( [aV aC~պ_(-@IV8hlmTEf76|0(8Qri"pXɭ$@(س 3ƍ[ٞeS21zx%eKh% Y2/HHHH@96q f^@ܫ:oZ{^MIm{Y(-BAQ! \iF7k}%|[Zz /훡T!Ѥqc1Lwk~,8aFϖ<Ǔ]siQ) GXbsؑF-ؐ_ik6: Y7H/>#/G$dy8~$̄]Nte?u1X!_YAcbMչHHH…@Bl>ɿĤt&tjj:Vn̼U诉J!cr,Zf= Z:%wb^Sƒ~bb9°q?/ǖ .tzG%ϔ- ؇U-! "*}6 =b[Ӓ l wdhrl,~] :R6H$@$`#T4HH&`330r*XmWVL BH" ]IH$@$@$@$@$@$`KLmK(   Osx 1cp?`( 6QG$`+~bxc<x 1c8yuOy;:xl"w!gͭy$@$@$@$@5MZߠJC p$ `SPH%@AXHHH ꇟ9J 7|/U]HHH'@AUXHl xP gTgs$@$@$@$I*O&B$@$`|/U-$   &@AUMf$@aK@*ĺ A*?  #T#gs$@$F^ ȱ TUUj$@$ `SPH>>{c'   OTy2  {)h! 4 j0'[ 6UafHTEHHHH9C% 0" RPFPHHHH("8V# *$@U; xʓ ؟ @o) ]7+gqgPΖpiåRW p~8O*z'7l "  ˟M} P2qj 4hŽhѱ mT  j2 IGW-w&zl/v{aݾԺh$Nb?a>qmCul]w&u9V$Thi (8%8Nb'L-+)$@5K@怒fYu[`Nj|Jtxn3>Y s4?S];: #Ѹj~wNķ߹^.#y|[]l1D~΍ .h.v-[Y<\:w }E .]ޖώH PPHj|MAUzCA -wy1񏢅'U=jf Jseփt?b~uG$>^ʺv#pgоå _+=N/~BU5\ HْJJ&i AUػMlVtm(r!<4vW!璍,SVeA !ZJA%n"$@AUhVۗ&!;& >"T徊dJw{Z~+_͘߂عkMmstJ-ĻII6iB( +@T[0$WbٟOXncU]Ngv`tHⲦX3\qxϳS<%cW-ܴ##AqJAn"0!@AU8ègQ xpKl^3om5KDOWIE@怒*XT kD'=Yݱe"W_m{;Mѫ<}4#Bձ4L_O M/!~yN`2ףdžcP[EUޔlM4/q PPes< o) fc[o{1G)O_IHiǸWwU(?|uD:MgWO嶺] qmT7W|f4v7#::ʀv\D\֪!N\m+ߐʏ%ROUTŹaSulQ2~oT,H `TEFܲː36xfWW(T/FԮxԕsjRH *.Jxc n6G0+c'KA+|=7ڈ)M$&( Gr(=M$9#p}{Q1xU-pHTuCa@}H^0 twϗ:Kiu,w=L5 UgE׶mW7i*aɼQئ=oW_KƎ'5WsJ>Wqm?<j bGދ MXQĜYa%KwaAzPP%2 @(  L@J&cj^~J3oأ >UZ](*Pu1TຮKFo'[݄_^jp s1k (?O+NkQ1hH/m]Y8W@a[~0+T:U!.KUBKeq4w"ւ2! HMHMjq:1߭>ova|>~~&ƳcAqUP„UaH#@X2⤨S 1orSӛGIi_^U>. j{)JV&zE gd$~Cs[R7Ӿ>G*qjEij磝CPۅ;j.5~ tL2g|nݗ?y.֊7"KPU_ rFqS?MDqguN? tEjr.O -[{]+q׈u ?G9uߍ{\,WXw]ǸR=1Sj1#/S j $HAUoddA{x*aǎ/p0Ш.o){$ZXm̑ώ=5ע[oFU*9~8' Warbhk*Ԫ}@rvga|Ȇ¾nѨF +n x/v <>0 K.+R7LX.eo_K|(*s5_sCeG&2?ۍg\:[CǍy?AU1N4>x ;D$`T6 YQ.2EںOGÔBL̲Ji|s;5ܧ[h ]{Z@idc\vRGveb{l1[ei 7@{) 3r8$PS̑D}tN2f-JNW\(} P\\PyFZ}%{ Bc1r~X @FmС|,~қkQxv6 )ѳjv,`l+??#^{99[M93qgOKAA $@*0^,M$@* m|'d麋HM= |^?}uEQ߳pu;߂򣯊Mq59~vh['Q:M\ƛ>Elɖ) o~E-=O͊Qtl- =fI;|M ۵cofh7K}}]xT@AP@AU(yJWP}n 44};zhڂ_blK}cЪ"g0sdoHS~zʹc1 bފGEIcnU>aKY6̽ VX9܎-&Ez1,[0KY_o4Eqɕc"@.{_`k.ڴo_$,"key;^("ˆ N"-'UiH6(+hHٽplw1<[id?ܰ ԙ w{{cX2gg֢n fX^pi0n4s =hrP+$~^ Ͽ c{1==5]"RC&V|݃}茣ǁ)qok߂4i6X姣;gQ[ }k [%DJwŢ/c˺<_oKY2|!EMk161vsOJ_kD_Oj;}]oٗj`qa; 0F $@*Pb,O$@n 6U?,Oƕ. _]G2z.Ęi!~4|ʼj_&L]KEK+-RŘ:f&Ws#ʶW"U?  w̳lKK7@7˰깧](لU!4\ U};wcH7[ҼyCڛ&*TU!~+rM9UJҎ!Bps㞁3%J鋊TU 1b ^Ӛ=~mi{ji,L~e 1>`=s Qua͏3q.TGކi5"b_s MPPeGЌZ$p3PQgj 'oZqUek^&BPDQ%cM,sG\'0# RPfpH6}1 Ih}^u;4?]]\;KFԲpљډNQfdzC;MJ˻ Kc~U\5c({"\E\]->`^˞3yW*-b* D$ 2$@$`A@6ơ kۨwT36c@+[c. 5JVL4_ b^a)R#H݁!Kgwbnn-W_Dɂv=H1L;ʎ^M$QP5o3{OхB}EdwN=2|STSxX6CAail7Мx ᎜%>quXĨ?}K,ZsecR8{JN13 F5EEݣxY}Dn=$t<2xId"S*agF|gHE z].*}:+R8#=Gj9v.xic6c#26! Dž;])nIØGeVһJLN%+ UccX|<ٿtL)[K)%#u\FOcϿcץRQI1hh*cqydo|wJ%].F!E@怒*\GcIb7byvT36wϧڃ.-jŜ3-"랄eÇh/nbάdDFPLvTeW n{޾3{w5,Mcx!v1e"l*>325U J*hH|o)ͪOZACnYv+tp]_b!AUYtt{GV ǛEJ$NuD!p.p2؋;pT5AY^A+FT &( !gT,Ub}+"j 1 r2y X81*2-9<#}jW<LM^ snkt-bبoHMF+*;km:FZ ףd xPDjzȇ0e/P%jVzOIjM]4|2:G }j_\B{Sb;XeX76D#AXo)zf\K\w!%]PU 7"E4t:xeda%:GԆ#9h' }XrH|7% l bɎ>nl~ OJR!j!riP%QtU&pT'ԇ.U:a.(YpB7A: !™u |8S^sWn6yfFGBDnJH*5p*љix4hڬWQSYU`[cX?uT??^R!g>Vl_|#yYY勋Ҁ1rWGRʢHE/Rvn$'Z`ː3Sh_x y[DIJ2$¾6aNL ؝Uv 6*9fiٍ6 \(ږnOoW]{vc=-ڮ8 ~';JtUPX-Ee׿ۅxxKi ̒ r7Te]7nU}4IE+E𖿯%qFԺvF9U *[ZWDS1*nn ZA."`UL+ZrԈEU&QuQ"(SZT"!#T)u}~#ڷiGTc}*YІJ! io1))JTZYI|ws +3ڴ|ןz ࢖@ٳεF,TQ1tOms&3pWw=C|4N4ڛ"T=2.Ֆdl) Y ؔU6u ͪ1fAUT=f%*~bj*fuO4nlBVE)v^e*T%Z34 l}+:)Ro RPU?|Q@&./c *t(s2I(/ps-ڡyFԺvxot TŞ-bᧇ ;o \ך9BAU W H< PPɄ[HH/ 6U~!B7nUU-Z#~&X英ނv^5'1 ž WW:pl:RFA%(;΃=9KVV|?_|mxmN\o/?Uj9toÑo®,ưxo2׽ǘ P+cAPeUL;vr PPekи PK륧*X*=}Fa%'nRJ2ۃGs>deg ;[*\*>W (Z"" Xj\%p RPH &.cUT)O;8s&Xe[lÎxjKdee H젫DecrzL)E4hZ9;f"q{ mD8\,nT];KF)U" x 7 l cVwb|cfAC C8] /6i.m\W_PnEGy} *)ɵBa}iBdk AU*{.s nAj\ ZyfdQK1'#Tyg(젠*DI@'PJ%A "chMPeخϒ,;I lF䠱0G[iP3þu9 2'Es bI܈̖lB~usw჋fDv%a״-1`8[n^xj+k׬p -PPe 7ЈZ$Pqdzu agSR|$hAKPe'Ws.39nU! T94>#D;űo]i9/ͅuT*G?8M f~-6:3A啎K[j*u &5IYdu'=,TWjaiC׷l%D h~Nz{]HH@&@AL$@$QSLQܑ01x-+Jm=r-وbz5**}VճuJ>2Rj ~.a@Ļ sG_Gj'vb۵(:H-lȢq)U(2{!t) ]IRArrG zTSnI@|{KQ`L/܄## N sˇXX#es[{w]¡ gԘ >ތ"~r4&yJup|pq6V /y{#wPu=D S:kA:S.-9s d1wU>/V#E>66=6YH(hE-(݃M˨r سd<-U8J(& ,13}DS"&8v*ݢ L@怒*=ͱ@(IOQ0 x W-9UmN[ HNVET83"bق)*h*Of1@4T1/UH@DlТ)֍\#1o Ċ"vcj8 }% :"@AUguHK5G\j8dIRib|sĪ,IPU J"j2^}v@fr0W]ES&AUVi7iX2B!c(+|/UaZj@)rw'][y2+/X)\+{2Vb("J?Ux5nZ8*GDejLhjO.Li[M'U4A= f_a{ݨ"W,|JMY(wϲj?eYH@$PU 6UUXL T"0-bx8܄A_˕ȻAT)cEA`YB#msܮ}RrʾNތv[0 REk-б-#MCK+o5a?q!DAU:&k7&,gv`tHI-#gbjסZڹ؁nn4@Ύ8xFt:x#ڷ禲ؖ  h"yƪ0urֈHI=-¡ g/dBL{Yp3 ^͹;e*H-ԂR]OEKb@IDAT>9 &zs,UGD=/;-K +1\j< _ED&0 ==v2G<($ \$ R[rVLձjMKۏ%c#u_ {I~$Anx!PKI;Pt}LQ ") %ӻ[+N! Yj6@{) uo~%&q*VMbvxOs*%2~ޡ$_)9zVO\,k:#wѣ8tWHx6]<=Fg$攁sf%#Y='s\vEfh/+uJLF!fԚ @=$@AU=t:L$ 6UaZӭ~8֯T {oOˤzĸEEF!ɗXu 7wBtZm ]".+]Q>QvsGsk熧E3(4ڐ.lxm1 U!H5 ]Y$h5 6oĮykvM;=13w[c_CxH-ġ#vd$ zHcAB@"f3VVDu\IP Ub/<  &@AUmg `-)%=xybϹ]TY2LtIEɚxXD}U -? 1qj Ԍ 7y5GG$m2?ڡ5ٮO`WMku[ܢ"Iʤ6D%"U{6DBoTDJa(*ņ0B%l|[_֯@|]J xz ;}vrJ%MHAKAU#_ J :*IT#ߖci{.\#i΅<>sUbK|D(QLGebmڙi:۽{˶am$w$Ej  քGFNný$DU[refv ;c7 h)]DzTC #~8&7(s#-joT ՝!lo1??әT ){c>4$H$` T 4X>]&%+1yu͜O46ZEIPu`P5Uq*|#]_qz @,)kxyxlZ!ʊDYl9 WH T962s*S0gTD4s  g|,H){̂`AIک v I M1* ,f5΃Huib+t> +WΞDgST0^3_"7t% U2 . @l WEKw{ÊˣH4jpNd#wb`*= -ށ&U>E19nn{ԷYwtB\ݭgbgӺXJ4KWb[Qv#p8عUlsw4ʛTy#z) =M 0AP!ҺEHi\tQk4_"3 Df2KT<6PޔĻ15]=39j3]O?a|U# 9osGV0 !Ӄ 7aԐnLu܆=hѵ1 zDxgo4)|ٯ!e1srm92lH+]:^b`'`ъME&`Ǧ8 }s9{$C!1 zwhva㻏5S">^y 7]w-ѳDJxqp- A K'p-:;C2Qc|R\$ lSN`ɰPJ]GcZۭZЧrckss/IҜhruT$YxJ/w=ge.o K)7mغY|uaMD-" -yu3F5BTqI^SЧ5`LzUj-QOU"{ѹuk|rO_`?1[Ggwa_39qlb)(!" ĶC"sRd/c "ܩ a>! VJUY]' U* % 7TODFM ^;H~¡+Yi)|+&RL})*SDf.=w1 ? c.K)-^"} BڢYP%3 q!$PPn$0y߉E m9 "Zo1љHnɵH H gxRL9 Xކ?[//ɲQ;r.7MrNNEasǂG2lK}cb.>%"u/.6|fRVIls㞁@ykg.v3I|U&RZ&"" cMϼt$@uGcϞ@V͞=TNލJO1m1S4yS[Sb&=1VSQ"eߗ"e&EfbbJpBtX@̕Y-^"}aWVmfAJ-ÿ$@$P *#$@$|MAHv\~1YNߣPGM7< :co#m Wi&oHfU&Ek;OY7Xlߟ0"Q}{yw8r6m Wӗ{>!=$R'ZIKGOfꌤb\ Th" HOĀ^sm:/iXwh15V+#cI|당bmy̛9BJY(GJAQɜҰ)f| bȤA{i2]8P`DZC3'39۰dx u uJ)f|>?OOm~nx\ƥ͉" -ҊOw%h+Nx]Ӱ9{֎U L&w%m|/UKZOuFvHiB[0!be|X41n;[RS^~UI["glYPon{ڎ~1=Ÿm]ח?]^ uObEy1|,۾źkĜ!D'V\/X=jʺ3n% OTy2 |MA_Y4cy(PLEw.l~a]fD¹p!;{h":vEcU~E/N} 1N]p鯻?9[Nً8+l:6=-yP,e#E% Pv?9] . / #5ֶTxN\_~? .j [DRy4P3'OT4E@F٧tZ#wߞpZ‹ѩs7D*)9s<ދ6W*\WGkѾ/1s)|}˕: A'\{MZHs oN ҫFꤸ:x8EI|u{4t # ѫSU |{3ʜE.ǵE⚈+_$8L]vtn]#R}*W?<__ҦTvjz1Y=WȞQ]u\)b@=$ RPUINw'2E>w5hy'4`lKT}%fǐ{k*Ӹi4=zN.ny :﹀c懃8Yt b6ѮEsHUuE <wgJPU32 ͑ @*HÏYPU(Uz/GDU$ RPUEF$PgUuf ;& 0!@AU8 } 6U?{*GۭPPeEHHHH#) le7|/Uv!U~ U3c  po)A Ų@Pm$   #@AUfO$ B$ RPH PPj$@@Pm$%*[ȶFQPe[а*XHHH”UaX+t PPJ@怒Vѳ3 * PPeU |MAXN!PpM 娍kH/!&@AUHƓ @ PPtlG@6酭VU}UI&p$ RPH 1Oldߥ~Q=`HjU] 'qM\ilq5%Z!@AU`f'$@$@$@$2( WzC硰\ h{eLDq>y( -(b- F η]4HBU=N$Pl H PPU`$@$@$@$`3T!4HH/|/U~!c!   kT{98 $ `SPUl- [QW zTi6b gcǎ~xp_X[_E:8_X[_E:8PPҥK]M:d? @U^Tz5{)Z]=sD-nCگK>HU^ץjyuz:XH^o)i^3o,-ZU{iZSkr@>-d_>-i/Hid /)d )id /^ />d //Hid )ipH `8g-}E@3r+"nV7@0 .47D.lu;;ac/ v;[Cv9U'귰YlvyGt/MCK@:ozGW׾#ЭM#}!N2&`~lîP^ U!UQ#MehQ}(I=l8>^Z _'M~h8_eGvMYa~;*77|Bq)*'+^nD^pK}$L8-CKa)8 x8o ;UƷRfռ5s?&#CN:}\?M?>2|PTۛYZcѸ8\r{3]eb_ֈvnmzH=> ?{7Z~+<–>ײMzis g]nܨ:w {Ŷ;MGa% v )U}u2Cl/.InBwC3ߟG~ъwa_Ci+ʰˎ+_O3pA#4RQ&mAtD;y)Kh @oCYPUX ]h]bz l a|?b.|95 J\Z-rF5$,_#0AU ܵ]+*.•Glüc!XcZFؐY>ڊͻ9 U 6PU]#w,-k {;ٳ bZۖ oz&v$ĂԬM5&`sa"QS_3_(v)3x<U}uU5eOϊoDGpE3" m'uEӹgd?F +j%rbUs=D pg @u/bUנ3DFaG\0/&3L4*Y0o[wލg> \:O8;kabmЈW`CH}6Q'1u Z΅8Cgx`Ћk<{0X/-VkDüZ}U}|79ٙEv@2@P%Ԁҗ'n*rnCZ=2KRI'K^[K8*Po;q݋g US)yVs,՜M|ˢ1Va5_>Ƌ>'!N0QGf/_d21 27<2}&/O}a b:fZ&b{<`..ٔ53E}GlJv܏棏 e3W Uu؜ɯlNUX#8 Ծ97)>BRYDTs! EG m#}kAV^6-z]f̰}UY΃ľ.9Jtt.1TT̉5t@U~Yzv^K{T߻/i"hB-h?oJ 6&]48ýi_VRע"zj=r/f;ukcHs}#@s5F/ZX 돇zTX2ek d{t]F$ī_q}gx:Z=jK%OMYc _3J9ܾ*=OnѾ y34bsVؑPsW7hw~,8]A?ӐOE;"Jw$TUeOEbܲf%ATpk.N_ōBT/=zۜdPu./{╗棟vmWpv$3auA''1 ƚ1aEvytT@w\/'r3CRo ,P cuضd&Ʀ"Fה1hgq\~]kQ5>fmha,9[Zj 0Xcw@Տk0<' sn]~nMt1Vb=N}y&I7 6Sr`kёCIPs.`oW`_"\tmMcӷůt/N rFH/C׵]#8Ey+G|V}*Zz>TWrYR}CѲѲN܏^={3rPUTb]q"^s?m;׃1슛_^eȀQ2# P /#5J_g?ÅfXܸ tOi ҂tF<-.vPeUCO=OtK?FhTv)yw 91.4Fk'Şwu֑/ _"U<+W1fPֵaJ`?)Prn|_#k_2~_a}I?KɈ1w9-jH@A՛Ux =-j6P%tS~ "3Y"{Ͱ *L-md3aD;pb߸skB!hu pGWPZq>x;x](ERAJPcTZP/? %E?*m6SX<={%Z;`5*N$՗AU_w!+zUP}3`F7c¾j}]p 9a]S~k jM}Λh~> ͛J] zx7~{nL%rCۺ7p=*PuܻpVVGMQ_u؞8F(Ũw_®jj֯tq-tV@\Z +=/s Nj=vkFa{ۅ{ū֯%r-ũn[E- ^ײT_@~@MSspk?\_8n.x-~ _SR}"n|itu?(kbXܸ t :.,`{EܶH@) MLFwW^yz}_BciP_Nqj;~[&KQvG!+"BLE {43<#\ˤ#FR-ij́]^ҡ1:Ոɟj?јKM@0FOκ EWOʡs(Y4 q)˗ ۛ/L`r}ɷ:L{8*c{V#n)K}hǰ{{)chJ|YIۘYKIw1z{X;2y>f B>$FY DP PByD kDl!C Uz W]f:6:WJY*|Ug,|-RDo\bYL}_Uκ"nE\ "JOR_c/^3T/Κ] ;^p#~lz΁N''3EHPZ[r?qJ{I#u$&5̈H/5o9jq;/3H'ꫩ!篱qY7q5cv;`H|pw\ o/_ b{[*7PFTM՗ؾ6ozaCÏB{wUͭ5U%R/؀*jGD嬙0i::F_S!a8Ȁ*cT!esAR^AI7W#J^$Tiyȁ2b,S>3EH/hUhUU2~>AR EŘ}4n!t·2 \Ty˜WW؞42 AR m?q rÜ (@#*w\N9kk j}uXfVK۾:s#94²3 P- /Pe:4[| iWd DVi}"K J+UUl-uNc}EV}-*j"Қt`S"X_rb@UdKK H@̀*U0AG Z1~ŀ*?0?b}1h֗A+OX_~` :eЊS,T1h|bƠY_?e@$iUFEa}dIX_ @ TU q P %dq 1V}4WWsBUƪJ w֗꣹Ұ2/cGsa}5'dPeh4 hNXU͕՜~e@Wi@ڗqDZ[7Jbq/֍|w[7,(`p6 ^Yx 1~KkUƯ#} @Zaa֗H_B֗^ì/ב/Pe:җz _Ge@_Gf}%d}5?2~KȀ*)@ !f@Up4)tZ >ٲBkd j%g!µR2W+8[WpJ!Ζ"\+%c@U+8[|JX_lY_!µR2^TR%1[> P A&e}(+,Lʀ*T@ DUƯC%d}5?̀*ב| 00u/!Kaa֗H_B֗^ 2~KȀY_Ư#} Y_z 2Ư#} Y_z _Gf}%lɀ*HP0ؙJ\p:u\p:u:-\2t'g ՙg)ts_`SΣF}J/2iÿU_tq6:u\:? 0¡<(@ tAV\n4q:u\p:u@s@Ϟ=΋i}Ӆs=#/ )^{Mi*4xuW}9:3<߹Np:u: -(@C x*\p:u\p:u{@IDATs`+DoexWmuǛuyL=p:uׁs\/D P(@ P(@ P(@ P(@ Pm^Um~ (@ P(@ P(@ P(@ P(0J_ P(@ P(@ P(@ P(@ P 0ͯ(@ P(@ P(@ P(@ PT)K P(@ P(@ P(@ P(@ yTU(@ P(@ P(@ P(@ P"*E)@ P(@ P(@ P(@ P(@6/6 (@ P@x;̰[nAg|*iP(@ P(@ P|0 GQ(@ P@7t8 83 bmڴ)L@ P(@ P(@ P¡<(@ P(@ P@>|8:u۷ť^͛7Q(@ P(@ P*r23 Ph uלYguA~ӧK^H9N< ]zD11l89(@Vh>PVY#н_ btDuGo+,$qB'93E 2.2 0Ae PZY#5W7czT.ug{A bD,[9{ P<~LົnE??#o}݌1<jP`@US:(!Ѐkٯyu~QBD7{LÑHvGNN 'gc4uYrYQɎćK0`FXR5}MѺqqqݻwH]7_]Ν(@Fk6Nc1g'v ~ْ4Οc(@ P! YAss-l]3'u:K**A̔#)@ P0 _E, (@ Edx*wqlH׹˰u P% 69ʂ0=]A]UnPK} e<h{/Cm)(<_ ڳ>f߳gaa|OQ"xۑWs&sΧd?Er=`5V4eڑ yzJh6pQײ9%mes[^g(_v81C\`Dž8b|r֯G(1XoQ,O %Cc܈(;שFтb.bZuC%v}c~mp4=E*+a@~_Ct˫Lܱ$#\|MoD 6EǯMæ?Z zW]!orhArrtj4U:ْA5_O_ 6G> ΋SShy6mC囕Y5aܪ~bgp׊(xaǵ_u$f (@<~vQ%KR kK-"zv1N3J/>|/?[pոvx&}sA P( C P THI$v>HEU:z)/m\im)YR:ujqO-P*Vg>⁳sTUA-!e&~dMSl~;+-_<_Jr{,ID }S&93$q/]/gVU؄!"5ce9ŭT)7PU+e}/jqÒ*i)$?VV)S_UڲXӚv)ϓfɦ$Pdr9$$Guŀ({3+Ss-,aVǀFZ)ݽo-4mw,yk [}Ob\zRZ&(@0 0PJyukcsݪvzN9KO(p݇iRM P<~%#QYR=^e:x=c^=n%e鎋>w5I(@ P^'a Pf q`OY)=E4 )vs)٤ LtK4rzW@rP.,)Yw9_,)-3Krx75RT_,%q"}Z!%:rYڲ6TT[/e2̒2 U I*v?^Nk]'kAPt5I*/-IpdI]x-ԡc%;y+O@TDH6JWρVUIRn9Cʋ%G+QprNH p%R}TX/6|ӔjQpfu;31CuS=q,8lh={J3g ㎐2(@ S燁i@ ۍwߓ=U*@ar8(жx y{kRaKTTTySْS}^9J]C/}n]Y9H P@P>KD D@ךSy^;"85E*h>\JKp]I&4;@J?߄t]KLL੤,g g*` B [6 [Yr,r@UhBY~݅L@L0q1/mVIܳ-϶w=V8((塽fnz=A|}Y:}Bz{RQNyLOB\|eREjXǚzB00^:KvwOӚQx'|dQѣtTK.D2eJHi)4=kv=4W ח*-@[2׍!6R F _rQ/Wzp׽=V S=o +ĐDcyv9 C P@PfPfG D@B֜ҳrbȣ5 ɻ_Re :_;.J J+B7=-#@e]^%H-*uQV__/ӋCҔWc2mYuɉ$!ZqtX_[%d(Ei7Uug[r`iӊ }!ՑU.7^ew݄ͷ[ҵh8R[%]5kLGn ~3ЗSya*Y79AVTP:Ti# tE3gNHE⋥S(@ P f:[vmZwS9'~khi޼y!͜U!1(@a yM[^^s6Ŧ]kW{MnU[nZktWIZkӾRs(@M/_M@)1RG-+m<旜#Xd\]ު߃/޶bsP()*O~ڨ@UqfOr qOO{1.\j߃ j=IxUSR.&;yE2^[JPXmҬ%G@y%;b+J\2-Lw]<P'@K)"詼\զ8]Ve ʙ%VZLh?T<[$Hr} dKgR@D&ر`կ_?)!!!>UIiRFv -@JRZZT)ҥZY[/ZM !R"!WKynRrJYv9ҕVn4!Л)@ z"ǡR> j¸@ G]STzJ`Rĸ"^/7Vw P@VxՀ/qoLeKRV&:!ǯJD@OE;MV,aڅ!$kr[j/hqJ.r:{-LwPbEb]r^!Bero%eL^d7V_$r_;#9K)VI8@&ojeZLijJiJՎV{p_*Iqڵk'=!.MRZ_.-M=V|)MqKem9iT㲴[Hs;0KV{Hr 8pH8z|-y|[io@N^hՋO݌Y-jlrx}C[I6[ >. W1}5-gzUAVT!pZBU?$ג$9y'+VU.[qRj'cŋC*^(y!H=h*YMX+e$%)۽k yz%>_s.q"Hջ[T՝SV)))IJp JSz_/w? WgOG9 P4 0@l x`eXJv_ZiV{aѕtvfpHY)2+E~39QPxR)KP%~zE\+kG Ij@;şz^桫!M~Znܺr(@fP .*(/tL BȔ(fH^*OWy t馷HE+T+ijP-nUIi]rVBL+D˞Cv+<.y˨+\AfRa8Ĺ]wP|.dίlKR%'gW7% QRVu*q I/򂓍]h PS/Oxek-PӫTieJVC}oFWR)]yVoCr& Zt[͈(@ SUd^@D ))E9*溃gʲԠWp)H ಈՠP ttW]*VJ Luimyԗ}"W $M-g*mos%?_[>QH T$h-oM,太u7קgjbZŭ,s`jR TPo>kH>? 5-S*,GKyRvvd@XwdH)bv{Ľ"z^'-ee8V/)(q]}T'ZP%]+SV'wS!~NINJم[(U*7uUEJk|VMLWvh-*r>_ P<5P}nW.(@0 0*̠̎DzgKOVwaŖ,jR՗H)J4i'bnZ);UkH;!!ePAS[z͊,)+0J/!zV]|Β27y. @pؼ+F1PݏƥZ ?o>dx>|saRrDǝU+W }OlZtmӭs,)v!Od-O/ToDi@`}@`U7A9Qo+V+xΏ|]KZ,|3_)%3 Q7fɁc|s.9zT2r>xJ53gL D2*FyR.dKΐԖ]S??Trk( 3_rڨV=MニEJNxEjr&^NݨA P@~!!.l(@!PW]Z}:FG#c;. uE"*DVdծii ?e(>QՀʣ娕 )G.h8Yf/iMho :[f&رppG<{k-U[#X?3f8<9qمq{0 ɜY)dy=Q*Q}6;^0oD_ WI,HN[},s:'bX+;Xt|6YLVX&λ`Z lm'> WxP>猕;0H򊮓fJsFLe*l9L+Sڶu~ݷhŽvMNñ+X1m_~g-R}D(@#,fA P' xj CYbi| R.2i-o*-cXyכ/Œݪ/Z<1/lVj燡1(@<~/(@ P 4Pvb縤<Jvl{_C}9qˆ>|lh@cp2W I $OEԷhy9!7 aO݁9_ƐS,ڴ-_PVV_Xv-瞠&p 7`ݺu~VchEjhɒ&xNp6Yݏ[5՟A|k `HaMakW UjkXrŲpwr2|&$DhJiA+!HZgǒx\$|P呓}12Ym˞UcS*x~Ö7(@'W۫s.1(@ P-+e(pؼd&+&!;}[%K_S8eJKKqb뮻P;v3 wb-IiEȒG {j%HT&?~>yX8ڲ+2>E?P[wHp݇JbTj^)Ђ<1?()@ P(@ P(@ D"XD P(@ P"PXX/&Mt{쉛n k֬V *59FԑE/3mFq-n0TEasb&^d] ώ_2T5`냃`[U$æO>$5X(ݎ }j[#Xry/,E% ئbjEq(@ P(@ P0V\ P(@ P(g 4[nuF[_WlHKK%1Rt'>V{&6=r+Hz DPQw!.Pg3+ טּۡ06<<3^ħـ*R>1GƉef{&/BQtԂ<[g|PUk5^5יOFQ~߷kEsY^.'BˁR(@ P(@ P`@KD P(@ Pvڅ YYY3fL/lW_m"m%OIV rs݁Lbt/L,MZ(Ej ⢵.%_!Lb:G<)}%P, xszHrUj7JMɯ!]2GS(@ P(@ P/B.(@ P(@|g `TVbj;]9K2^{S:S[jK"ʒ`mhAK/vvm|.KdԬ%oL,\BiUQ %1N?aeiMyN[ZApq)Kg1(@ P(@ P -*CW G P(@ P >cXVGt/r-xWKP ֋GG.TbGGuE.? _=>S ><2'O/tZ&P@Pըծ';f[ BR(@ P(@ P H%(@ P@deeaܸqصktwn /Ri @P51jjc_] XUmKfbyArΉPϊ)@ P(@ P(@H`@UM P(@ P[nfÞ={pW]?wq^|Š2Au0-ʂ VW`H@UX?3f8İ g~6C(@ P(@ P"\U^,>(@ P(@# ;[PXXؠƀBJP]Z]ʨh}orPBtǀS75(@ P(@ P(`TX P(@ P&شinv!&&&֭NիW (@ P(@ P(@ P`@U8(@ P(@ 8222C/Zk׮6mRSSN(@ P(@ P(@p0*̃(@ Pׯnj3PZZ>}ҥKu]XjUi(@ P(@ P(TCyP(@ PS`ݺu9s&+h; / (@ P(@ P(@ P`@U8(@ P(@ 8^}Uw}=zrʕ+N(@ P(@ P(@p0*̃(@ PWܹsq1t5hTM(@ P(@ P(fTQ(@ PhrR=4EΝ]>Ae P(@ P(@ P@8PEA P(@ PN<裨EǎVbŊ2(@ P(@ P(@ P  "(@ P(@3<'xh׮]*s^ -[Rz&(@ P(@ P(@ NU)@ P(@ P@{S3СCȭ\-]4d(@ P(@ P(6TQ(@ P̙3o&$ TD(@ P(@ P(FTYQ(@ PhӧO'|*$: <y晐3(@ P(@ P(@ PTPuLO P(@ P@BBvڅCh߾=,X~:d(@ P(@ P(6TQ(@ Pm݆}a!akXdIH陈(@ P(@ P(p :UA(@ P(@U[nqNUXX f3?L2NK P(@ P(@ PFɌ(@ P(@ P`„ (++CAAAHgq.\z*LD P(@ P(@ P8UT S(@ P*0vXTTT`׮]`P(@ P(@ P(@`@UK2O P(@ PmTGmm->Ӑ:vG}/)=Q(@ P(@ PNUU*(@ P( \s5/~\u\0rZ9' &(@ P(@ P(@ MUadF(@ P(pW#** ~a$A逸¢EN(@ P(@ P(@p0*̃(@ PC An-?3τn… N(@ P(@ P(@p0*̃(@ P B^u֠EP4P(@ P(@ P@ 0P%(@ P(@*`Xp%য়~Bvd<Ag P(@ P(@ P@8PEA P(@ PN"666m Z۷O?h陀(@ P(@ P(TCyP(@ PS[n:u*V^H]]bAg P(@ P(@ P@8PEA P(@ PNΝ;c̙xg̟??L@ P(@ P(@ P¡<(@ P(@ ACA}h׮綾^{v]3lVDN|r<#N (@ P(@ P(@ P-!Pe(`0:qr+b V0.ccb9ήf0_^71A#58Уo D{|~:nk"ط*kDb"ף&ڴy /dlrښs9x79-(@ P(@ P(@ ZJR@ l[>8 o#|:3)o%IFAsK*΁Q&9%¢Bu=$_⋔ԧ{Ce^ƈ( Rؼ&/+, ؚ>}m80@AAfkСعsg1Y"N@ P(@ P(@ PAU(@ Y3f;`M-@Μ2iQT[Ζـ- U[P^Cl{ ..e<{82j1%&vXFD JHJc$~)p[29 U[#HV "nűEx͡6 K.]:ZQK.AII $I[㮻?hX=2)@ P(@ P(@ PUU3(@pX? flBؒX uG{c(l*a@b=@Ne+qxckVjDAESvfĻ[*ٺ\Ol MuWvl~op@PUQ&O7Õ{vfhDQ|u+9)?@Xd -R2]#**^cѹsg<(@ P(@ P( j uΓB,م/y3V’nguCbQ+j8DU}Cp 犡EDT q%1_r6N<==\nGOh;СbF]qCi+Uv"Ds{JhTPļwm-(zEuyfWm5ݪ kV#V&.bUb}ݼ1(61`h@bƢm hmtl33sg{|O̝{=;ssshЇ@YU3~& e1!qyŅ+_3U1;0tPWڵqN՟I J̦t'(cmFU\3e9ny9Bf@mW&#yCs)tOrs aϥ:do 6eԣ1%-۱?yV "V [h@U1 [^BStdJA; (7evqym݆իW㥗^¶6+/"֭[4S(@ P(@ P# .(@ P@ \r-!VY sFKYy%ѱ^M}X۪$&ziӐepݯ!.v]dYCvV[iiv|m[ǘWf)Nt?1ʺQuQcv?R۩47y&ȥ,FGݴc2X j{۫Re31d?,Dzm˳-@IDAT5{{Rh} %Zr -B G]KV =ֶ\_k[j׋R9Z+l괴9^N(@/7,W~;/[x_駟Zرc(@ P(@ P(a(@ P (喜,KZJdggYE"bԆpl9"|.w9雘g);Vfpl!DKnQВ;,mCj R;zK#LddY+dcϥVwZ<-Y݂K=n<>AX43YK,J,931Qꘓ_b)?V"S~=YmIs:Cի+V;wj;L P(@ P(@ PUd[(5P (ՄBnA"sjLɶkKJ{'RhɶێA>CPb-e_[\N]~5Ǻ2cpS*QֻZj-eEb横f4WZwiVXYv~9u]/gRU#L_F9#TF7xFR^t5d̲T8˓2 Z2-`v.pzokb~¶*KrQ̙3%T5p@K{{{dˍ*zW(@ P(@ P(@ S`j- P@Oi.9#A(,Ne;[҆EA% ^ *uZj+Ys-iDGG@{0K飫|Y7*khoN[+^ ]Pc^kdzm]^Uy]T/hȱ4z쨛=Poϐ},Z-<ȺbV@;ppB-Ǻ^o (@ P(@ P(@ P`*Xl&[eΐg9GpK{vfh괴uvZ:olkkX//\uNq# P(@ P(@ PUAes(zG)Z<}4 N;],-xVhtU[2Djw_`2KQ%[ )ȁ*5Ĕ13uKvVQü*6r[b\@UoDx1{:xʲzk{rKn%J,؁K̭ti%S\ TY,Ev K{7_52=OS1$[oY}7ۿK}ڞQ(@ P(@ P!@U0(@ U@ J,mmb6'eƟ<'%r~:CF*S7cNuLbfKIɰ~2-m-`'~:93!ldIg$! 1jnV1Z[, *3<(G?XZ,jL^#1^u vZ+J,YK.h- z9jx Xg:9.TPJݍ"pg/YUǜiy_}M[TᑥsS1/[n˧~'ׯN(@ P(@ P(@ PM*7>(@V/i ֙|Ќr89^4aHQfi: C)Jt}Ve]@9oNKp蟷}~ٻ@u jȋO/c3;UKYezni(ZgXk=g s+,׶=C3,7 xo_@XMWf@{^|ݻwX)@ P(@ P(@ P`*(@ Yz  2Z"cWi&λDXQs"nz#f0XJm[:-E9kh;VTf{kH˱/el#|V]h= ovي.e~rJ,UyDe*\7Gh͸,b_c/;fɳ50WgmYzu'qy,/,׵Kga_^mXa2:_6 vn\ϫtr,U/x! *u sY}E 0ceޡ(?OiJHg(@ P(@ P(@nSV'x(@ P jhq]CW&hmAX1l5ljGk{g >4@Fb.^kH Gcb ȼmME`k skVJEUIz~?Vf&tk7M& P!za[)rv߬uc233lS(@ P(@ P&@Uܸ(@ P@ S~ ¿ V[;k5ĸ%2eDELwL8w0oHB)ϵb{Ϟ8,(AWfˌz<6Vo5S ":mh*Q1$@U C(@ P(@ P( TEqq5 P@ 8R6n3S(@ P(@ P(_c( @kIKqʯ^b1c`hQuq ]>(@ DPڵk5jك'x"=)@ P(@ P(@ P s(@0Z3>y>_oCg(@ D@ss3FW_}+W~#(@ P(@ P([ TV9Z P(@ PQ+pU}ػw/';F P(@ P(@ P`J(@ P(@ H#@4bG)@ P(@ P(@ Z*](@ P( {8{J P(@ P(@ P`JW`(@ P(@ +?qqqطoVX!@s P(@ P(@ PZ*S(@ PЏ@SSƌB,_\?H(@ P(@ P(@ P@**;K P(@ P@{~,[L(@ P(@ P(@ P s(@ PbGة5GJ P(@ P(@ P obL`ǎزe cl.(@ P(@ P(@ P^[bݺuz"E 0Pr@2dT=(@ P(@ P(@ P!|<@ 0Pma(@ İmã)@ P(@ P(@ P,`X<<`JW`(@ - TRZ)?1:;^Q 466Ν;h"P@S(@ P5@9n5(@] Re堢H(*B x裏0sL!99:\H P`*xl(@ P/%n>(@eLU@.)@^ 0P+.L P `&d(@ PzR, ;E P 6x@u'@U' Po~!k.ƾ5­(@ Po(@ P,_}U)@ P@+JS  Tߔ-Rhhh@RR~c…mQU$\@ P(@ P <tR6H0Pf?J\}`vܒ@}}=f͚^z wóS@ 0P)@ P(@ Wب# Ur1 PDq[ ,@UF\|Gy/2,Xpb@(2H P(@ D\"^v}`Ol܈B!PM 8rZ( TEg]+ G S f €~l2umĺ`״~2w:;8?[ F|C L;Eg.M&t/ܺ`Y܂(@ P(<簥`Jr#JԒ#N.(x1{l޽w>{%YqL݂\+_gg蕽H}X^ZWs6ڔn;Kz@ xʶIvm2u 1v&1 L cǮam]6d&H3R(@ P}q+ DZHW(@(C0PV6J Q b) D@]QhU+}~\=,m]*)v1)9UE"Pe`u_n5J&I "PU@v*!%(@ Pб鸸uy98 Pr Rz 0P%_c Ě{goxW|'ֆR@sQRv7MU:ja‚ċN1sA7PU5k]3n}Umv[5 ՝[o(@ PΖ-S  TRmS@x@+.L^ 0Pk2n@ YsA~~>͛sw@(nEk+„9m= W/AF/Ptی#ׯJCkl%5f6{,P֗ᦝ%*3T.b8):6% Q(@ Pz/=Uc%b+(@(eݓ^*KP@ T` 8?"K{JRZ>@IWUEjl*{<UJ]j °2棭4\O}Iy@յ1jv}BZ:FR;ť(@ P@x+آl`*< (@ !J? `*FbQwon݁i0}LM[&лMOG˄3bҴoa֌Ixֿ@U;ZlQH`c <C>Qr Ӈ9w{(@ PxK5bCب3GI P@ PJQ&vRb$.N`ߞ={as my8: -8>qǯ@Uwn-vlz'_xkS:P}Ჭgi/#9N벎}`y׻ψtq+(V[R8ԍ?PՎ!Xu_Ոr-flܪ<=ɞjCgjyWAQ"rϧSY]8{>-{xz2%s ub.eļJZ3B~\;F̓.,.z<6w(@ P(@ jņUQg$z}Y$UYu-OXU7wa0fYVI-v< E)Ocaf-wsʛ)iuUװ+yj2^i8@J҉4_ބ]Iwa=&R[h9 P(@ P@Ztx 0P/@4 2Q*=Vc~cػw/fϞq4q@6\9>99t 5u c֪@ 'JTHgZ?kϬF&4{Q]b\Qԣ_S\>:R:gmK[M8*>-$".N{'NAO$ rP,-cׇسr#8E1N:{G%%\/k4dW2Six _1Wn}wؠb2Ppߗr5g^Ǻ%3:뗟uE,8gV^2Sq>CbYwXq?߁*oa*i/w1P{c̯Fi.uřDeUX}/(@ PM翢"`?'E Pae`*S@ ,Xy)NR x TM]+e~nUR?kcE[JBMJ4<( 1NQW s?6`>aLOqe/YgY\Ru[YqW d9o&-";CA2P vUܻ+@Pck:b @eDe[)؛ٿmV\q+1d :mL=Pen lQb*,4LR"fe"45t1 kߧcмC P(@ DEU9 -@T\B-P X`*_?_{H LO]Eޜix1lVDu**3Nnnv,6@m;Z% CEH6J+ۑ +Y 8ݩ\|o2r_xDB.ۻI0c6'[ĥ2u|^;03Tn{?4aWn /0pg9d}&PuCu2<Z-%vM!ȣH6CUN>*lpNvZx,^7f붺sp9X~C&CKekƶ#]ֹF2ԙ\V P(@ P@<2r1(@U C(<ʰ_z`J/8(_j|ž}0k,#@ tR8-Fu[JLS+PQry^|cPn/38]En4v3uv*N3䫺 Tܿ{Qr6ؙ-cs]IFk&U6PpET=eTu?ՎhB1CU~U2IoقlXS]OM%&.xv)ZP)Pd [umJm31-25*qdqAuz*NJ=6(@ P@ WԗWp!(@ DBP>cIX6J9~h4bHLLs5(U@mrx y+c-'7ȊK8ze/W 9ۓ S(]j!/lU T7%PM\pKFW8q2mJh4o~ 3yFLTUmk * ];e0Gno{2--6Ke\Rn r /6_{Gmn[z<̩MGx, P(@ P@n~} 0P)@ D($YA9 P*C]@Jbm*X}E}xNo~pU^¯Olrqqe T.*~]:d UUb}Ci8d]S3CEqɾɫ;;3g u@DߑHѤaKw/$cczDH|.~&-YdV%,x_(@ PW{ Tv9z PQ%ʨ*;CtXT:tq̜9Sgp(B.`!1&N `"V 3,f:n_ַ7>93 D9ݑf !mX@U3m#hs E+ h a(.+PUWKR{_)S .j; b3ay x@1 k&6h=3*]0Mxlu+>ս's[)@ P(@xK1,@U C(mZNeTyB P(@ H)_RU| P@2zjS*}֕~GšC0}t= c@ .P̺uikJ ^2gs$-xv,ԅk+gVeV8'. h=}Lc6XG&ރ~v\-Ś`0ߞr TSj<|h|{GOHyQ, ŏcjܨ5rWnt̼ܢ5Pej([bFڕJ| tf[C$Ǝ/b8i%-4*eElb26{mƀ—|KjZL5h3Ļܷ\DEyY&l|` )4a~;sp)(@ P(<ua(ГU= y P(FŨU1Zx 0P%QU Df/M~G{p=r-uug&bb8 uORioOn% E :#xHn|*$7#"kd@Z~ߣs|_c .n4?}f_']Tf|~#c1zٷ P(@ PpIs?Udk((C*? (Q . %%Ř6mZDSHbc(@ P( *K*Rе(u]^. " @ϟ='ov.(@ ܐ-P(@ PIzN.(@eLUa(@> TVV>>S nD P 0Pפ(@ P(W@d?N PPj0x!`*lUAdcz`G"@ P(@ P ` P " TE;(@oGW & dQ5zhx>L W `7g Xü9fwzy+$@U (@ . … {7ù[J>3۷-pp [wzSbnk,YG7 caF^Aa [#J/a#hG_7fNY¾1vvzYC+ o_a')F(@ P < bژ9suggΜ N> (2Lhn6}{})=-X(*+x GKW8+xhI׀ph~ 0-^|_Wl vb7 g W8a8[pjs_UdK(( fwԋø9OH;bhoY%$%_f%O^\r/K.z%\/R@`JO P(#^^u{z1P2**VdWg"-- :!x kZb4e$(CJP.(qq4 !07yXamCf}e[+U|g8Oߜ=|oD 2tGO'^zE%P:&?/ MTnz߼I)ۗLty*TΜ9e˖'f*J^~nk y<Kf1Kaz]twO£+ǢiO[C^&zus<83wLU8U* >{HK(W;kH$=Bzٸ,^yi_Vjz`.Vd>x9{/Kq,< uřw4ل87pP>JO[O2>$ӱ)(wWGP!Tھ2L)籢qK/W sSIANR (LPbXqȋ1-R, UWSkBs)/ "^ TԹg`wE/zg zyJE8!-ފyse=Z/z&-SpzXWOQVVwR^榷1vj*ϔē ya^߮[bozB״qLH~[X;Q @~Uװ+i֞R<\?#<%"|(>sMQzui8Rvz)ePZ.۱8VlNJ%ݽ G\k |!8Z.]I9 P 퀲. h(Hʪ_„Qw_ty֗N&::\9{lK"PU@Ju?/)ωgp"wOjN_I%;߽NH+',5ǖ/#u N+V(* gbV]zi_a[:{]N:+V7<֫$¿,h1 ?ŴpPްtP2]tM(*:o=`nT+~㏊ǜS꥗@Ug ! ?ߌcEL"d'Bk$ yh_&qblrb,H"fSlm'b(m˥}E['q8=zژ[W/aE1΋1oÐ뗟.zU}SzYep#lؼǎmDTM>߉~u }.b(_[7I}}~C&wȘ/)E2UPkކWqY5k3˪wayovA`*(l!JdD~$\|ё N__`*dEl^5/ / x/&-nYs b֜xx35Oed:GqX>pBF@MtnIPY;==K+sR 155Ũ cx뭷쿗= }ԫfJ,(EO+׭KXiHpb-z$~xA@IDATä}>]Փ?m%W QWR/܈1@V,;;*Q)Ƌ)c|q D/ Xw8A맠%꾌Rgc-b2[!G6iSx >Ovby\t^ʛƟJ^VcE,8O]Fie *@Z4˘:׎oĨyۥU]zi2 _c&mlC Uw㧇AM ʤJzzߘ ^j&bS0*"h{2[JZ/YlYrؖmC @'w|;r)&ApCIH{|);sq`,5d`Jjt.J41uAE֔1ʛNt/+g+H'$Mo'MMgxZ#q$ r O!$ʱsp*G掹[>z|P;%X׭}0,S{ lyCܽK1km1ĖOKX^U7z3$3tqWÑuSLzi?sӛ<( ^/_Q~Wg1zq/ k&?-ceV7tTy/C{ǧ@t,pmb8yB²˟8rYk9FR})Bj\-P&wzV4#?Ku+5 Y 9 P軀(/F .Y`qdU}xK$ qBּd2g?YϼY˞~8v&E3AگNH;.y@$wJ1b,zvY#٪Dz󌅲OݎKXf20b?ߌq.rN%V΃RPfFOO%.dJ>;2]g8b*rsԥ]/r#o}~o;PeQ%#/.w׫7*_Z$WZb \c˥ o_RRniiC.R:m(;m"`DѡHMpIܣ^_1\,<(/UO:4"><_Ǧڴ}g%+i?9 Cfi*K$fҹ_IG_(+^Lz8!]o1l}\rdۅW#wИDn}i6f))nn8q8vuӛ?3yE/S"z ozKĿ[",U"P^ԼrlO?Lz9hoIҚpN$hp侣K/YVPrIr'̐ƪmR_ ULMg秗yK17A[:d6"<<TrdƦb 'f0Mp~q?p6҅mX|{]}h{,;Pe3 Tf l_>]xy %&l;ѻh2S'tQ/[U\+[yx@ Ḡs #]Krk$ҿ(p;S$ftQ/AxVL[iSS>+ܢ<\ٙXz)Nu%Y('."+G h!..Cf%!gW8$BV-"_(Aqq3 #+ SsHl@Iy^ke9. P |,O o-Kb #IؼE_W4sU]K*lJPk=p+ݜUWSCT֊#}f˒i(CMQ&1bV0KZ LU ^~SÓCc c fԔ־עi|0C?KĥP*/^z T)cv2QkHCў\N.n>P/Xe bDIu!s v\zu؄b6UZ|q$)/Mzsh'{lj* Um1ɞ0_;o"@X"ӣ]LzuikbVˀx|Ar߻I\rx0U2a{mZX rB Q  TE(@W=PZ[.o0 :pI}R/= نz:!ЛM^NCJyXC/~ (SJ]|2)^2TGSo̦V 9BwLJzH.MÆa^׏1Q(mXފEvX.M3Y,~| ;"{z9_c{7 ˟Q l T;(@i2> d>+bʗN- Iw=bso6V^'O_j6smO(YB O(YMUQnWIC Rޠ7z4 ^! zWIC Rޠ7z4 WHy8B&@Uh0(@ VY]UUP(t0:h7 ;h+̈́I Pػw/@իW1jԨPS:9su$gΜɈ8 P(@ P(@H 0P p(@ DrF698KS T!(@ (ddd#G 뾹3 P@^{N P(@ PUh E PiT}Јu$Ι& -B{.mЕkCIj võP7AT':{9gL2\9>=}}_([4oٷ+Hr({BZ".ZS(,ӮO6ܺ SN#nQΗ9  tY_:*o4SrE:YLn&my UKL'IiR},fn iU\^ /sdK)9wzZ<)RVV/-7d2jig_fcdU7ɍ+H S>zYjZ}jUrڛuWJ%^Gj8R. մʁ ={DtUf/ݺ_ڢ+vxpNE;A&O3roV] 9}XS TŇn-q:-!5vXD,c0PP]v^NCqӤxܽꗃ' o֍ H VZZy@\ףAoj?˪Wm ~ֳ5eA#)w.p#tg!wJ)鑆V>;fTMq UFK     jv= Gƣ!erv3(\5Vm^<:'K0!?5߶^YKhX1dm3d nB:PulF jO?!rvwI[Ӫ ypꥲ=>?YV5ZYw/.kwn[ɂjSebΓNR~xU)@`ZY{ezā3A_%ubCR!u^Zg!*ŕ"HDm^{e#Dn+[&P-O?{ݶ̨_@PΙo"ut\b%vyǥb"'*TU#_\9_^ejOBn2u+թv{6 0v-ׯ^{M. ZC%@@PI    #x! 0nzT4(JOL#YndTKBJUNg`DuɻU4lj1%Tmܧ3h8ʼOģVy9aa-7\rJu*N |:˭|B!) Y*$/H۟jw\;Ku;ui5 PMXԯH;P^E=< ʮqVIm2JРgl}gʔZcW 3"*Ҙ^B0ϙ:nl ^{^_}·" \s5 @@@@ʈ|@~sAut'љ I3zu5SrJ СDlP) F5uĪ`T*?:}OShEd/9 kP]T2ꪜ*87P_R|`-E89il#Mn7xcU"( P5   LPsb@`<jzX^9 W5wOq+չKsnz~\y]s-Ͷw~+nY8-`< HKlZa.[s^ -/(qi[k frYγS^.>k:ee t~LbL-=j&=oߥ:볃5h 22~Β_UCP6}yg Kϰsr`R7`+ā n+>íqwoUS5?,Mrճoص3Kwȵu=@0$WOybXPyNrN@TсJقF@P \*:J<5ݴ 2>YiU6tHEòhN{>խ=eynKc[k3k7@&@@x1 jl@@@@&U7i  0{K= AtbzƩ.~Af!IXڒ{eiLQ'eےU?MszRL蘛'JJ+xZ#u,Yq\KLcJ*TR2937*;8̙%J䲹3e=W558e8igBUVro>5;%9ޖ}T.,kzI&R6S=TV*#O#xd)>uP{4ۇD)ݴoQV/4צ;qo,f:l?#m߱=zuic*L=$*{s\.7lw u{ &{_v- w]NTɆ3e5{ee=zIXiG)K#N%ۮ.@I dƍӣ_IA@h!_<:sA/@O5w'!/|SU /=HpTUW<~%v15^oǥ]9/ e<gr4.?%P`iLf@@@@Ƃe /Tf'w6%{V%=sQJtVJuH=Sni\fN?ڙ(zs6PԞ<^϶ioPAϬLV~:ՙ[^쯵ڝ5);g֊rwz$kɺhΒF:k U}k:KUtf^k3RY"0)9~e2rJvj,`qNۻ؟w2wPS7nqf6t)Vqfʲe̾UUےD8555耪 NC+03T%$Pcͬ:P}ݥ#4r̭nOW47bG\~ݾNr o˙վ Vx̿#V B@@@J`*M/@)+}}Ie2mt>-3ocۓ>Z6}̜^8>3,v|} 4cQ+-%iTRi2@-3yj?]Բ2{hyrlk_ >qDͩez_ϔ)r =ەRimm[ G%ҫEwY_cRno)@URYY)Q^xPeK> 0z*gPTϬZ0a;T,.='vOHKEL$- ' 3r㥡H4mZ^ZޤF;w_~m &Ei$Te- =@= !Y w^r@UePa9dTeϙ/CܿZ\Ȟ Yθ2n-.lrP   apP5@l˳zK?ߗυeżCɿiizp\=n jb){lݩϼ$+y! #c,̘1#(o@ҽrFy1./rsdWV٢ U+ꤿ`" r,o">w,i1;@@U)9兣'}9_|j\sӭ2=RhUϊd̊e"{\j~$GI 0S|;,/)"xZl/Zt>%}I2);YtEPn^xX}?V6Y=;nZ5K6Ꙫ5mҴai:Tٽo@@@T~ +^ٿmjPy*jT<`@` |+_D"!L/@b ?yP|^ڽ[Pvˌz3M@TO ɮ%3dN3ؒ}Khњ̬޼p8 L*;ߐXv^:/(&YXӧnЭV{ %g^v-'u J[I1f zò̲٨e'   A`P%@ƻ@Wߐ #)\hlg > 0<>|_Kg'>)\@` PL* ~<9:Yo? zPSU:m \-[^-ryY~ճWݭfwDn *Yr^o9y6#dh ~5 HUt'a޻ܺX9HuMktzsˆTX%5b ,==$Zioy]sa Lo~"    j5D@@&Ui$C.PL@*d'2eGzbgcgPMذ©m2ڭ}X{et7`F!o>;ȨVSCdʍf‘XQ tі4t띥k7QRz6^Y<ͭ{гpSm\٪cB69P>/*?lR٪Sne-m;xg* @@@@ j4)@@@;y䷿|w $0x@Uֳ=qFʘqkvvrp?K}}+\%AI䧛d՜k{!Y^T@ٽJ|˗X&kБM~UlXF=t[-ҴeiSdJ!Yg2橊%t̙µz*s(9mԑO8Pu~R`3WuCl^Oe%   c]C@@$oӊiN~d;KVs^S{d>\)VTE=o@@@@ j)@@@W͛7{'eeei؉ PUgmڰ4/(+K' n0[Z嬹d|6{^J) ʮ9FUSuP.IPtnETpDel>aΫr-    0T8!   D"׾&dRM囜 @F*{ɿPMezb WvK9Mv`QUN5 u,ۨ$tt +cƹҧWbJ*TR/X&3glSNvsu0U2_AJEάT̯6    0 T:E"   Po\*{? Fidu9gn(SP]GRnyCPU&+d@t}H;)3_54*-ܿLB;"ZI[OǤbj@:[%qSm\٪"JC<K} @@@ƅU㢛$   0oV_?`ZC"0x@CdJ~:1l֓CIZndžz,#hS?ӧHꭲ #*I sNC1yu[zduXloGʥ=[NzYWn4j-u=H_RO*>S/f2   c_G@@۷o|P}9&Ei$@1U"Yn[^0f+Iﮌuʎ[KNp{Kysss L)N~yK%iK6.{^O*gBZ~GZ{d򙙣T@b7   9\P!@@@`r :uD P@qUFrpWܰqgv=7rrs!>gPT*$k;'*,mtzZJ]%#K!}FrhOܷnxèj٦W 3h9e@fd|O="kf3mrŜ pɁv;sgN.ږ.S^.og@@@@`DQn C@@($o[oH*)S %f? ` PeIe2gL+hHuu:],=S=xƯxzt9T&t3@@@@ jt)@@@ G"@Q%T+<Uyve%wɢ$ UCq@@@F]Q*   `lٲE鴜{ E PU%U-$\"^!C,TD%yKc͙DTb?   JOP@@@` l۶Mn*~|c4 XHL* >'&dyQU}.,H4mZ` @@@cT:   do~G93Yh7(@@U`g<'IϹeӧ7ח焩e2}Zg5@@@@ j8T@@@d뿖o}[*\N@+@@{Z    U%K   % TFPKFb&U#    0T 9)"  P Ug9L^&or@@@@`.YE@@(Il۶M>裒#1Ln&wz@@@@`8UD@@(Y`֭o;_'sLZ&mp@@@@`6Z2F@@(E`˖-}vIӥFZTMG@@@Aa@%K% IDAT@@@oH$T*UɜVI4@@@6@@@J -"-@@-o@@@@ j$@@@#<"|Y 0q<(O?tVgr-Yov@@@@b*F4   .ʣ>*E 0>~__.s|c4⣏>WJ/,ggC5    P5   ` TUUɎ;o@ O`ҥrQrUK,#G=    E PU@@@[k_H2Ʊ?.w߀-x{0 @@@@($@@U!#  U#Ma['?)tڷ SL7xCf͚{     &@@`BG@@͛7wyF< A+zjy3eq@@@@`.   MXwPgo7Ϳ˿M7{    #@@U1JA@@vx@oa/@`| |r˛oՐ.H^u9s@@@@R*E   &կ~Uv-}}}V#00}QQJeu9WyG&N#i     P5*   +`B<䓒H$r-_~y_~Y>f      PU@@@Eaa%S&ҥKѣ6.YD92K@@@@FFq@@@A/??;3HJ#?.w_c=&{/4    UgMH   C!pKmmCy $O~򓙖2k֬Ij    0T 0#  %PYY)$B իWg     C"@@Ր0    lܸQ+ƌ3@b}LқnSH    T A@@@ j)%ftN@@@@Q j)@@@+az˻m@@@@@FT0@@@BW%O$of$G@@@@@`vb @@@(Frb@@@@@a jXX@@@T{W륻SI     P5dd   p6T"     UC%I>   g%p=ȏ~#y7*NF@@@@@l:=E@@2˿~Z^!˓@@@@@(URH   0,ׯX,&ڰO     #@@U1JA@@v ,N(@@@@@BTa?   ˳>+]]]#Z.!      ʫ6   P5j      ʃ&   |K_ѫ%#    Lz&G@`:,?r埗;W-  [^%LԼ>Hl+jZe%0{OOw?@Yxy@.YLS=:Up*OKfO/ _?W_}uD˥0O5w|b\|__ ~A_<{wΈ֯~qi\_BZ{#Z KC?yIWɊgTG2gBKdZ:0{J/wu͔K|ZnB@@@`8N]F>19.'m }Aߑ]2cF@ k>ٽjoi K'vk=٥Ƕ&#|ّy[eǏuJ-+[[_|K2&<..Hj_"rt'vkj+P.- ˊK<#NȔ/M6ʵ-Q$\#OnyE\c@@@&U/i  0m3ILBҚ8 ˋ))[vTmPupe\fg?Q%dA&I~& C!Zyuvq ~!  Lf&sv@`< ]SOT%"?듔^m"OFz*PS1C{=,V@DnbXYҧsC"*{4/ƽ l=)wl vKρ= P5ld8.YNq9POOiz;y|:*NKfP@uKlYG<(C:jRT=Jkb(P/vBص_VYQxf\3Wun2 Zcywrh:Y|V=F2(@@@@`f`GbtnyY>7O%Γ+̾rpղt^8QY]yܳB_/.'W-] P9uO\^TǛdoɅ|J^'_"Z=tUy_Η9 oQt U>Vߩc\}|Ԣ些W%>q`ֵ;,q| G/n-ʻf\zOOb?Ӥb1Sc~!m˫^ ̹Hp |Ly\λj?-M{m)++IeONmzVI-:%Wݤ4GM?>OV.7.8>g ׵;fN?Kϐ1&9EH=)=3dg׉*ҵiff:, uA`Ǥ, FapU4@`H֭['9 ^=թ7MYYQy y?'N` zL=|Wd2̧/|م2grͫ2cɳϽdYXˍޱPfi~騘CYue~uƧ57*E: z^zhí򊙱ϗ .@.r^ys&XVz8L]W-Ο5}Z?3yFD..tMo+e:J8/'Lʥ=[K\ߦsIcwx3~{OK_lsUj}GK8r^3TKEؼCv.^tX^\'8KJjDȚ]V1iMg~}|"   0/@Ɠ@Uʔ?nΟjM(hT}UI9$Pz2mNOMͻ&4YeT3i U>Fv)Z[[}GEշF:yژ6VA:9 =!ks17U"*,)= {T !sF1{:cVTQbgRun%"jPjF:}k2(漒qvyI6˭~ժR͒  ٟ?HV 0"x.w,ih j4g }ƙJ+1P哩Twveqvva):w=d+taԡRۗ8N躇þ::?~Ɏ=Ip++bIÐR U~g U Uum:Ar*ѥ]utjZSTWw8Nxeo}:ƺZ#ֵqоnv   J@&Uki, HvJUUvWVʚ̗x4 @@U scBqTE/`yD5ZM@ePTgʩ Tb .ZU$ڬ/ۢ`icu5*sL]̛"P6N9fI:b >Z1ssƊlTШӻO`sH̴?V/KJYv'@&FiR97t5"PQsI3L/ 2b]yZO 0B~~oJidgc( JUUS{Ǡ1{l7)}P7j=JTcG:TL?uNU p5KeGjuژV.9-Tn{23P޹֐@XEc!V=fF_/ܼ&FzPMEʼn'H_Yc?"vbt-;12(GC:]HޯxW*Ÿ)O#u8넮Z,Q_O={.O6@@@4TM HٳU~l|V G1Ƅ&AXYg݀PMK89C)nݧS]Mp7aY$\k͸eWKxnr87-ÃGMETmcy}SMȜJv:v7V[7t 1Oށz}nqگ>1yꥳod统 M.Oѥm&Uիϟl#   0=H@*|Ң-#Ű{yJ^08i?<;4sK5v0cZm=ݮ=e-mi(농"nroZG;{yfJT]]NpOKAh\|npt7ZZnZ$= FPU圯ƄmRݝ3OWN~f!=CBeM.t4wM'YΆ$*INJ"'@@S2#)`˲2' jܱcU޲ht`n[ m>zT W>Nv/u_E j3nFI^ۜFpJz,q|C3Mv]#޵- ~@`֮].Q*b@`Lz7;cAqF+7_{UwUN@UUHZۧm twPr'm6kzg{clXUS%묱l~@iuf')<rj[@@@&Ui$ 08 @Y2Mo91+e M>rj}^k̀a?m>ÝS:Hn]?!<}O$w%'mʛy˙q^/G[Ԝ8ߜnBˮ)Ru%C^?jɮj7 Lj>) &I[@`OD}ӟZP< `Yǯg:״4eqpmxO?xPy T}5SJ:TU::7y.nSu [Ef,*9`ڤ/=z {p>QڋhxjoT%+kU{PKfv#9q-cCrc]9E8/   Lp&x<@` 88URD"lc?xZ6d/uR1.V|mxTzz8:[j%;'oHٹ{;+~ͮ.Ω3LY9܀*Ü왢D6O'U^Nмdf(s`/{\F^@{"BF~z&ji ??٫od79$U= l}&]@ V v6@(pw??UN 0vI^uq|"ic1߱ƙvnp]2<= UTwM\h__eg~2[yTu$RNU3ynHNyXn@UNuE*:~z,Yc[y 5v6ժcfh^8,+s`]'~6`zk[vRuԫJkZsFԵ,irrST۟}h@6rSJNg1R7P'3mn?@@@` P5#C@TK_v >_;_;Iζdy 2YL>X #1hvMlwӚyuR{%Tg}v]?2E2|ƃ*fڤ:@"|uBv5(>̞ɹ)a,!bd(hS@2^eSS3qo} zh|Sɞs,jOVAFUQpFL n/oly AhN@@,×R U@-\}qu8>+)T1{f,ݯL}̬J.ƺMXeFHǠ[^0bdsceN:*XYrz){~{`HHtI\&l̰K24+XbUw*ZqҼ&z~ց .uEYv0$瘫-Վ*ե$_e.*Sb>oNItk06/7vD2͙ۋnw|3{9CR9';zܢmgh[}Rg[9Z?CYm> eL^>Oe%i2.4'l{PL}x.yU[o/|9v49w̙Ĵ"l}9ꉕ\s]ru3`zs|PJp,p`>=˜hg^йA$@$@$@$@$@$@:"p dLU(Dn!=FXSU Uu@Pw.zliL@UuCЫ[tS ..(}{ʚpyA|]F#Dem?X#No3qa\tsk^+}<L2!l/ŒnJ+8TT\@=4S E!=e;$ŇO$@~ U(..ѣG  "`BEyM!W7f׫u]ZV,{5h IHHHHHH@hҿ 4B k'bfN#l*1;f(ZRKf C%139<ՔlĨDK ka:‘Z?Yc3q.5%ձli^r)dL'Ut H|%G# @&Ps|-kė_hWɦوRnFMswjx"&*w؅-sSn Fݪ`wVIc+ZM&GMNwNҍ%      Ur(HHHE لG/S[[Yߌo tvR^$LFbXg-Q.'0X%2mEx2aDA  iӦرc׿( (lĝ|#EmH f=6?`Gbr~(^uB|ňFp.f.~>QY_7Ki>@$@$@$@$@$@$@@kSHHHZ@6-iv:d\fahLF*K,!X&쥺7IHچ UmÝW% (ބI*uYH|W "c0!Z?,!X&䝼5IHHHHHHC*pd+$@$@$@~$`@iٷ8wGk*qzMЅ_zlHԩS矣$@$@mHy|^!~ᮩBEB"WIHHHHHHZ U"     Fg? VHHHHHHHHHHHeP2\* @ L2%%%8|pdu          ǒ- 4OS|W?ٌVx* 4/  믿n 16?e[)s8ذ\\\뮻Æ 1686~lk`Ogr 9%h#h$@$@$Ѐ@߾}f* ̗υG dž0[w'6-##yԥKwrrII`ph?N98888888888\ǀ/ J?Z1R xfo[}6(5FdžByС昘OCQNV nm?6r?yp p p p p p p p p p p p ǀ۟2K$ 1HHHHHHڔIPZZi8 '/Ա       h^g8Pp'@C1@     h~a;wof/I PK$@$@$@$@$@$@"@CUdh34Tz^HHHHHH@M'? [|gbn U: Z3/B~'@CߑA      k!@CյP9$ ҃ѐ =^F@BksHHHHHHNࡇByy9mA !@CUpUHHHHHHC*hHI@M*5 n '(,,lxa yx6 @*4eڇ% HO`„ @AA2@ 222s9,Ա       hhjʳz'@Cd$@$@0˗/ǂ 0B$@$PS^QCK}mgt,fC 5F-)|?!i HPTUU9P@   ]0Qe\ ֣sw@XN{V_ZsnU. Z; u#$  44TO{!@CU{Q$ ? Aee"f$@$*cU`ΊF:<na UPbz?%JՊ x#`FYa rHz@ј2gnA mB^IoM;/O"1 -3 WU_^/K #8,Q7|3|] U&K ;{Dw b&zΟ"6:Gd7 0{lHHH@4Tɣ#!)XHHE ʷ" hmg bș(jʅ )(ڻ!M9uI!|В{Ã,!~^ *%c$@$@ ⟿ x+>FU1P67f- UKPÉ3k>` yj~mɭZᔣe#2I)1ӳml1lH9s.'wb|sM$@$@: @CDb$A* (," h04T* @&Pu 0+нI|OsK*WKé/[1wI)ƏK.a߾}M9uI$!`8k*; $ +ظ-ژLz6L&\8[KXCK'Akxy͝Pj7(:ſ:u%ajhy9v&&e44U]W7ԘW~NSoXHH@4TI" &XHHpɿc̖IHu*s[ X:b\Z] cU%$xP]]{m_oM$222s9ʁ$@$@: PuV./ut\ _roM@;7؛lK8Y\ eQ!!0&kiW*CtbGvCJC<_Lr]H? 2Ңp[(;5N{giIyP׊I=pSGu5I$@$@JBQ @* @P,_ ,[碼 PU1Gu*%LNU1fTY8V :z?GIسg*,':, UHHʱ*SO8# ଣuq!YsǑ ᨫmߋqFn|Ur1qȲ5\+$v}"vٗ@ Ü߾ 3[WpoP/~Xi>5"f=ñ0e8l]x=㸲Q-X%ڰ/qH~K hYF P @ PULSq}o{U*1;uGe4>+ GO|kn!#bpwt$*QcRwA^J-NGgB.uP ~<>N3J_h҃J;% h~qK5crnX/M,g=ӛM U78[USRWn|[Ō'pbyl@|{7 B]}GIƿžOX$| Rãn;!,Kʺ?7'``φM|.d]f;0)XmgbS&b?2 ¸~9krHH#@C|0" UPb    X&. E&"Ĺ4+l5R’~cU`Ίr=#dbhRY؞4p]js3GrG?ƍ:'hFJ$B*! Jl6nC=h.Y쇴~}r}>"S}5T~yBdm:hm.RAb w|G,G"0gm$>_yAtŤ1jMlԝ>g83G=qU;lO% =7WZH׊V%(M]ޚQwj<.K ~x0eڍk    U)xH74TƉHHHHH @ Ƣ"n]r2&AOJMYeݯȩb RP5C Y닰q7cV#a9ǣw# FA$d4T5O  hkn305/4Kޚ"B֔R_Y}a:d$ Z>bS0>t]j Š3QKe ^ߢn\Pi?{0w9emYơKƽҎ Ujf   y P%6#    @'PUɡ#m2o.K4ĚE]Ps şJB8"p\CJJ[-Q.&qX,OVcjdד'=ٌO?TX 6HH@^'Dm٢D޲$5>ٖ܌G~u@#U Cf]?+1nxK$ugcpVgXu /08mr$'wFs&1'z>We@Ǻj%UeK25]=d @k5iZ$?4T%["    p7Te\X? cd奛,T lK\mcF`Jarx UIQ=U([#m-c5/9cǢCصk2< OhD$@$@2 yZvi}p5y\[*/\2LٳFyX.aܮ51JQoT+&+ܚr=ۄF$m~=HHd%@C0.N*|xHHHHj0T-2TU CUDab7T t&j?ե TJÈ%2EY6F4T9g*hHI_1ΚiD,aPቍc1$)f) hQDqFA$@$@$@$@KXos?X>=h˳\2T Ϫr?Z_9GQP"lj UFl/݁ ejeIJ_?#0faΝƀH|#@CoXHH@gE}🛍:\#K7ʕb<YS0vMZzyk1T-18W,18U#{-溋PqɌ:|E9?CV瘭@w^ 2IHd#@Cl0 Uqb-    Hӛ0"bq?/?ljgU Y!o*c,ۧxXRm&- ..;w'|"} H! H۸TU{rxd-UToyZ }>*cvX &l?+l 3qa\tO]я~%Sx$ +{Y{) UvI$@$ =r~h'I:y]dh,24-M<-LI$W#ҵ"YZ&%3c}_p5N4nSuGdz_d./D 3,Yj|lي5wZ?m/klGxDu:~ŪDjȲȲeh9ۡ1a$ѭwIHH@N4Tɩ "P!' h50\ՐB$@$ p| i_O)6 kx*~yd:<3f&-6TkEWI(|r}%5U+b:1%2,sba28LYkޓ.u#?7|3rss 4 {QNC7HHt@T,Ҿ-^cw_}rfv I7T)NKu[JO!#ͶTG'4p2){}\7LY攬"vHg9ULcn! ar0>/߶䡵aeӪ0u%8M]"znjz{x˨ [$@$@$ dQq@P4^M$@$Ђ0˗/ǂ 0& hH&Py܈Y5o.:-o"ǖY϶2TՔl@ptUDٞ;n#ǙXaK`d b CR719s[ V[fX* sKr#GDnݰ}v#ex$@PIH$P}r՞dso-=;_#oGe#f7s0B]JcR:m8bdlݪ~$;^y\*%(ם"3$uf.azsoԟ;R̉p5Vme_m܅?≌nWqral4lAdzId-oDS}eø wS?T߇o;Tz|rwHHH@f4Tɬc#h̆GHHHHH(ǦE`Zzg+H <$ UvͶ-WM1͞S+D dadPeg{rq6OF7\)ܓ@ll,zm۶I h*JIH"P_ש Bg}x\LBSL(\ Z-rfu+bE+%۔ʈ%ikmʃ^`=O?7YˆͽXpQ=]O(|/ؼý,XQ=g1 H@* D`$p hh<HHHHك[DP'}ja#Rּ矌 M)*i *{N;wVrf͝.W!tUC|4+UIE\.v+4TJ.KhB  ڌOu‹3 ge2y*P$Y{;-S-uPY4{_Kj*U;b+lU!;<-}P ٥"xAX#J?rN&}j\krHHd#@Cl0 Uqb-    hw*ǿN7pN-8t싦ؖD[.X]C|e (=zGrБJ `xNXt a]"ݩX }Q`u!vDv$6J$@@w +d1 n;;b OɌ_^ٯ/^a##\\߿zuuWuGHߛq{TWwJu/ߟ_2S,z>6p_sV[>vPz=tínR$HHtI*]ƠI4Tq KPU) UWȮދ޽{5ϓHڞ Um#  1wCU0Tݠ0t    8H@hҧnHHHHڇζ^s=ӧ>vov U&B$@$hju Z2A'@CE     _$uΈ#%غu.e$@ PՐ KHHHg4TIHH@OhғZhr lhMgېӢa$@-Gc˖IHځ" @{$@CU{T}4T *ab5  '˗c?~_Wh>lV?z:f]z5 _LFZ^n@$ߥ^ ݻ4T9PH0R1(x zyD#%,^HyzI)ǠG4RJJY 4JF >Ԣ"oWȨy˨:t(ӟר[K KrS Ļ|#8Q/ (Q/zi@zI,FhKEKbq4B^P$.Jbq x!@C8_n-Q/7 R/r Oы*7(hF4H\D$G#4E"%8Q/ (Q/zi@*ah$ U^ 4@lV)p$LgG7ަ-Nq[K ڔ/pzʑ. YVhOO̜kIӅwP`%a ޷KmK^.`FܐUUc] UM k:L>"n)NTv;,r~YyHb|$BG* Ue|>)E}L/P*/c7ހqz#إ\Nj]=f6{cn(\O^ {&E"_u-}RۖJ/*̭Z|$2 A=\VWו^_ţg<<  ~6Q/L同;NKؿt=:t47(#&]{h/ϡpIrr=%a!ש&Ϧjוs&*n}h>+l\3'%m޸I^@C< 4 UMź$@$@$$X;;f8)1 ֵ*p$*\5#笃1v!~J/S BzIkp'u=^^%#j 9UIT(z Ua'Jkn̫cذax]^e*لid K pxa4ca/]cKw(zPux7B8͍7e3wlۣKl*_iӆEzz">YS]RWH}P䔷 Ά*oi6$AܪOy%/w36jk"=m>:zR?נOd?!%~)noz U}Dipyw{j<}}~'p&)G:7;GrKOz519)7ὤדZ`ܪ[9-w}%>&͔*wO @C>tb$@$@:$PQG'!uLxn(eXK=(8nŦ8α-lXgϲxyA<͹gnE41^RzUB2Y "KYly0-U(5+u&~V_•`:&Jano BLL 6nvE]6.)9Ex>LNFC[yzh&'1y ֤%P1spI8Vd^0TW3P=mwjc0T UGO9Fxbm N_g,`~sʱ%ʣ(>ZoF^+P C@e_aO?"5-U|kOm]/K~[_m܅?a;qKQy>/81e|ɮou&|,4IWqd~{^Fl1+tz0Ddؼ| ! bD<:!qyY6d˯_WjqޒUICl?H_c˃C^?> ~[bg^P].;ćI 4PThj;2 4 U͡sIHH#lic0w;S{ij~q5?K`<EV*Je0E(Yh8%8P!tYs ^v Ω>!= 2: #ﺔ|9Ny.M@Q+f=r^eA/5U<Yp~jj1b6lؠɯہ1bW ~^}$r i(=޳G ቩ}^0T]C]G<:wPPUX'x_!LÝ%?>8S~b,^?0FdYD7wCU񢒵#2Z~gn6qδ~K{r)zY?&`2 l"uz2|_ѵ'4Kg>]<@Yړ^CIPz?" Uv4UiVwalœe_~}sB7VO뼏ĕpt7ъJz{~m)VNۃsC-z8eWb_v,<.3Mvwm/k0:'xx9hb0h2' /jr(lT~*b⻏AQ,KN7)kW!0*7.r w3xm%r$Z9_ /SE q4nlè [%"1]dK)Vr k&rt]d!2CNUNƒPV;Ľދ;BF`U%,+<+- 8p_TwGS3s^UW $eࣜ8}-: Ƅ辪7G+*zPk/ڄ* Eaxfk ޚNoňDtI,LҳO?An HZS~g [Z'" U>BćDd=&橪SLL_|6'2_'ОAѢ1 "wRsDv!ΞRQEEED9X_KoJ?Cdz5Cz搶0=0zxb_e͝[ LY1aK"#r}j˱TJcc\z,E@IDATf{!#1) xt0ڳU CK˨h]ukV'MÑ=zg.l0/Fe}AZ}ߋ툭Ac8&\)sY#9s.WvnI/^(YbHV']jQ."wn.;U٭wrGCJ*nP#* : ,_ ,u]$$ UzRKy?g o$stoRTh2Ք#1njq8p W <]|o={LŶ UkPIk~,Sw{=?T %!i,.YErNA[!"QQeW!ڱ_ 6̎EHY\ֈ։@nav0T5yFc̆.xPdru%PՂtc[NCՋwضzع0yj/iTJzzZ|[7+O;0x=uOsH`}x~e/2T%]L[ʫ=5 V]|Kɼrn{[g%Cw0.NQWxQҚnھsɿ!=s`(6Ǔ̹$%?;[Ekbɿg^ߓ}Og'*7;(I~P% H&cu  #*.*-WaXoNa@d,JEƏ]%8ݹPNDµ91A䍾a~U/DKAirA0.`pz93'^Eɳ3$bx t"##1j(OkzyU1bN7H|F,),1BՓ/㇞x4|8ԧC+C=XP[6s懣_aB%c?@}k5sƄnC~?gcx5[_WlOzEZ~7*FAr9tG] c/݇?yfGړ^ܖ$S.*kϩrj{XXw/$ dzq(猠MޛSfI/k?D&lIsX1e7 bhi 4T\ 1Xqyyn;% u:w1"&j/{L7J{v}kPuA6Bp}8k1z|/PUbaX'W;T˿:l8S=[6%R1%C0Tgķԧ7s5xj+fέ~=hpt8$LŌ-0=_WA W+#Sڸ Y(JŤ/4% p4Hss?_1c5 ;M Kb0=#l 5cUXN@X1C WsX=)Oh=Zi:l1+v3% Jm%,#C]%[Jij둀PlHH`o.BtS^o,>sN!c#N_@= UDdܯ13Hsh70JA'J1䶅-q.}S9־DcBwDQqKdT߿?Ǝk:F Uk1ƥ 48p!DogDy<*`J^0P,iOl.ƇO,d5l%-qZO$+,o?j1뤼5GK=Cl;$ Q7\%7M==LUA-hc\$7&VЎCeՓ^V:fdb%<|_|eA'f:=luvi-M۠jOzY~֛Uo(-2/./oOzuviwe˜/{QWs߿4k!떞2ly",#c z|ǷKŁ{\:Z&hI BwVI$@$k? gK%MScY=) E!Z>x6ir"nAd?xJT$4ΕP}LC逘_ 82"%͈yXN8JWve$@e߱K{ob CU0T7*-!4Z$旰mIXm_Ikޓ'(Ph8+瑚n 'Vx̱QQQ()qfw*AEl?HwGbt1?[〯ZZ&0ea϶2tZx|;II˜%ז̧9cYDp 5"?#t. ¸W~p9M:z|i~WOFݙow0 (ic>޺'J/-Y=*Md7+)+bF+Ph_dn2=d0E;˟R3m'edйSkvsZ^K~[^1?28$LVk[SeTkΫ@s P\<HHo222|r,X[mِ<4+lK,d\xge 0TeՇt(z9y&wbmZcYsA腊b,;/MTN$ge mv|@.^Fd YuZPш5kָGѫ8Vaj ,IaRDIJ!7cTwlToۆvB_*%>~W^-gʶk= 6lx5AW=Z埻PI5ޞK3I/+4k[W;(:ȼ,>|_U; MTźb!|M~W*x ir'}UU}cVIU&Q M {sbb^XdRb}_l0秗x4ܪяVJԋz鋀^"h9+Z/}ECb$`'@C 4p6 WW^m.A^M敩$[1akԭ%D]%@n)zPE]>XШhF4H\D$G#4E"%8Q/ (P%8  "  L8=Q1Q/ld JYƤWdd-sm;֬e\U!-ѵ]/jtF۾yz#^{y~!rP_$@!) ";èc0H؀oֆVJVm?rgf埧5j{hEHK/j$Fv>5߿'p p p p p p p p p p p p p Kj| Uъ @!M̂\~zrHY=8Ճs;Nm UlCGBß}6wl$&&d[Ƀ<88888888888h` @[Ne )޽{g?~߷i8 @&@CU֟'     iaڴix7 ?4T?c     @^#w1(          A3{I$@$@$@$@$@ٳ'{1>VH$@$@$@$@$@$@$@$@$@$h \m3     t%%         %@CUJˎ ӧOʕ+8%         (4T @1c dffHHHHHHHHHH@hҽ nݺ'+C . PK4 ]b̙αG$@$@$@$@$@$@$@$@$@$@!@Cnb$@$@$@$@$@$h l};          $  658~_8[v W a0dP_t񡵚r\CPPuuu|ic+<@$@$SO_'  Lس!nP/Dx_q8UrO7a]sGLgQ($ }#Ġ~4jHHHHHHH@hңjHHH'wDĸy f!1/n%K .`a,a. 4@hh(ykM>' ]6.80VM)g`޺m*crZ vqV7hI8\-a U: F`:A*' UahFVQ%fGx:r   `X|g @(/ބG Ӡܽ'fti50#ٚ1&!edO\C)M9I\G]gY<Ƽ`LaHD!8)VTnytm      #&  h G` Y lT%[#*1B2e ꈎ+vFwT$eJX@$@$Mছn/KXJ$@$jN"e_X"mj=V'Z%.݉M ٨D֪x KQvx{{iF Cr6fLLسr&̳Ɠ*+;G$@$@$@$@$@$h xA DU(ޕ_™7w inl1>;Y`|`6~%L()+]7#I%( W|TQ/u(N8w.#"-ש(91%FtFX FD+ 4x>C?ZKxGmPk_#Xĺ-;w}zpPΈ v o뉻& 6Ri:1JkEa|'?  ^h7Jĸ0 o1.KiX4ڑgn|DV7m@a!\z Gq eY'jU*9yIrdLrւkg0Si=Ǫ3lLƒ$@$@>bٲe>f }=wx: #^hJP{f#jd]Kښ65 Ls5">eIq#+w!Y,T3.Ǯq        0E$@$@z"Py [?yJUoY4YM\\]i4X3\p7d-3X*dZ3s)cJLƥjΥ;+ެgY9vm~RriޗDsf\4VY! ),üHͫڼ=MĐivQg.X쁕ќֳcw.uO:UwhMc?AzGY)n ?[)wItO>H{xR]eX1c'O3gY?k CUCM/FcsoȜdܔ@N瑂φFgqHHHHHHH@ # X ԙwL9IiY95KU)YzU9@]TL.F-~uCZ+Ah+ѺBh[0]bt)[CmckO6l3X &JM0i?w&d̝ydsϟ`sJʫR-^)5nwjez`+P,pR3TcPfkZuݒ:=RӤ'u}iV]]YL`j#;Hk*`TİZ(-2Hj%I {yEZEu*_h? 7AИr jVwÇ*@Na|;`i35F]<{t󣮏SLq@xXVC:1.8 @ ߯B=|f὜eS+z[`bo/Z('ջ=OT1    /Tb$ @D]+,UjO@ -~8dk͎_AnJ` ZEM0TiMz@n^m%&'{xJqBMF+GvtR jӂ ~Uyj%vc= K~Es_=M2^GEßjzVw YE Za(cU(["g8,Wf@GkUj!nwȲ~ṫP$[ J8 ?cjk׮_aJ!O^NqU/I{~+0e=|t٦hEխ*z_ 18J@@@$@OE_@ ZͩZkj45mMj[pڪNr-ԯ7#[:X7 T뷤Z# `VZRըM}# V +h?X)嬱mHB*Zk5BVDOWV_ne@ Gj",l@?  I3#+Wow4WvUΟsZcU+W7 +9@ 3F[n2HM߼=+]ZU"?-,̲d[jͭZ}uiUyky    P颳 5JT S-Xk2Uf`&P9a2 Pl`@O(PlWզ}k>ŵ %c(Qzb\֠UjyZڪ٦22R-«Eʛe25󷹍@V=V+%2PVͶe3V_ոl=וN^!=ϫ8 y睧&r e@|ߟI{x/xľY۾[KmecK!V :=c}I/}e؈W>k @?F_%){x{x/x*-1:с6{뷽MZV0PPml    U+z `֪iFq(TnnPPJ"|i0Mա+Pe,ymW$/t߶Q^YM}pk_?1Rje&6zVGVw`=P\SY0:6P@~ӤugW #Y}.>l F}t=T)$ J{fc( e[$[qQwLUZ{ʊ*#   @j Jw +#9ZZ-Xe޼ d@"^Iu=Zs]%`~=ioJ̭D+PiZqX-+Gw㻴`U*қ'ǜ*Kujz|!:oq>^\*[+ 2dRn`@B+L?`0׏4_MH@+m ֠ *UWz6O$Z+* ovQ!"OmGV]Nz][`8+P wմH@{o})>{Mq&ﶊR_0jM{I?B|B\}Dxͨ$xH#   CU>4d@mq;Qc(P 9wـV`jJ 7ʍUmʜ]},ԬNV]l'|sj[4W61`Pb*jv>甩"U"֪?f -txvʊfIhats U,CX9f)@ =x^e |ߟ{x/UZ[tzzUZoCJ<C d;,~QkLC@@@>YWthP'՘L$qzs=nL4>RUohk9qdsgxUW~5?FIΕrŲJ}2Uoj3#׵}=ahzx_=rE^nt[( TZJ6nfb3{D=^^=~h-]v{骚    R| "ΪY;eP=N#r Q)) uyA>5#G^R粋dsT42<'f,UJS&Njd&V@ 9ٰa_h|0%H@{WF    P5=E@Ov\N:%=U'[z^A@_"{<   uD@@@T P3C@@@  PAP@@@@@ PD@@@L,F~Xoe#     @ Jщ[   @& ѣf& "      d@OOwyo[֯_@@@@@ ETD @@@ }]3flڴI 3#    G@@!Pū@@@@@ UTL@@@ Ξ=+cǎ͛7˺u2X#    G@@n7n<#vZD@@@@@1U#FO   @WWb|F@@@@@`T ;9 "  ?/wܧy     6jB@@%@* @@@@@[@p   @;#\p?w}Q9     p .iA@@)p?~lٲEo, @@@@@Z@P S?   @O &??˽gy      0TJz@@@-)'NG}T~_GA@@@@@-@*٢ԇ   S /~RPP\     @T%Kz@@@,@jt\     dUI:@@@?ʤId֭f͚+ @@@@@$@*IT   0p袋DWĕ      R@ @@@`rc=&__!5      0@U2@@@ o+ӧO/| >9]E@@@@H7U6@@#GdƌP|8     .e&   c&?(<,[#     ~ P   @JId     @Jd   ~ȇ>!)//\?#    \@'#   2sLٹs}: 1     T@O'n#  $paʒGr      Tl. @瞕>y|<5~:}g.,GCSW-Ǥ2wз;N4"ȼO0=!k"'7dM٥FcλyOkr$^D18IhllJ}Y?:@@@@@ ?)(W.wџdK"ur4U"I5޻xKk}=$9&2y0(OXHA|Ӓq9SAɖν2w{)<(:yѮG䱯|F<\uʹ)4O@W X~>9_"    OFd'G朗z{w8YL^(M-sˑk7\(oVuXYy~5iyGdy2}R$Qu+/H^/˧nB~ȕqBN>*x&)% T\ q87f\lY. 5{v1E#1M??Mnt>vP^K[yUv7N|R&ROI N.'\'j3i+7")"M$6~?wc\$k.L,PuzwdGF:sX*[ M=ߕae\Nu. UiQsCk g?;J @@@@@dh|  #…NS+ !@IDAThU5ZY.[4ΉV\׮:&:%Z~Nw+\Og}ymvz(J2 [Vٞh*5BGVUo3ǯUprTw6z zF=yyrטϲl}%5HV`hU560r\mgg;p?󊴚`k8Z`CФ5WCr~٪5Cu4hMEmuA]_|MJv<}K@T+@b jJ [=Ex R'+++Y#     08Uj@ x:(N Tqs;w?C%m[*`OڦK lܦNL>F?v*a.,ӂmZj/mʻmLGzB$A6s|-\"VNY \+Es*k@v#ߝZLӷwrkay=Ƽ^$'ʯ^=z># ߿?PUUUR@@@@@ J*'!  )/u>83c{\[ ~{}@ +J*ԣu5+.Z\Uh@9@XEz T9 RV}!GCY~?jr&e jܴ{ժՔ;)*j?5y=rLףZ|hN"x=ek5KMq!     @T%@G *NShwHs^kXPQRUTp*Jk IGs* R.Jkz2-_EKj\*[+oձTuZ)ȱcs̰YVo+g)Tg[Vol'Ƥ#y~QZUhZk}ٟOUwGkSԀ>uV۷S+yk L!y=: BFAb}DJךfW y7Ua6UUv},|V9ZBvJ]KW9/ n{'89DBR@@@@@  #@O[V` {䔆SH':ѣJu Xt[iFzG?թg*˘R358l5U4C7]Z]y+$SVR t5j9Uhu X_)jlnx>6&׵5% iuZ/թ ֑V'jUőc.Z(mVR۬՚m &pOVkT$jԪi$~=omXJ"˃y,ݥՖ项:k08@At[6G?D+\     @.Q7@@蕎mգ:5zL5#GTJdsTԉ˔[T;VHϓJqT5_$O~vz SLd܌ԳI2Oå{ F=lܸsDy{dʕm6D@@@@@`T ," BG^yNv8)cٳgevN0m$V镽?*lF:ȓedʃ[;-{Te;{BR5%JU/,>yu@Rw2פq;4my).VOhݷoM!    tQƃ :乇*` l<ȰWwN>Z&rYmW&wϝlk )pk&_f99s444x     p .iA@~ ֶr)yG佗~@>*}Z:,2jL?q@/+V-[Fq      0Z@@@b tvvʔ)S$G;&\rq      a!@@@/e˖ɳ>/Zh ^E9     rb@@@'_B.\hyes      0S@@@e]&o\tEϣG*@@@@@C@p(   @\uIqqs=裏-I@@@@@R@PR7   @A5kV     C!@j(T@@@ aիW˶m @@@@@HdjR   Ξ=+cƌ\     @2T%C:@@>%]D@./|@@@@W|5]t@q%  qV   $C@U2@ iweÆ rIE    p ?^6n(sp7M{    P5H@.GH S%@@@@`Bjf@@@*@jr\ 0$sΐK    Hh6&   T K@/`Tq1Ԙoff0z@@_xH7gIM   H uD) ǘ4@`MH  @Z Jd0)&  $(@*A0# 7֗3[@Uf?G@"\&ZGu3o= CeE3efEyvA3ۯZZ 4{^Kp-ȕO,Y"-Cuq&@j`n\@%   +@*u熞!) njv=L fp xLrE\%]OUP@"T T]&{TB+2;e%Cԟ: *P'W}tн^|Zj~Vf A˳=")@ʤIFI@@VUMc } pñ/!#0pUJ#2Y 2'2UPjPe_Hd݋AY5+`#ǵpnG}&P4rֳ͓I"!-rhR!SMR@PRw p#_@@.@3H@4cM(I)U)5t&!,Qisˑ dT,bUgZHd T-Q*2JFOV[-X~Y5{*U\8z5L+T$]># 6Cf%eZ,&!0,F2T: @@F@UL%AC1"5TЫ0U*K%y6T x~t[f[ȉl}HeBvr Ra c G&PT z[vɢW_ s )@*& '7MH   Q~Gp p-s'@*yԄ@"fR{dLZkU^9zuaY1꿋.L>2[ƋD:ujJӱ7^zL&q3Ltq6y6= zۋcK.?Q-6_7=ZU9Z.~fMbB}"ý?ouP](\zOʾ^_{sur\w[6千7Θ r>(L G&yeo;V?믗Ys޿@ՙG]Vo\ڴ89^qV2/moy*<@ P5ƴܹg   c 6pLd )(@*'.e#d["/.l[x{#;(tUv~t~e6`TbyKcղ1V ܱܽ*Ѡ>lrʅw3ܿNwLzH ȫ]*ϵsZ.箷{)k:)ٸJܹpnW^,K1 lq0sY}N(kU@*%jCk_&)~>w"%5y > zcZ\od3r@@HU1Hn8T2 PB2B2"?S]=E\'G;(t2'WXy>*Q-o˧V*Vm,:Mo^VY:PjPS]8ӴKn+ȎN}=q'PUU y*dl$dvrrG\b=].57 T~NLʺW^#TܷC:Z"HViMrh213]RsX:q@ I]8   7U~17|ވ Q~`GXY8g =B7&#m%o\m=wdTC~}YHdݦH%⡭2KO4U-k7dv5rsGߐnɕGm /erO'"wW/YMrK{IhOpBsPG- ~Fhtd[#v N/{H<]~P֮Iю #gɆ[$gޕ2g䛍;[XފfjrB-[..k?[}9f#YV6, TWa90UZjsB+Sv5*+gN-oS]c&k#2fF|F P5tԌ7x    P @ p1 QΐZGFB&q <`;(-3C-}[d%(q'd֊HKԖj[[6ʆS ÏΨ66ۘR]5[/-jΦ݇eY})OB3"["EZȍ+lq;ԟUTQr7U7qt怬z_lݥV%sW(TYxՂ /"V03ET`iZʊ[lj ۗWXq>_j+Cj+?"<j36ݓUjH~MyU^S,@jt\@蓈   @J Js @ p1᳦%BUQ)Y8_5W,RA aG=G_a6Re^h?"6lA D)Wc%^S/em8* dEV2:!t+9Mꝲ{|[Ϭ//;N'T T3Uٜ{+emwGtlQAD=PղPnTǶ#,53-sննZ lɯa iώz/ wXRf]b%Re@`*u-(  Td73p 6jB!8UK\>rdÂy8BMj>=\Ӫ,򨷥Bm haNdDʅg+.i]zwj.ip[a8t:9'DUdges(n =5VƺRO[ȇUJT0Q=o{?y"'Vʲٴ売>TYj ^]59)oQu.lLxJ#O*^^*`` < 8 P5hB*@ 7bp@@*_LD2G3׌tT 9-"pL{;ܣ˱"T:ٷ{\.z~h46ޔo˞׌9 [SR~{@ ٷy䜯j>Zc*qprFahZm:Z_u"+;uU*UB[FVԲJV@R{f p##  T VpLd0)&@*&d@!*PXjmY+gPQgB Uz@xղWUY r~U͞j; Wl>xԶmڦڦ+P'[Q_>}t߷Tv'9i =ht\xqJdKl]:#T_si{GxhZiS"7x$#~Z){dLa43cՄ 8lTЯϑMmic2cؽGȢ+$zxkg\?WC6F&]brE\-윯pyKdk,]r%ν7eveYcXXWGMR83PhQ' 0DjPe  [@#i' ǴRBRh2JF /d-3eX<پ^ՌZqU ^)3'J~q8Ro T m6pcH_Q*)T˗Q[*N SSEr3;#+cǾBGgΜщ*wW֒Ms2eLq^V}c-מ_+Ʈ}}5@`pF<!  /@*"% njn;29On,u Fwj,45Yʾk&߉x\?g>.?ʾ=?Kl^iϲp6}@g@':P5u7VRp~:sPumY1t^l0[ TuUfZ03<=Ɋup dj#pDo],"ȴ9+}k2/+:\̣qn䫲\s%veY@7mFO* J٠/$HȤeFqudM*3P\IdËdy˭?Y${iܧ7\^m S Fui3g}tD۾ N-Thz?\A#^.C:sPV\}ަòlGJ6 U"- kHT͓G` ۩WPm19ѱBȵZJ*e:xk3kmZRsHL"^rG͑]GH%;fjA+k)T$k_ѯ%T\-klw>Y3?F|YJ1E !з76   Ryv pIg&@jبi@ڂmal"QOP۶mT8\*~PnzN͓d_>XZ,.=s"* {'tXRX(kPS՝{V_ْ5#eV+N傱U`@]^6 efI4ם>HӁ7?{~ʫfʌKjҥJ`LzDeĺOJcC}JяVGOj9߾ 03^@@|.@H@tc(I%U4@yU#? }s@@@ 3Te<3J@7pTQ e@@`T !.Ug72%  \@'#& tQƓJRi6   0F~A p#}疑!  @fʌyf o蛩 wɓ_|:}7#4iR k̗d2_̗[5_~|?y?%7 >#   Py @ p_SO:^x_mx;vP {̗d2_̗[5__y1OF/aH@@*FVڛn)_~_^wu'?P s)c|4Y//p~/̓Ko|F@@)@ʟF@/U/~ |K_򗀿zח@_/ok-    P9 pqDn F?td#z5 7|%L60_#ʟpWd#zOq%L6pcDi@@AR @2L͡CoT%SsCodj}]'+C_5l@U25. q2[F25 @@~UoN q'OqC?'%NTIS@:'%N88)xJI%+N bRpRt@U<8]FN!  > PI @& p_ }*?f%|K__E_/75_@@p r@`DcΕrdڨ%NjxCKryVe5A>x'e\;m|RF Tu];_3 ]%ً>#-%cGwLHQ K7tVH xeYrLK/t$l|Y3Hz~_Z"3&xUs}mE;cr|,ݾBsUY^!lx;<]xGUE^xI[';>"\\YO+;~I[||K7$~ N]F@@$@j@l\ 0Tiy)3#u2sᆾ9rEf!qY}|Z?7 in3oЬG˷ˑ fD\𜭫 ʪnU8SV,<[f_u_gD)+yL5_ۙ/9tJȿ~zN-*WhNۡsUUsJ@UzuQ=WGvQ⁛C߿޸CEr$J{J߅FG@@@ PS@ c߶>OFԫ@յRyb<yI*()]-Ru,}J$P,mo/SU[%X-߼uQjˢ4XB'~ ~: +zŠ,@xd>u\X8G>Vwz# =J&^<1-~ .{eY4iyr,=UQy>[F.aC>L:yE6UQ7/ i|.?MViݴS6W&ꖃ[W Mreizz兕%G.(< -.{g+7,)_7b TqE׭Q_zyR|T>=kh{'A6KF@@-@jЄT Lؽ_0L K@U2_+ɮdGHFtY+ds+GXY3[۲8/m.C_R|E@'K:dlҤ3*vbyK-Q{V;.__v엵7VQ^~%, |.= rqܱ5YV.*4ƴ)^to/do$z`By^OkiR^U+3m^$$!{`fg?{pWg*O@ qu:Lp-&% 엵7/߸GhQ-/7+?. ᳪ[t\nJd/,Vjʳ:k2VS&kU 9\zoq]Uz`5ocmIUxkX~-8Sh}ѿU[yCh8p Ks~Gk@@@  B8@`Jsѝ&Cմafzt* yݺ,L^{.*ߝft,UZ/eg]g,/F% i/h`Ort5a~nr~,|dcZſ,YI|93} ~2 Od g~wJ߅B:$+ R7};Zy_{_HXjuhneQٶ}aK"ʌ͉ƽ2aյ"-HF7t2'mT=bmwJzOWʌ@IDAToi2OVX5ڭeylo\pQM >"OS"= W[_^Qv.ږҖRH#^I|9Zԧm4 FWMt66xX*U2[}vLUT U +}5F~2Tב"  %/@@U1DK>+k {eI{+6/^^F@հ:~ت_?/Gv]vH_Kҁ :7|zo&;/Y-I78:=}>O~ hPç?ϛTT9gC-V֛u_ׇ<${q,Pz[J3V0cuś3;_E;CZ_L@Ujtuu.7pu鏗Gt "gT[wk5=6KFkeml~ϛ5XUfWi2/M3==j) s)ߚީ_3Kk>ճExyo5{wg?[zA1tF+,a8}:WO(Z3w[uoߋU7i&N<6z =MŔ47?`E@@J\`@iN85i\l4xiuzJ]ITvjM-GܑzM ky;rgչU8 l?֖?A_4A @ [~W_3ö՚%8Kȍ/=we T@Jcg> _ˇ> ˵Qm3;/_>/J ՛b&G|f"OgJi*}RZP_~_]_dQ_4xOciTX\ẼSڲ&8w iۊb evƽ=KߏuM˵Tc)9Qhʴ@@9fΖ@ PcOON) }ԁ-ZuF,HO&?o<)]UfH x*587lO7gX|)V/?Q=W)B:{ W],`}. 5ZS+o~2?uڦt8nFxi[5+6*=b7ݠ_QfjO)eww,ǸUVRM\(gw|N }q'&k׋{WG-)צJ?!cGhw {+qMfӊfU}*wDzF,&47`C@@. .q @愣;iU[_4qކ7馽 IԤ,.Ssf.P%OK7ߣ{/+N/3R:V]'}u`bEƫㅧ>匟}A_g4}jLj1w[I==w{~쀪*Pud<_t"x6Y r>y_F/Y*Ut7Ԯv2R9äHu\u{6ݾد5h Yt"᜞+TOp9Ҝ~#y  U=(:&kȊB/PJ_[Ҝ˯Е,GnZr+yE1E+2x]ow_Yx}+גa&{.wDXJU韧Q hthD*Jֿ/a3m|-,1d~_>!  $@@ե4(&`MT&].귥PUqƗ/,x׸1^Wq Wk**lkg~cv:   pT] #L91?zs+3N<`s+cfxZ/xM+Wx8^iʸqiIe   E ꢓsA@pO1_xc2^O ZxSxZxSxZD@x:wZx:C@@ _#Z\RL8p3_\E@UqfKZU\Z~k*Ƌ/Z  d P-g@Y`qV'}q&'M6'P584٬x*/xMlVO`fg&M6'P58_&ߘU~.  \ULH )tj|]Lϼt^ԜOԜ7+0^ө9u1^3o{x%SZx%SZ~6uϏ%TX*8_k8@@@ LJ!5䯽Z~hkxƋ*.j-?_Wq WkbKZWq5^@@ *[ *pՆpq@@@@`,˚Z@@@b Pu1 y,X    PW2,f>@@@,@ 0=/T5@@@@`/z.&NG@@T]lq        +@@U C@@@@@@@@-@@z         PT0@@@@@@@@T]lq        +@@U C@@@@@@@@-@@z         PT0@ sgO蕟I7ܾAL t⻯HAV.Ig|m]vbPHKNc^       0TM#&U! @ X 5٬uskM{iV2ܩ~J%rZWDXU ;@U'|2u{wV@@@@@`1  p1LVc{X}"jK՚)T;sDڪT-m:z]e~_Ws콕.=yYy|D@G>lF@@@@ &  L @T롸|Q ~NU|G:tzɏBUq<X^oO0A@+o5ԝ1p~@`RAan=vP4I%ujk|}ZLQ5zpsD?xR\vǔl8˨z;;ڈ4Ocݷ^orUJڕhiI5Pu-~A*ktv*Wl* \ۖ41{@fKْ    U7&@`\!页i_:(ZO<POߟ0g +-_Nr"6Xc- :s.v MG[0rO_sNc6ov꡹h_jgHUڸLjh_g]Bܽ:Ub 9C=TDsoM E۳U.{[?zq}^5t+]O8Z_֝V+RѻM!zw*m'SR6g m^&P]ww-1;|"^e]tO@fYY.    @! Xl @ 2_jkZ3ZRo2)Y-Ish(arvY bQ"Vk},iELOp}"Me955Vsr-bYuu+c-39b5DQ+1Ӥqw$1:RjᵯȨej(s'RےQk_mn6ULZr'jl4Zڭ!u1PM}'"qˏq0LZ1;Gүw&|B@'p7Z6@@@@@ UF[@& 0wi{*ɓɶTW?z-[4?ڹN%Ghe]i}ǐH/]9]s4 U M&ӕ(T6{L=}[tn]Mª5٠Q֘k<]#p5;uѪDx*p=xd9r|xۼ­y^ogU0dS#yKwĖs V?l֚M U1@@@@B F6 LZ`wɎkvkof$7١jZ`3rh Pciw4*ޥG6熅P1%Vm]fYw,I-HOr¦zrԶ T2 l@dT%tbԻtv/mU['>Y@Yi:@@@@X"< ` gvw"z|Um .U.^:[:;R>}V8_N=9J~f`s~sP.8޼λ37?ԁ-Z hyޡQ<- ֦2y #@@sU@@@@ QBڄ 0Vym^6'PvdYcgWϪ f;2;ߴ7uo^wTn2Kn .xN^XMs~]jNxPYj񑌥{R<P5_X;#g޼λsciyd#ٙ==jkVyJ@IfR@@@@(2 @豪CZXD2aJcѺٟ"NYIZ묐S, hʛVm499˛ g_+nVۦPcv'1l:nʦk X UaXԑqq>xcF"%ӆPe\n$e{דmM4v=PM9F6"6?:?i %onco۞C ܔ @`>O{g$ʍ=+ 8hj\BPN^4fu9}?3&y "p7Z6@@@@@ UEGs@GЪqfkBLR%g!dNPfzJg[ "Ҁ*-qxۼsmc!S6(k&$3K Y0b/t|}|H/8%L][:+>{iZ7߯z5r>d,-gv玩_v Uvn~>֯5jq! jFX@@@@ (F#vEv&HJ콣NkbOkγ?:۰MFH&lƭhwM-HMsuۮ@}PTO|ET6Bc6>n]YyB0UĔoQρAϑ">exM/^^`Ng} kh3T;?`?_=f5%|G򝜽/0sk@YvY># P5\@@@@  0٧Ud3YJ5L ]6xAIͱڐ?4bjU wQ5[UyD+.0jj,@ VwK?ʅCcqtه>mV7Iwjݒ`ձĜsTt̊؅ QEYHfm{K$7YOzYOGϩϪgM]vZb _aX/8+@@@@&)@@$( vg.?O[?m4_I}Nk-]GL^r ״ {OH־ҺL@f>jP6-. uYZ1vGa-XkaժZh>h9d.^e_r[qPu4PuCZ=kd\Jm߬o5(p~9IA=    LMq #0pnE+c-W1;^v@ \,=4᳇MvU9?ݷAM 7UwCZ=oFG5,yts*+>_L0YzR&lΨY^Z3Ԧ;fYnI^@@@@USQ@z{^{M4?7߷JZ3 E@@ -Ԉj|;TL*ĵsA\}݀H,W],Wݭޱ\pHid@^qea 6uI嗛pn߰\{Wb޵j,+S[wgua=WMrWA- '{.M4zYO͋UY%Ui@@@K!5tSn4UlUeõmjMV)@@@@` TMS@@@@P5\~Yn.V־Z>D+W^m S` ,7cE筇W*?$̠[o6Vp Pz0O.W%+盝:pJEJ2[[/هn T jA-4Ejj֚@eR&FL@QҋI1}r2u\{ pLZPRulLq۪}NDV,TIsٓ7Cթ[^媫zgj٥C&ݼ*/`=b^C!U*.-KEҿf>-ے%lrߘ} \)m*l_g?(zNhUSk:me[XE@@@.Ds@@@@$PupVmOSUfg}%C^@UK3ڐ\eҫ]&j)3*L,jZVЭhn[lY]NU;ѳ*n{nF{uқRLѺv=sImQΫPS1ܩoF{Sjon=TZ"   \U    P- d' Uu͙ܡo+rmZ1!JY'ޤ&Z]n"L je4}DsoM]!_@ .VSdX9m3gN>|OG|)yte}"}q}qPu-Xإ=^C@++     Pu   /0Ƥ 2K&P-WZO%% 2KrB5;$Y^۠^YXU +jWާzv?Zt@0][UcRs@@@@`rTM΋    PPUۖkGr'\X@U=^p_oYJpyj)A^TV?ywU҉;z< j]fݵJ>ՋLa2x    0unǙ    P"S rtbMĀNcUK@@թZvnDɣZ5xJ[M->ySᘒrO*WV+3bՈFUpir}kzBeT `2LŠԩ";+U**G@@@.S@@@@4P.2xy)czHm}ǐ[ n egu | b]U;PQm!g?`ew2zMVZ U*cZF}eC@U    "@@Յq.   s'{SW7v8sgtސv͡PzNђIfu | 2 yAZB&ӷ'VhF}6oܑ >֛j+XWDڼzI`#ݪSy}n[]׭׭~tRշ돖ѠɌ73     Pu   /0*i@Ouvt)UQ2GK B``fC&*Rp;J[Wu'3Z= 6伺P Ul]v$[Mk\0]ȭ Uq@@@@`TM    PS 5܁jݶk&M('meX[*8@Y^VXV񃛼`Q=t۾>w}V^䓩5=o2e yZ B;$f_ eWCՋPK@@[\@ocmIcC@U^+v"   L^ɛq   a kh|[ŋ.6o;cW\ d    U=@@@Fx!m߾=A":yީݡ(V2&-S2 =Wm~rZѺ\ U0>eZxZ8or?S}o.[%+ޯ ӓ@@@KHKh*   HCCCZhQNl;;չsTVV}\R_~{gu+&Aش@K5zp#h&ЗAߝm 3i^=|RMH/>%26bF{OOny:ޮ=M@yQ8ڸ3O-j?.8;Nܢ; ׶5U.,Ў8U/GsimHG?MSAvCڶriON=0wꍔWUo3r <բtڥHLG+D    PT<@@@~_~Y?n/֯_?n"p)}.qHXBG+VOˣ5M(e2T*sF*OvvT-}ڳa=`U`Y=`eBT^(Ջs'Oe U5# eשNԨNUקVצ[Y2   @! PUB@@@@`VX_,+Zx;W&0yR?}[/j Uz.[a[9_'[7ߥ֨lSߍKOSž4+~SZba: %]*?zY4Suպ.1mZD>?WNtXvW{u ]ZbT͇ы?1KW]?&-1ɏΝ=gEnj|VQݾnEuqLQ]lK?w^{Í}rkKx҉6'\+~{mw߼_%K.R V+_^sf_x) kob%_]L{u׍z '6]3 |,WA'e2׬M%ItV? OTk=Z*7GVC+6b+ X_ d[MhՔ|O޵tUyDz^Z]Kd[!g ;fG@@@ CU1Gv(a< kԍyHޓ#eOG.K#}VJ'שPШyHAV3:?Qoߛz[i%yn0}V?՛oW?qtWUߛjɊkBh(  ꫯjٲey3Tuwwꫯ.ffM`Y:tܬ_{߼XOev3ToT1?c`Vj+Rgn5wcjڞM3ˇ+kUyru}gv=QKٯo١'L%ݚLTf$DCsY.*[I-틧 +Z3w8%X8cr S[Rܢw'G`<"1%V>[ϼEҿf>9bjK>5%M@ wy@l55rpsatL+g3cRЧ6>-zW\@N}Ii6IlÃ[S٩ZXM"  ̞@v@@:cVeeU1@b'o'xj6OϞa7f=Vd_|KvĶgZe_SukjO}::TndersD.MgJYQ>(Zd_Ǫ 8G&]de8[u23:t48̾R6X[YC&Rɶqʇ33}4yu&Ӑ1s]ib`RCVɎV,b4X}SM53\sboL>s'V@@@fM~ @ KOkIohzr'#Τj~KnoO Hlۧ:;}oYzDpʪ:=? IpԪaȲ-5CʊHϼW@@KCСCޱbcC_ s2<9F5ʆ78ȿvw"N T|USۓbNN:522bL{%bmI{H҃ ȑ.7I_y.q[V}}|ed(iջAh4l~=T{?1d5Ϝ1P>cy=Zk칂@PRCw67 .w3<@IDAT~СUV"#rmُ   PT(((Oޞ#CV?i Ϋ;MO_f}V39_<MXF܊3H8#Vc[.b5f;VsuSz}'22Rg2Y'C5&lm˯#T|JzjMǾf];  @! uWx@pk2>??̲s~Q {qN.n@U0V0p&}Un0;m3ڭUU! qzr|M56~rNQ9\czV[V<}ϗVs۝c&+Xxkզ#rNw$ڬXh˾?jFs3wX]-VpOeȧ~7s)   0/on,@K^`~_Ԏ/?>]Zr:OoK7Q@}iW7h9pN|?޷^V3z'wiؤ%sgO_ѫ/_7~T[!SCzz43'+e.˯Zׅ?Mx%7'~pSuZ|JZ*[C)U%+4;oF[ɟ]kͲ2c]}+~\߼"U`~U+muZHgN(q234zYO͋UY.R Vgnt}Z},r&zWZnVm\kۥ+1V(ϘM|oRM+L=xi.>d6d|zK7aִ 3y L]{SO={ѓO>988s.vR}O`\t?ރͱF%>k*[+s%*Xw܃ʽsO5=ǻqZ0KUKU}ιVGw^ey^T9ʬe;;unsBሮ_ =qyfzrېm֪~#ܼ{l`3Wo7GjzFk^*^9w3{SbƸ"=9{w>֨z0n K -c܃ڥɖlG5pn@ܻ>еm}3۽   $0\\(<=kZM#VmĈo<%Z1Y~*|Ya73OP)P4U5z}r_kJzCUqCKKfF)dr[ Unz OJ䛇Ozgiъrfw)w&5bŝ,N&ьbcUj"Nzk2N}Vu]8V{{n5d-QY}"8{\αw *{a'D[mzMd`*   pa/q=d dg7J3"few,>:d2)egJsO5m+fA;А5cOdw;2u&{kq̗,Q^6\Ug%Lfl'EHŠqLOccטm*{ -Wn%K*Kt&(^"137;/`|yuKƛ6ޣiʝx +{29݌m{3SxR1uEy   P ,W A3@`J)pԪjc&4#&'wҟLOІ+kxcܪ-󤑟z & YUzުAdѺDzvkLxN|*#`i"#ݟYLnv5xR(jVc,Ie|sױ:`556>b2d3a^k?n&G'e܉,Ro:_DRej'z/sRze̞TiL]O;wĜRSy}{Ƒƪ Ҁ#VC3LBx  re޽wjc9'\*Er3J :U[=x_<.=W:gwTe1O=V;GK%u逢Cby$|hH걚kRJ=wlk Dܫ݇j[~}, ȳ,d PAcg@@@@*ЁY 0{$XfP?iOEk~o?Nzv٘Z-}~Oҵ'馫7Vj46b5ո:Q˞u'~9pzP;iN J_I~%f LG7CV}{#-^xYĿ5s̄j|[lBC}V}VV3;P=%PUmϝLmn`YA'eU7d\08?_Fe|@@X6pB &knds+2^?3߱|3{͔<3#^7RR}219~Fr'SY܊t8F:^>A<\ܽ?2l f6Rcc7 צߊy}3M%ͽg̹O&ggª`2ٻn{tF:)ݧ&',*}P:\^3d;}0+d6cZ)D*x̞0}2'EcVG2=22o9`^* zNW؁   PT$]IF=Ĥ"uSpl_2ˢ .ytz1.$i%uD@huszeCqaW?i*-,/{SH7IbjU9'}|Od,c\Ϲ7qDM|Of/O~H,/4bk}՝(}aw#ϕ{uic[P^?8Gw&;X=ozzXV'!ͻo@@</b@ W .;= pa3 rʛyT,}Hodviw?Ba?lG_>=~雏2ƻ|mKl_dxvтה)Oٌt5fgߏkfJ~P˾{pMzmsȚ.qL)^;sgjO24e:q2f[T}x֜wlB}5whB   P5+\ Y '~3J=oe?˜˚$55-=q:ֲln ʻm0d6Z*+ Lc);0ˮ%؟L-hn~]?߫2.O6R ؂ 44LE&&7ilf9O~,`mQ< U &,fzcg+>!  t Z`t6`sM=~8*}}N ̓*%r=N_nǻsO.;f+5de>3dW{ڙj9?C]-f U]`8r7bwñG9T۾PjhmbNݓe߯'r7suz<#11^ri&˗9'H $,459s+c;}mLִ1 Mby:<\nշf.U`Εߧp*,~ޚ$ZOQ@@@ j&TZd OD9{ I>yNN6@*S/襪)Rr=SO?HWDrp1dE˽|Un[K9ɘtOM^e^~uۛs9d{FF}JqEi:K@.X.鐹\[_Udj̜vK_Gz/+To8Rnյ[~Yv"x@@@}=VɎןH]2.g+f#V?};@Ȑ՗js`$.f&YM]Ihmo]2uc{$vvRV$ș'{i_ynvuCVc-l}r#;>@@@CP()&~;w:rAP'o/?%ɞ$RdyMr~|6vy7oh?Y[הro_>w<tyOy3#M?駷_Ȋg=nlsRLuo'aUG3TZ~Wmb5;&݀0YsyC$%<%0ƛ7V;kL? l   0jC3yɗu4 R-WCqo pjyǩy #ĆTV5܋WG@@fVv(B/$ޟ =6vPvO-'xkgF&-Xq'EѺ@wi &82Y.ʝӦٟv񃘪s2cY5βvS 'mǝ5JdLRFP:C!7S*x$ؤ^{ڛe*k]`i`w;wǪv%\ݒC=dRX=Nf)03A.~dbf#  '`ƬXl?VKփ9#VSU`>`UMU]|p'so7yM')*+C2Vk`Âi A@@6@TPec'S=6vlT2]Hus$TЏvS6T:#.<0:CUz͘o_kpmkw d[*: CCI)-'h?9 eܥݶxZSrHY|d^)USb2d?e[eR~?Obo5D:ːwR|9TA5_Ʌkҙ8H{MZsڽܪoj46rJ}tB 4ELH}˚?,X_["Д \u+NeYӔʿ>@@@&'0췺:ڭVJt]=lk$Oa   @ PUC@`FZM& ?(k31V8O)pNɣw+XĪj#f2QKmw÷붽&>T&xskM{w/MTy?rU."@,[d*oY [EE]يu߲ZW*EAhU.W./u,VkQBAZi[zq'd&6Md&}h̙3_R23 >5ބ洃uxWzJZh8D<+T͋GK P(@ P(@ P(@ D*ݠ(`Jۛpmyʵ+'>oDPlB;'F^v%%bQ~0e ȠC{ o/<0q1(@ P(@ P(@ P(d Q@hʾz㦪*m/Rw/s{U|[h:`B0B+ަ(@ P(@ P(@ P(@ 0Pe{(HZ(©ӧqՅw DtHUgQVYlB1W (@ P(@ P(@ P(@'@U9(@ P(@ P(@ P(@ P @.O_SO=gφ(@ P(@ P(@:t%K`> )@ P(@`*|j#TP]PQ%y(@ P(@ P(@'ki)R(@ PQXu36PaftwM J*C(@ P!2D Pj*YK P(@ Pf`d)@@ 0P(IC P(@`*|j#0P(@ P(@ PB*> (@ 3(@ Pp`Y)@`*Ƀ(@ P(@ 0P@L6E Z*S(@ P@0P$l0UFA P(@ PF`ha(@P 0P*y(@ P`ʸa(@0PD6A PķNj{'WaUu-v_kuF v]-V"VhvZvӮ(@ ^*ӗv+p8~'UZ,Aڳ7w7_ZeVhJr] *jmV£< P(@ R@j- Pp T$(@ #PQU"!Ht .C[5m`s0cc]ZZm-йKg Zx8[^*/v͸(@ P d T>0;,ƛmcY M=rPJ 6LF̑hoW#[CKbv}cl‡k+8ZihB/ieN7~_ŝ"`+.m훹*:^lbmZAջׄl: T R(@ P T< P>c(`&#Ƿt4Ḻvi5V@UW*mJFpnGbx:-L{vӾcF6oSB/@Ukk~:yi5}`<W(@ P`*)@&`Iy8(@ Ilaz,;bG!ڦ~>Sdz۝F5%G(TT5V,\SR,,*4܏~Kxc|*ۨ8:P5I3PT|'@jM*N(yD)SF~=piG;eNqnq`n .l T9Prb,GOܷ U ^Y )tͥ==]CU?݁!i=lY,Be_PPUAMJlxa4UFF P(@0PwUAn(@ P*K0 ؤ_Sޝ-˗gqlp1]#:sGMtK uMb`KZL{^vio,d42T8[}& "pY OㆇN^&(@ P T>n{w8K~t$K,v8Ws TⵍDzGkSb8U8tVlzE7pt1ڑ"tDujCsΣ)q:iG9yϦ=ZFOW]Vk N!qMO*ݨ`']W^V¿~cawfi+Q2J% P(@ G*Ԃ=@&@e(T}~}FO 'n3xzn5YoGdkU&!u FNAҢX0"6܈YO.'+o(@h^D&ۿ{@Ԁ7)@ P 2_wbVGhgT,3?⹧zQرh:3R7 YX9ibԮuj/G#Kd}d=1 r54z4zVC&W7_ U%rU'?u~&H"@k 0Wەv\jB](@ P`x5a(@ 0P@L6E PAfܰb嚑觌sochMb$&wףNRʣg¯So,☣='|!ֽZ H^lkź\-nUUVNpA thy(@ 0Pei{{f:d->~7$wk(F/*Oc]f` ɏkF27crlx -=5z<@_l}"W ڀ뾻[}lV>ZouANQHڷp P .@ bѢg2gD{:'Pc(cB4_Ubz]ƲWRvw* qwyK1-ӻCaNpCxcDOdgƤOK,Q09ˊ{")q&Kxټp照.9%J< wf(uTi~1PeR(@ PUA(@P0P u#>EO o.͕kTjH.B/]4yo]1)۰vT(`Ҭ۱~\n;g Ej#PK Db/?`zbĆ~- spN8(@ K*cգG5n.ݯM|4 >of }w:)/7„Ip¿q+=PuYi M"duMO]/wt Q(q??h ~3Pe"(@ PUA(@ 0P\o)>M!E7zyg}VO{ápKŰK.J <ч(1.DK6TOU!F?..)ƣkPH*ubD.yhx='F*Ô(@ Pa2L) P)j UI P(9#=6f:S!"aEO݋?l'@LPh(W;U( t~jCZrph'm8}1R1ks;|(|o<CxN^5YU}^^ayʺTF *7Fzlxc9)BZSCZʈZ)]szC9"z013Sb0Z?wۖW|( T(@ P#@qjPM @UI P@e ݻ=60RV~77`Ziy[1*%bA#G]74A#_ʨS]~tI q qhLQ[r ވAdkǞƳ~= P2^MS mORpwIyѕH~@u1bԨ ^Д{>+~S>Ǿ4bnot!۔8i㍇„r? [\0jh^J/w};54v;U)@ P(`DXUdC("}~ԽG83:v`Dfz'lF_'}ɗ˱q-&T[?U,i^5u%ӸxG^=6lCv|Nʤ7/=' +m&~4JUL]. P2pqc977>Ѹ(+q:,b,_*׈=(@ P@ 8G Ursg(DΣ )) Z+%-C\l+̉HN٭=މG7r$NQs#DYcn.>ůUG>$W +B?(b"_P A6u=Y4Ǔ ~ٶ(@ P`ʸg?݁!i]V5,/C Z82PGJRV~bx_'w@U"t\ۭx/g7OxUoodnՌ̅nQX3Cb6lGX }bѺΉ9ʺ)11+PSzK7nvcOՃXN{bw;矎Qlst*+i~2MQ P(@ MQsG@( :I PM!Pr;]`\M@\_O5V= X-J;U{81{:4O~uLi話prOuzQM4HYڶo1k 92żO P0U.O~ym.T"R|Ќ$o:8s<`\}k_qi}<1^:^{=:l:o޽ֶ}ҢQ5D?҃.s1(@ PP TL|g{z3+\Uax@~˷lRf TMi.j5nc=[+ Eaa1:VZq-qvkzwAgo8YTNVaݾ};\9p6B Phr88;J~9[aKrYW1]6P5ᆇƈ,×'ODemGw\9SΕ8Q3{jݮ] .p58v9jխ;.u?# 䗖NZ4ckh# [* (@ PN+) 0Pm PU-)@ P0UF9.Pe#a)`` P(@ PY*gާJ*'B(@Uk P@ 0PD͠YA!2P å(@ P TyU(@ 0Peڱ(`,U(<`y=p.(@ P 0PL"@I nR2|Aד@IDAT P@j47S! x(@ P(U P T8(@ J 7xSue?;K PU&\@^V*b>9l[yи?b^mȵ(`P Zv(@ P!`*5( T51@ PC 'T]hעy8( Tqqjk~sm{i׮#.msASijZ_N P(@3 0Peƪ@UįR;nz7%ԫK{Vrb؅aKc[.d [cܲv!eҸŰ 2livs\xǹXPojHS(@ P 0Pj#@n&1g/P*p7zm$r`\M2ru\zy er51qjb% T:}붅oy\b8p%C T)@ P(,jeASXU7&o;y'gJ0 ^OfC^FkX/W#/a\׾^&F^z:}c\M*#Wǵo-|rX/Õ1PU/(@ P@`YM#@j/`U3֋2ze.s/\-__U:[U/(@ P`*(2BFt!ِ ;e| ɆWH})3]H6dBNY/B!vw@t!ِSgl@UHعS P(@ Z*C`_o/`oY/-|szmYGozܟ=^[oG/۲^7g Tm)@ P( Tg]yT,@ \5cX/s |}^0Wob%`ez1Pez1zK\e\bo)@ P( }P!`*d>_L YSglz睲^>ӅdC+$>.$^!ay TL  ;e| Ɇ T;(@ P2ty9 P_3ݳloLJسk-ĉJX0~ uzn10es+c]Ў.gO??Ŗl wn0\VnIQӶ^kʺ h[HwV_^}.rP..@+jJ,T=[pÛ-1ޱӣƺUu^Zߨmo~sKyhon(y5]5noיo eݑ0a3[lzXVtGAy;{ TՔx`!*u=Q}q_/}Js@'+kn66 HޢNFY?u}xOxAKou][LJؔw2ei P(@ 4UM)@P 6PUA_l}">š0wacP9Tzn "S}:imOn^۾[0vwq;6<['Vu˔;/=7 #8_E tWtU˿oeXx;'lF_pb5z9Q܏~Kږ&%‚k~+_ 2/Ӝ޿88wMU/*E[ sSyh^~ש /N_'%uQNRԻAP4Ud@?,9wIԽX0PwRԚԷ TcJw;b&i}nY8tٶj6W >qvWؔ0q1Pez(@ PUP>(@ *PUwGAy)LF6]"P5T{١/I>y4&jH|cr|Xz!+ ^+ _/Y# y{&RY *PU]ހwzuAd{ Ӣf΁*h݋?q$H#yy)Qz 7U῀ hWkztbTbTHN#W|:uKߊp!#FX(F۬x~bs݈EִJw9^^(^Hu{ >^9 cUqSi}#ÿZ/GUX*$d_{FW!mi'?]Jz=-/"u*s3ƾy,Okgi1T۬!+^x|`19<1n1|w ]vO?F>~}(iuq_zqK\3 8Yx~OaH_0Q][O"\F= @xsSx+gkrN2P%2ԯ"pP] dg &ۢ(@ P@x0PuQPLC5<sUK敶/?~V~Uqg]ѫu;|cGN iIݹe?¯3@#_g͓A)`߇ uGl{.<_=Ǐ#4xTp1rU>i~ۿQv; "(-~*F낍oKZxݣ`hחr=< "hLv߸o zSFXH5b*^12{U/Oey#__uV0xK2H@)ȺŸR7z @ʕ%Z<q#X6K^& ykUWtVP&[[u0\2WD@U\=Oc1V}G)G(m>s:UgpClKsp{7P檗 z8qt{f݆%]&UpC}4ϋ.7^ /P%PWg{ә#_`и?b^ƏqAKsôn"M%~@2lĥu=<T_pEph3\};G{*ݧ(@ P@0Pl0@8V/'+o(U?`uԁcPx6zt6Nl=100N3X41W127X1X%֬|sEXy\Oýs\zw(>XBtDκ[ͪ^^9ѣce ڋ}r!&Ni W=~d~r7bחrm6٦3pN_l}"l>s:9^7{ݔgos{z 1%HY/!>7N}u$[bZEfC.=f;q ]Ju@88s$.6}BAeB^xMN.d qt¢^=_Labb1 P(@ 4Ogyh6+_ X^ۮk)gx'Ffhkg􅙷urԞ鎹e︧/̪N9sV1\3:G%^\:,#Y!l Ti˛(@ PM*>(@@N#Bω/1m_xťooH9܅k:zўI=zƹgz<}ao٦BZ$ɯci,]Hs8qsmS%-Âd1r1`D\h6zp3ƮÉ\m!^ּugj<+<(%OWw+^\>F^ʓϿu}:Ea<G#g?M"D0? O}ʈbJb>&B1ҩ5Pb u 誒9cl,uk:E.mj'^uԯK`uqL9Ұ7+,U2{jLښ-zX\ã^g`) nx"_Rw+7<nzo\\q[ܠp(@ PU ç@ c է1zŢQ8$Դ~xDZov== /|=I鎹e/̀o5/Ϥ(Gl9 lR؂1Ǽ]@%3iÿ:9/ܷX8Ls/[U"X,LLc#Rqk 2*kɿ <͕Uo 7Wyk9̂仮Řn hMͯ^ڣVB/=7 j's]5KcK,xa=v$:= C^z;`z)@my m>4,73czNpx_(.j%q\w] T֊oCaUSA<ꐗOoC :IG%~!?]΁C9"zX&U3j܍?нF g`1>4Fq P(@ Ua_b yUխ gвE+tnFޮ籁 >`/`1Cx8vu(;WZ?-: 0z4t?8_v^6dǛo}5z9^_m۶AM[pr|]T5D7vZ'mw8Co3HQ'P֡e\ܦ@X@UX ¯^WоU(uc ̻(@ PⓀkpTc p,8&\eX/s |}^0Wob%`ކ_\mt+`Y/sՋ*sՋ(@ P@0 2A Lc~a3]H6dBNY/B!vwzL YSglz2P3]H6d@'$>.$2Pv(@ P`a(@W0 Gozܟ=^[+G/۲^7g?zߖGt W#Uq[ P(@ UYW( 0Pe1WX/\-__[X/s |}^ T^ ^0Wo2W[ P(@ C`(s@ ;0>ӅdC+$>.$^!ayt!ِ ;e| ɆWH})U>ӅdCBNY/B!U!aN)@ P(`h ]vWSv=(@ P(@ P(~$A(@ P(@F 0Ph2n@ Il]M*j}(@ P(@ P@P^^q7(@ P2ru7 P Oo⩧ٳg.(@ P(@ PCZd ,Xģ(@ P`/>nL P(@ P(@ P(@ P(@ UTM (@ P(@ P(@ P(@ P(U~qc P(@ P(@ P(@ P(@ P  jX(@ P(@ P(@ P(@ P(@S(@ P(@ P(@ P(@ P$@U8UB P(@ P(@ P(@ P(@ %@_|ܘ(@ P(@ P(@ P(@ P'©< P(@ P(@ P(@ P(@ P/(@ P(@ P(@ P(@ P@8 0PNP(@ P(@ P(@ P(@ P~ 0P7(@ P(@ P(@@O5:ws[ܪ*;o>shݺ3z\9{wfSC P(@ P[* (@ P(@ P(@ Ԟ؅y_P$ea-<⼨^wJlZ} P(@ P@` '[(@ P(@ P(jcAxI>D "5se  ؎`T~07(@ P(@ P> 0P7(@ ڼ0#sb틺b ztn}7Ԗ@95.(@ P(@ pZPeo(yrmum*K6|X;׆PL98UŨU#lVYY"wy|(@ P(*@ P(پ]$,Yknst\cgrD|\u/Ϟ丬sfmu LSC\L P(@ 4wjd*3@NL;=v6NW !vLZ|'V_DG6Tvs|~Qa;sc(+;aǗm1/1zS[[awNΜvS\BL:C8];cW1W=|*vAqǶe@b-Wڴdc8 :uK qBUAF+k}Jl'kB7W=ܜԉ ~>ߺ; qtůq}D\^=y?ϵaB/_x~?8'ڋ8\UQkX\ M|.  ]ꭶScRGD}], 9]}>iM P(@ P@ $P(ht)ĿRlJ+c/.!QJ,KkM+qlI >2 =ޙi+%&%JqMiRǭ(@ P(@'l)A>Qm-<F35^sΣ9m&gJjR|].Undmb-eX)cZ͹R%PY)zgU,~gI $v ΙʳSl!':b9k< R #Fyb~{Zޫ2ӱ[$|l"˳1 HYr9eU#e&Zy8f[Sƒ 8,2Y(@ P(ؖRx).F X`}J|`+m+RSR~FAz65amve#nI88ڰ$k>_jĩ);7(@ Pt56%#R )-YHJT'f9bRFz"T傔r)M Ta[s'$),GϬE?3eJI @5REJL]+efc7qi9:o礪\j,< %NJMϐ23ҥxG*>=߶vb⒤mRzj 29Rz4w~P(O6!&^L)O%OИ.CZkK6BeXf"SLGĨKpjȺimFiۺ ~jMVL|(.~hya_cr U:Mu7?|.ʔ?hv;5N/?M P(@ P@ѶdP9] ,ed*ǒ$kH|83Jک N}K.9u|V,Y[8~OSWJyYbD).6Fsj߿KJwbZǹbB}@]4+%PL=Vw{jXN)A WY$y{J ޮ,] 0P#7Equf~ʇiR1JP&I jRj!RԖmr|P͇ꇯ}Ir@J߸4I^j߹6~IsS"R\}l7Jzkix(@ P,@UA(`>L_~h4]bj|*#fݸVp8O\*%I1bBU TD[N;=Q2бGazJJ ![䳴M{u@#bPӪ؜FrvKӜԟV9rx*h7@ruviVC}KEBlj_T$f65bT}اtP(@ P%@U$(&@PCSuu'g%4o&ef>+_;|nCX(QC͘4)XS~ս Tcr|nZOOŕb i W!-6F}*JO#y(@ P((%v(@ h? ꇦ.WIꇢRz}|$'mvć@:*Tf%TNVaJ_q\-Rc+l-R|:TyqX%O^ )93rt?kFM_`DzUbŝjS)ѩDDIXLk|@\1s75$B\ q)R2Ei:Infm P(@ Pn*K"T?u6ERy6Z)uhc3 R T[X&R{LN3/}26<-M \ 8X4^P+v%Дjډ#PHC!{䜳dy}9gZ>dsֺxs[[cF/۱o~18澝6~v.Nm1EugG6EۂonKKYS =';vCpٟVRkS#E@@@ _{l)|x)AZMt'J]qo }e5d Tۏ^2"r|Y3YZk҂q3SX @g!`fݚ3kn xE-;v>q6W E   -~nx^jJC3||vJr.h9ԝLp '_"F@@Fm p UފR0K虵sprb={}} +5l])xح7ؼb3kQ3\¦fV5G#_7zmt=ECkvDs~ShƏx{Z5j9O-[ c=u[wK>`TfpE侮{Z;/ܲ.ԕ'74vn)c:T=f|X FEjRz}h[5HGB@@#P_WYPܮSjsuȸ?xeۖ*+Tĝ]ٍXzrHO?Q;OB@@@sU ,X_ R޺Oҕvq -Xst<ڻϵ'jיs-0&iu8w}<f[՞9 N1]{Mv5~`={5[PF9}N{uׄR)6iv;SUU}L[-st4VV()ji?`NڪQ]ОTטs+I3f沌zJ놜2VsTYfP/ŞqQMs@@@_тy KxN6    b@IDATD8kfrOa咊skP LuCJszެ^9kwaUꁛsgT[~Uyf,re&b @@@ P5:u|uWo5|&@@@H,@* [@_ T3zASSnϙᥢ lSܺ\Ypڭk隭tgTtF&x       P3@@vZ- Pon*sM@@@@@@@ P@C P_ӧtN_wnɼ&`z^       .@ @@@@@@@@L@g4         @@@@@@@@< P% !        l G@@@@@@@@TyFIC         `*+H@@@@@@@@@3UQ        .@ @@@@@@@@L@g4         @@@@@@@@< P% !        l G@@@@@@@@TyFIC         `*+H@@@@@@@@@3UQ        .@ @@@@@@@@L@g4         @@@@@@@@< P% !        l G@@@@@@@@TyFIC         `*+H@@@@@@@@@3UQ        .@ @@@@@@@@L@g4         @@@@@@@@< P% !        l G@@@@@@@@TyFIC         `*+H@@@@@@@@@3UQ        .@ @@@@@@@@L@g4         @@@@@@@@< P% !        l G@@@@@@@@TyFIC         `*+H@@@@@@@@@3UQ        .@ @@@@@@@@L@g4         @@@@@@@@< P% !        l G@@@@@@@@TyFIC         `*+H@@@@@@@@@3UQ        .@ @@@@@@@@L@g4         @@@@@@@@< P% !        l G@@@@@@@@TyFIC         `*+H@@@@@@@@@3UQ        .@ @@@@@@@@L@g4         | 3A@@@@@@@@@/TyH         U(#@@@@@@@@@/>!@@@@@@@@@l Pe{?        x&@3JB@@@@@@@@T^A         򌒆@@@@@@@@@vUW#        g}T_SۢYmwϻ$R TeUs2a        *l3C{*PUkfksZ0<ϫпLyF[@UI͹XM#}ȝ@蓳udoP>)&^[gTSKqz~ 2Fm:I-x"}|k˩DJf}M5       n`;yW3eڴ~٪6`/\{Twfڳ^

ں;q i<|1ڴi~yLDL;zy *(%rweztjܮ)(aNgjS8w_gSַ<;[WPK=\q:ջ{g~ @@@@@@@Y>tT5Vi}4DafKk˜QbslLp3Goѯu/ `gҨx5mMp֛ٳKsbDtosi)vqF__%G B~_fWߛ#+9ȃ3PU]]!1@@@@@@@.{#>zj Z,U>B T5_v PuG֚){>:Ä@ݪoinq8;g`|U͍.G0g]kW xuy!=un+{%uMd[*yy2qivZPKD̹rnL7}OGO;UuuO Q(G-Whn/ƮZy[ Ӧ4LS WJN,$*7Q1        i 2[ lglŖR TUD7r*9zNsG]/I}G\k Rn_;Uk7WfwǯjDF{Ҩٵ@9ZX9'*$6[^YQɢtzwr&垫 z(ӝqyg"j !}QvU! }'P廒2 @@@@@@@dAUJbI1P\eucƭs͒g}D+?lʵ|aYJp{I@!ϔ=kYxDZqഞ [t;jN;?`+ϭkѨִz]77|G T@@@@@@@U樿{RJ`LΊĨb7h3:hl9sB7UhwnGj>w셯)9]sm`L]DqĝΙ ]y|Nܥ""O㖥,-F@@@@@@@ U9t~WǗKbdSY7Kc ѥ8p &PXz\p=@U{jmzs5¹ u$lW_^}G̍p2CUgT @@@嶅&Pc^@U(ti:w}d?zJF2 넢=ڐvYÎCM+Pe[.ml,D@@HTup TViuz lkCt(!Ⱦ[{^WB==PԞB RnYA   IgM]Le ljakk{d?YTUMjAu@/5TEeuM3% M5hD@@@'@*,D._c R)[9Rsʟu5P9=1*_L(N!SvXh֯/WV;gHq PX Un"  HQ"|uhR uz,BCmVmmoYUAq ߣG4KEk:.R|i%EsnH |bWYO@@@:,@*A999 u]/ߤ% 0@nz-rư;P]m{J]wx)=?Mw?~]$m$J`mz谙!mT=NhDT?N2@@@SgFĆiGH@iws1OhR!ZܚYe9{}8ysz   G@UO{bV,xP Q݁wiNW-:zvQp[9:pnLؖfK|G@Ub,˷t@@XmU;2PդַW}妯kþ!uNQg&%?G3F7AWIg m38S#FKWhW}]Y 4ԩ?5/jP^U:g>ȧ+7ޢۆ)ajё#WO ӋJ_5KLtJ= W6KκzJq4Sہx{9?nkf3O%U$Pz2@@@R?^J9', 4?5PkXHSz2sy-ZYcewwnԼbQ k]10QXfF:fUqHYJ`MzpfHgjؗM/~6~k hôa6JjbA4ZE/د ٓaf,`qGSѾ󤽡,-ZDG^sG_=ٸy=0+:7|jGkʪήu9qۼ8G:{ƅA@@@F@U@U T9C2&y;1 f&$ nH*65}ӯQ%j׫F9jcX}wW|*GD^ٯ _Ir;ޫzunΉ QUq *?T1    2fUJZWlM% f m]/KأG_hoiAk*6ԤYC3-wRӲjh{C C#4r5ohc$וiMNf1b2wp Ym5<2YB9WSn R ?hfV*>FŇB8BMZ8db8X*'.C)MZc:|)3v]IKC't4Uʟ#y{3T5Y/zqԫTC&.`]Is>vNv>'sI=O @@@,T TUn[>!ƭ{ExT5/@ ?f#xM(Gfh #;Z?@Q'i\o*Ԩר?׵̌]GoQtFReu8hkd Um;ISG !|^TTcΙvU! }'P廒2 @@@SL;Zr.l֞@jLPYEؗwIj3֙b@:-]N=Pv%nqdBi &e ̢,Zch̒vCBK.R|&ifh>lۋ#P&jY2oY2fi}kNmf{z,aT,r>v9l[g/S7@@@.7UqU/-q΅l-\]}#kJ/M-]5ξVc5+w(w|rf)=r >7[P>81vp[ǞXzߜl +;g8|Iߴ]@O?BKz׸ZXTzΝ<Q3M* ?w T@@BiGKImw^xMgkIzuYP:~B_к^a@uY04:AIa:X">FyU*jE{N(?`3 23jx|U{rB L}^U89o[`87ۮ dp;N־kl,WXjJ+v(*sevӶYZosċE"  \LdG>[9 &ɒV[jDsyA9:|~'μ^_!.|M.O&T`r^"$PU~&cA@@:/ h5$u z e`L=<[Sߜ>߫r?Ԛrj+PgBݨ&C7{铝zuAX@U0 ˘NZpx'.}{ M/tW&,~6jMhh8@4y>K8ckbBM<*PȱYpR҆{v(Phlf/S@@@.7/PU_ܾ#0v,_gASn834%>Ifgyh2r,Sȵ^?w8zuyݛWjEwD KxΝqT;ZWs#ڣ`3~RW#t_ߘ)N ){vnȟuC:W6ll}G@@pe@9AIM5Gbh @ -Qm0 $ s\!xJXǍ*5p%9X8S4%4X%nQqt+LEھa~v>Y}^@ݵE\}(}1f"މo#vCM}YyI:W&k?GXFFeWjY2pZ% Tu$>+x8G8OS: @@@iJg|\?cէJW\q_{|csu | _5\u:;K}z8|9}J6c ~]qE]{W՛e[zo׎#Zs|XUm΁?+9n0@򰙻*Tq   BIB ɏ(dҤ h#2hf>0R^kNUf K~Wɷ8aY//r}}lX&:P| De[40у8yUY/3P*~X=֯Ew:{xq&);O::r%:\m34lsn9Ri"@@@|_b]%@di@@S}ADcL ,/;iq"@&x^lm>6n?v֡]As"\^~oIEZzU} Mj#I̵s?3ԶYC!'MXnfʚc24WP!i!94Zw讁ho HgxS7wQgU8' M%}Me:r\PvD@@*_A! Pu)9   `@2ƕ @TC&*{f0w#M*Xn2[',ߧ Ӳ۟ޛюvHnռw@ vvfiP/#k(ouh3NV\*5u:>.]iK/   *@rXݏjm|n]iZU\׮tUV*̞zem-ccίSf@syC13}u4K25XǰyBOfN9'>3Tujlk.8Hg#  \&.B3L^@  ,PYP5O ծA Լ-S/-wBӲ"){\3/=C tYo۬lWPp TY ϴ\zu}ƠpW@ugϪ1 RW圿O^Umb9W芞=u7*c`Ty~o:[]2 5MucfFܰVу?jOe/M,]!Dw)-wֹi##  W@U{h Pũ       T.Ka@@yr@IDAT@@@@@.!6B W=        AU @Tu!       Ҹ8t [@Uzׇ!       UQ9  Pi       T.Kajkk[߿G-LW PmiWHR]"J]ۦ^ޛveԫ+uozyoڕ-RmiWHR]"J]ۦ^ޛveԫ+uozyoڕ-RmUޛ"\&*5we[eWK^v [^_.zz%`Woy}Q/-/e]E]v@] FҨ)tng?Y {Kw P@O{S@O{S@O{S@O{S@O{S@O{S@O{S@O{S@O}^T)+$ SXqzYQp'WŠˊ2;IVܠ^V)ILNR07e wz)A(S+La eE^a +nP/+$ SXq@e ұ*6鸅zcUz%I-+O+M:n^X}^mq JǪ$Jl[W:V%qWbtBұ*DۤꕎUI'&PtJ>Q6鸅zcU@Ub IT%IAI$JʓvWڕ$iWRHҮ$I;DFv%I!ꕔ'6R+IQ_#yc/E JJUpWEhW޹U;j~mҍc^[?pj#SjնTyA }5! +jT/טy|d+Pg_ǽzjj:>_52zG8eC2\?SdGxvEUZ+ZMWE\u:e,wc4/2?ի=*S>-[PszqCmvDҗޭ=1v^{ZOK^74iLUfׁ=Oho^5oԘJ3U 5L~7n23d ΐiM+7E~SlߧWT^f/jzJ]*鬍j6n:C&iPp@yXT^7~z9隇kOy^pz5|(g<Ty! U 6ksAdh{ JrL\B*Qojˏ_yB-hE FY"Z`&h5O)zz̧lvjgNvUa`ڼEat1o5̒>}\Oj8Lh]Rw&WuGYs^cd:ZgB97;[`53Tn~R#*Pw\LRm}'4-1Uju| /o>[T59*D8~M,80wпiJf)x3^YDu/^_~\7\?:Pk+ƯGCoJbý1~'OODt[SZN\OSo<\ZwJ9hVD߫^*-ofUXz,+)~63 }r*6;bNᄄW1Mkh0jў{-x1-UK},AH! QG_G[<a1*c3~ mE3uX.n=C{UWuL #9#D0@0X%~rD>M6Z'QCIZN5A֮QN+NCH/~x}%m4i~LJt5UnӐKLH'v*:vVWjt.{%A-r^Z2;N#SeV+COLT^?ϫWtԩ%@p+{'ݸ6 Uj\8__*~K׫3Z隇J5+oT}~z%wk~_y^Wh\^jk"Dn!WI{M`` D8PwDyfy2?%7(S4E^vSL:C3阯JdHfH)TtH7w|ydHU.`-ɗ7?4oPܠ.UYZ_o^COXJv&^&36]?K\9?iz #U2z5RǬu4%;W&7;bz=VYm6C{*jXߤO٤yE?D;rR2A|Pw ssB;pR9'N?}QqfaфU:a=u3|?2֜bf6/f U%捣:^79fy%PO%P БMWdd׬2k:ލ2^7,/7|،|V/mW:[/K(q]=Xv]^^7~z5|xï5˯<|[7z~a{?5U/D![JM*2g2K%-;[lz^  <p=c̓1o21o21$6z^͍yPPH@y xWPebc}jkׅdJeŏiz|qg䕙#^(0Ui|KC:V͜J+ƶ/腆*/M^SYuVYu( XtqbX_U^:PVG1U\۟zwCǵS&Y{4iNz؜̣H}+ocoWh6':zY[f~8uK+:/Z8 u6gХtflU_ǗTߒ(N}cd82A̞kf=/W7Y}p=Yc^yy8u^op=َSo_>yPPJ <|,JD'&Vxɒ|Ë=%?_"/-XtRo3u:/wKٞA5۾poyeScgXToF驧Whȼ+g~^5NWuї|+0LJ1Ͼ+8>t~O[12[r46|b+l\(WŐW?Q y4QyhѷTLy0AAբw \Pj&Pfsp6r%D;ɋp/rKҿ-ZK"/j-p/rKҿ-ZK"/j-UnEk@" , #DS8 d*eQ!B^!,Z, #DS+EEah y@h(M!HB^)ɢUˢ0B4B Y yYFW$V!/ d*eQ!BAU$VA PPOe؛MW>{kySwyٛMW>{kySwyٛMW>{kySwyٛMW>{kySwyٛMW>{QPeo6 ,rJWrG^9 _%/iyX~,(ybU>GJ4黦/DLJu]0c~zJrU~?} WP59>Lm,0[J˖     P &@8(*k@@@)j@{!%:/7޳L?jU|RkԚ9s`"Ԁ݁SNWr!UʕzZϷjHͲ: 3.EZإteU;nk`,i3 3 ,36@@@@ UyYX-@AF   # ĆrFeJvJT*]r)7\>\x涎l.YmB7ׁ3UpVR_wVN-jշ\'vE`@@@@ T @1 PPU    'ՍuZ#F+t4rO?Rk>k)T73w-0Ul/i:d1S[C5DWG"udTUpZ@r9ޠ9^jҬImUWwe\I0[5me    PP8zZG@@I :wO_ݢ`iUesM5e,M?_|[.ܻ`AULAUCl.QwNԗ滉e    P@ ɦ@(*[@@@jһcԆȊ09]NYݦs tz삥U19ZMCeQeOiOuiV,3sAՎ3wվ$Z7u:U߻(R_6g aVB@@@y PP5O0VGRT$   @&tfZM޻6ԃ uTM˵ĺj ?ye*VH˗-,װlT :%9E\4bS?KlYU6]>Ѡ#7U~_r]+H,K    P8 gɖ@(*]@@@v' foFnASP|.K3T h":Fo*U,fjKmآxBk3VS9rl<]j.?c_gmU,˜YgjU*7jeu[5oQ9e;T[j]}౳    @(*%Bbg@@@Z 4|GS駞 *}{#{tr)bY}{2`R|a7V}r ^ vc*~{د{.<Q/KSQάHN}ߪI|MSE[6\73Mi*ؾUtZ*[   #@Aq_(j :~v@@@`AB=n:bE`j(sriՃ]Zn6V}3=Net/*&uOi߅T3mHjފTAԤ: U3VLU Yնuvթ̤U-    BH* @@@(pW~M{(R2Aޭʕ(6lՆyWtp4]@j[27dT4pjϬޯ/~n*J43TO{\(;kWݩ:4u."   PPVG@@X$a}^˿6I-5I~*Try41>wމIK[eKg4ܻDh0#s|H+7fVjޫUϪdE*2@@@@ lIv sT9 F@@@@@@@`N $b@ U]X       TmGEjQyp@@@@@@@UO"@1PPU )       &@AU%"@(*%B@@@@@@@ kTE@@@@@@@`n 6b @ UyYX       T9Gj1ؗ/_ӎ;gX| ~gZpz@z,3y-8c= y=߂ߙoL^ NXH^ŷw&'$[;ׂ?c-kT=wFbʭkn5H[K^nO^喀[[nE^n Zy%Vk_喀[[nE^n Zy%Vk)r+/Z PPeQ!e$V!/ d*eQ!B^!,Z, #DS+EEah y@h(M!HB^)ɢUˢ0B4B Y yYFW$V!/ B  O|*.l򵌼ػ&_+l򵌼ػ&_+l򵌼ػ&_+l򵌼ػ&_+l򵌼ػ*{e `U<Pr@,J^<*yYPN+ey@9#˯4r@,J^<*yYPN+ey@9#˯4r@,J^< r@*qbGa[A^aX!l++둗9myc=# RvG^vVʎˎ¶JٱyّCVWX);#/;r +ezeGa[A^aX*;r Un y%Vk_喀[[nE^n Zy%Vk_喀[[nE^n Zy%Vk)r+/Z YP5˿M}-\^_?JS< t>xWMV= .ظ\ܛ[|YUZfm S^oԟxTYsԶO| S^Y}:^iY7|śb< ҥMO|F J\)voQlLw~U_{zߗ>NkJ$n_":^޷D'>WJzyWzo\k9몴ƒNS^o.]3~F>|N:'G[_o3>XoخR7G:|+4"~y8W2xZVOy4|^s<|+zyy8u^>٘yy:|^>d~f=>?cT%_@`TMM58]TJ0*[tuWy&/{WlDGkPXzV||kZB":ڴ/}+;q]ܶNV=~?_WlJKVHuuΗ4{W98qyM?Y}*ʡԴ͢WfVE}+0qɼK^3W5R +s Pj獡{}9`P\ˮ5;~V]S_r(<\k~㘇ye1s^>yc똇yy|(<(>* Zfk:6EVh|q0;a}/PLc毣o~kX{u,0?>}# twvMGu_٥Ȋ@>WOpߊkr@{jM&^vl(ՏEssw$㼆7y1+yşxMikNSب)WWU <1k'_ c昣ދc x/*^n>Wvo2Ŋf_vsiV^ 1z=73wMj?~9_.SPUۘyz=tۼU/c>㘇y9m^y|}* @X ټZȡ+*S85g`)=<%4=a,X!5/#eHCf# rR6dU^0C03|Ԛ׺y",u;5ѬG> |y|)ݢ;˗5DAUǚi4@ Mמ5[9& ،7ܧ7ˇI+Obljvk(_w^To})[GUh;d;;j2]P}+k#zd.B+Wv1T>}62Wg4i=-qlBgk֨|>vK:yCSaƺהqt@Wds/>vnAc.:k^9<|1otۼ/>yCGyPP##WAդ:'πD׽JsڿYѡijſ);/O|"?W6QUW%"3[G`ShUO lŮzk]*cυIsZᲩ 8tHoIϮRg>553=m}п&:LiڮI{Q-GEVlђ*R2E[WvjOyM2sK0*61TaG["?J*X0-=onĖ@ bfy8Vy芮6e?G[{׼lLguJy3nx'ʿ1:k^5<1_J6k^?<\kCɷ1   |+J ԙOdMݭTű_PqM0CU &oq>_D 3rM׮Me4pmjafamYSZ߯r"ذ^.hfgN+)z.,!ss^t|Wj_Ro4k˦N璺lH-_yO3QjN&RIa{RC^#7_}+~鿈6qQPeLۺu!>s'5޿ҹvܮL(9®s7뮺Wt.yW~Әy7m^yc{˘y}|טU= @H? dNY0} B>#~0$ ߪ<-ƝS7V5mg-d/ nz'/5٬×Uw['d4Uι;b*o/򚼯me[MbtM䨺^}A.KKuw%! SCOw߿1+*R!,95O/}]ڴ@IDAT#s~kt:~i?ߢ+ϋ5kNݜhݼ5җ?%-}i.'sgS,p=>+T*3aar®0~Gq%<|Ac4s^>yxc똇yey4z^sz6AAUVO  ^ς*f X5 ,~+~Pf{l|HXk;Z;]SǼǴ岹FAĖ*x@5z먪vǧ١LuEuuTүNW0[/l:;afo^SO^1j.9ߕlRŪe_ׅxN-o_yT%˓UyPaLLH 3!QPA%2 3T-^~ |p+l?|5/_<|&/<|1_ 4z^\f:>õ`O2 0 :'O =fVJ3Uwg% :A{ͷwj F]&m(`jSɇ&[#}*]"^@pJ^92MVg49itcKN֩'%Uc.nkأږN4SGڪE}'kT|HuunYί5oN~[n$kf[84{\>?yrұ!ӟZޟs?I]oNxOŹ>gɁնNUոEq \ ^lz^ ϙ0KӘyo|5{T|5w\5/_<|+ӿRp=ُSY3AAU'r _UQ4L@}gޜ%): IZn^)Igu|&xS/W0cOˁTzO/ __ݦ{|XfmWcvLJ;'Uxb|/ֲpy2E) 7[SվtX1̩ɶSSg+}R}]yЭ`'3[߳V=zQZ+!h.w> `Pj.(sa3߉L~iy8:b>7"ݤL֬S?v{Pጋ|+{\s׳y%|S]<@mf7yc똇y:k^<<\kq?cT{"@ UP%9Tf)9x?۪d7S^ٴj~HJ6L$ky͟&ԹgHMz>43ɇFLxNe~uJ އڼ*}H͇c1iI`VڶfUϹ;bLJ{OjfʓI]TE}Y>\WbP4^(` /iC/ff9݇ڒzt%<"~faQ1 Ӟ̳n.'/ך3ՙS@K:;'Y;G3;}+6zKUM1@4@ $Tjr26 ٹL]kƙ zϪb{Fk#݅f5{=ۯW7Xqc5Su׼c똇y:m^=<\kC<(JvD~!J!5LTI5pzΞ_q,i${-7yפe2Zϼ/|5旵/+6KT6RqOKO% f ;Ma MA_B}_GM2e'Ɵ{vPXNGe/2^V+|utrV83k-^1_w΃9|Mխ]:>4[}kœl9 Oe=q;ں!U}:ŷׄ93Ӛv/mSO!qpC2#:~a*Tz s^dl׳y%|g(pf7yxW6476AAU"qE-]A^S(Kr|V?PA6Y__Wf6xTubv%%/M Z՞6s*۾(7yM'jfYmfա*bʼna]|Wz9 C[wTqmWL7Ndz޽6NdsO^p9as2 s#)ݿ_9ڜFtfmY}.&:jI$2֙cڜu@ҙVqT}_R}KP8mW9_Zo1{lz^ f٧qf7yxWc^ᘇyɿ1of;>L}4AAU*U~#*EfZ%K./$ۏ~JطbIM^/Yf{lm_Oce^|bQMLm^e~#.y׼;^E_-Xt?V'j=m2>Woŧ|L^Zu!g%r}̣Z^QC^G1ӘGQUER1ØUeh*sAb:@˕$/rKҿ-ZK"/j-p/rKҿ-ZK"/j-pTE((M2EEah y@h(M!HB^)ɢUˢ0B4B Y yYFW$V!/ d*eQ!B^!,Z, #DS+EEah U!X'@AU>{qbo6ZF^T]F^feOeeo6ZF^T]F^feOeeo6ZF^T]F^feOeeo6ZF^T]F^feOeeo6ZF^T]FA2\*i(9 _%/iyX~,(ybU"6  FFFljmA@@@@@@@G())Iߓ4@(ۈ5@@@@@@@pM* `UDAC@@@@@@@(UdC PlT[/       @1PPU ) D(z"l@@@@@@@EjQyppY*ӣ       * K@9(@@@@@@@pN*" `U$A;@@@@,hLK,yć[˖.}/bѨ;@@@_*3f@ PP`,   ES=ۤj]<w;OV{Y6G5:47{~#*b)ۘFP#]IU-1p    P\TW-Pb)@@@KL)j)@AU)*w*S@V' tm(uM"SK džutFZ{g@@@CxBT=!X6   `n,Ӂi֓/NzeɶyշgzKm?א}ޓZxbyA@@@xL #@ PPUٳ   @q M%K=ښUWlFV6woTEtpumb$J6}2kO;i7Vu\Cy6_!ŘFݙvUTe1q@@@((*IxT= U   PAմ$b6)r37_ު`͟ҁ˩sVUUνWm&ٯӗJ@&\D@@(& )m *@AUA9   YE=sD56='Zr9\l\}kk}zZ-rjt+qg|T/Jۘ':>މS3Y*jr|T?X߷r+6KB⮊쿤7$~jۺ":iUbBllirj[sqLs*   PPI,U o#"  %0!SSӚ؁cm]֪J0PA9e`rGz;[2H]N^ERwZ'/%UuԕH]T˚j~mm lZڼmo8ud%n:p-3n߬xV!P$/J*%o@@@"gw@pTΒ-!  )0wLADAxYoҨ4&iœ2)fT8U}MZZ"h#7_}"F>fOnYH43hKo_TEʞ @@@JEB PPUHM   @V7ِ"fO{[=K{7SlMDzU|ճz*?v/WС~5UdFA>>ׯ:Kj9}`Tۚ|iw?޵K4٥,Z-ɭ=ZAdmi`rU3p    @PPU]QU+WUb)65STzASP,yߘ)*{i*OKC:FI*ujSŴ̬XfVDɘeNjL!1 (   E%@AUQ"@!(*&B@@pQU9Bum]:ѐsʾz'gnUu\C$A8yemMeYgjUt7bj8g*ZWߞx;a3`e֚vhNJbjX5[3uIԀ#٧,65,* @@@MbKE PPU0J6   @ dNwМ/5}qL*~OSRe"u¿>VVfBvW4t=uNպnfZ;5UH7@}je|Cm]2'?PہT6Bl# r@   PTe4BB( @@@ i6(u-y`֝aY1jNuڜnJ'Xd|)ɾ PP<   7@rc PPS ,U ̓!  []>D>ҕFЏ̥w=~}hGq ueV\^*}V%+Tm-OTCI~?OEܜ0O!X @@@bRf@PPDX(       *@Aբ U.G@@@@@@@/@AU~"s PP5'+        UEF@ lIv        P8 gɖ@(*]@@@@@@@(bf'@IPP$T&       +@A Unjx -ZK"/j-p/rKҿ-ZK"/j-p/rKRPV^,ʢ0B4?XU[;=>yk&N`~O^Zk{mZ5?^;=>yk&N`~O^Zk{mZ5?^;=>Ubm@ -@AU‰ ?T;_{5'[$/E^n Zy%Vk_喀[[nE^n Zy%Vk_喀[[n* `U)P@h(M!HB^)ɢUˢ0B4B Y yYFW$V!/ d*eQ!B^!,Z, #DS+EEah y@h(M* (ʧb2P&_+l򵌼ػ&_+l򵌼ػ&_+l򵌼ػ&_+l򵌼ػ&_+(7Z PPey@9%ey@9#˯4r@,J^<*yYPN+ey@9#˯4r@,J^<*yYPN+ey@9ͣ* @X Jٱ(vVʎˎ¶JٱyّCVWX);#/;r +ezeGa[A^aX!l++둗9myc=# RvG^vVʎ(#Z PPVh[nE^n Zy%Vk_喀[[nE^n Zy%Vk_喀[[n* `U[w2|PU?UZn4ذK7ꃗzմaգX{εZϽU[o^e֦a>5z:zMAU>OmW. b>E=ާjoXuW+6_-]g԰m&tW߹} vTK{/򊎨}+MQ}3{$9yeO٫J+5z:^R5 >8>gTCPyxrO`ޯ5iUGc5Gmү yxc^SyxcCgC3AAU"oE-_AՄ:ky(\EUӹqP[[KWworWyFtvN剥woʗFn. *󽿭M2ڧmz,lՃWFntne4_W^?|KwUsw*ڞէr@Mk,yeg̼oU$޷2 Xy[ܱ5`>s՚\*ukڰ2`.,q=>OupںܽG 5[lz^Sl5U9 e,Ǎrpf7>yWkc>㘇y8k^yלLJk̃!|+QkkSdƇw_{+0f:淆]{=@'?}gTyT7]1 s[M>zѿ&dQ륯hdžRXtL=g?}MR;k}xW&4E88Xչ5{RW,b˵|)_HgDRLwjlR"E5ݷ4KzJy2wWLPF2 S&^iԕlRErEtVac)`GCbx1p]Uukͬ#v%??f98p1p'L⚩ze&Shf>El7ǎfwu߼p1s3sͯԛ{㗓/2U[yWCO<+xPe.2s^>yc阇yз1 rL*ͫόb;2S}f ם[O˂=@ɱ2!]cB:[4df=*ɪ.wkCkiQU< c>3Gyߩ+2Ϣ;YSCzC^;̗-ܱC߿|YCTy4٘Uy:"@0~TMcOZz Oiۚzݫ4i=@ f1Zcm_+_~Qq/l?]qY"?uQO_1VU*@ |+`YwF_.e <\4.:C*}SS3kkr>t~|TܿQd[_:ϖyM߿Q-))S$%x1}eפ9/3:/ ӯb:Kvu!é!S cNl) )a6LSijS{|+ɦ.Δkz_w=97ㆇ{r p=c~]l߼5oc~=L_cf?>|*OHDJU;(A 3Tepmr7Eh0)TF צο-fN֖U=5q*7Q/ ҍf&?͝}yR0m{9EgIy%{F6lt.Ȇb/~W4Ԛh"'E>5reg޽rNhUv8[yc阇y:k^GyLJ~yPPJ#٧,*3bW 3H*#q@bYm>ucKZ~& ~IJ]wRK^Cͪ9|YuǺu|AMSP3*rJ"V(VLׯOMtBQwxqZ2J͠8z^aOc>㘇yy:k^yWGc5gcTeD ,bπ_3 ZeƇt5jXS_U|1U|o|+_qL[.kMld'hia U\}b- ~vѿbPl -Ϛ/3MC^jw|ڜ~خT]T7^L+jZ_y̦ou0ӭR^S]&UZɱ~z]R%Gq_r*<PPe8̄Ԑ=UT!ӹ0C0Gʗ1߿S1_u׼Lor׼|5!OceC<( D.#𭠪cOZz cf43~\uw?P$ɾ|k~m=lԥh҆RH>|kb?҇>Q!Mt#d{FJ?VϽtj:yB["Y;6Bпfف=mI!-mѡi1uK^wFLJT|]W]_JK͝4&O^Lbf&U㎱;ةH員)'=~I=؅攍DApnO]3}6PYmy4[YKOXz)̕϶ ĭ>yL1_JGϧ1_}1_u׼2+9ڏ10?cTez"@y UPMS0 80wVk+i?P3 d5򊚙tVg1?:R}3FXMR^ܐemoGE hx,?.~9ow|x=sRU;_/f(X̗b-w-Sl `}zռ8u[[J{\WÜl95=q6+ѕݪ v2s=kٯޮr-=|GyS 5撍2'63ZJL#zY,rHMʔl:ZcU_,θz׼rg5w=ۯW7Us*t<\k~똇y:k^yc>lf?>L73AAU'r_U؝iNe`{:J{35ՑMd~d4HkB{pC=|k謏T6W$y_ڞ}ͫ羚|k 8fmk^Q3*>y|km?nvQ]õdG 0_ Rꚩ/dNt\׎gI(ҙIr7yMY,*U[zKϘ?o7Qo~YRڋb#zt.LeS*tZt4^ |aаԬjT%tؗu/Sm~"kYou9ٿuD}$^"/?zeUN"د_O'g3E%xr=>gn<$3ҡB >zNϖkp=/Ww*<\k~阇y{a`ۼLqۼLq/yWmjCsocT%_@`TXښu!Y?0R٨+*ge%i$i3_5ř/yef LU'fhpZ2_Đ~Uo3Ҋ zʋ.yT>fVfV (]W~U2|U{7QL4p^8p<ݫ}isI=)% wS6' @17RjIlN^i֖٧Noo򊎨KdN"j>Yt)mu=?G%շ$ S{vE/c;LYKM}a}ja<\k~ᘇy徸y0u^yx;/<\kۗOcTR7 0O Rщ m^i-_bjO28@˭}+LN˝egoyͶ>[^9XŬooy)fQzZaa:2J|+:i>{x]%=E'aLo ]|Ӗ)ss5ֿ\|:ͧ5_|&["yE^{9Z-S^>yPP] >Tl.\\I*N"/j-p/rKҿ-ZK"/j-p/rKҿ-ZKA[yZH*(, oǎQP`J4,{1h,cIL"Xc!XBѕbmlafϣ{-羇;ߜc$5!fB2d 3L!IFM_2r̀$&/9 S_f@Q򗌜a)/3 ɨ KF0dԄ%#ga H2jB30e$5!fB*3 Q"@!$2DEuE1d֑C QoK1d֑C QoK1d֑C QoK1d֑C QoK1d֑J!ˈ9TA _2w<_2w<_2w<_2w<_2w<_2w<_2w<_2w<_2w<TI! D\$2<y\+_撒G;<`/sIɣK~0 򗹤ю%?k\RhG̵e.)y#ZA2<ڑs KJ_V%%v/y\+_撒G;Td VHTYd2 D"@ D"@ D"@ D"/;?6eGԚ"J  D"@ D"@ D"@ D;K˟HP.%xHP*D"@ D"`p@IDAT@ D"@ D"@ d VDT [oEDX70`2 D"`)e~/ B $0͂ap D {Ό4 D"@X0J@K"@lM*[0s \RԎ"@)T\#AUpY D w^T%&D"@- A-x@@/IP/.I Td D&@*r. _d- D $ "@ DT D  lT"u#D&&H D" k${#A D+ F&"@ D A+L"` lTٮieDdMUY3D"@@*w7 8G  Di "@ DX7TYz"@M|tMM@ AU D"`HPe].&Au%D5~USA D"@* DMBnDTلiD"@dM R@7ڔwtTI@@ ɮ,?A^xQN"$~&E-3aYtwF>}g8%VT1xIt^Vuۆ˂'DȰ~}_IMDEuPZ]5ΐLE,>TEC.p`ྌXq> HC2-k29$D ʖKk#D/IPJQ[|𴻿,$dgd)4DwSUTy0AU@ wQcxuB3/28eݘ/PN% fZ\ 3&74ɂ d2HDiq5;kum󓛣) +jRV*O&[ Du>PYW%.e>ǯN`ئHqEoG Y -÷⸮dk;j}$  FPX40ANU+Eɸy:B ;-JROێm'Xi3Cq ]*+2q$m0gpy-|><7~L_騾yx 33kеH\rݧyhjXMmM{IPeSŹ_b鑽ͫW0|hm&"a`2ڞN"@  Aa.TKȒM,qɦAz&9j=u{EmWw/k DrsGIUw¦Y[XH[6Z`¯wKS5~ u=g^g U)-օ }e :Al AUQ"mY(11PʨR?.0IZo r PjLS*ثV[D/|="%߸Pb&Ä_2T|p ,  Rwf 1AfTL'@nXP"6yuAT:RZqQ| T>!=+\֍aRK?UsxjCT)G7H[Ncjuԩ op~ eK`!u: &Uly| Q43 Ʒ3#4˩B5 WJQ %p{4/Y;nk(/9¥͂p-C"@KaN[!"G[N북h"5 bNՏ<~$tW)&"Tn H()z5>Xޚ*;&U#tW1x1ti*)ٶM6ѼFDȜ d 2/IP%_?,S\F'DZυߐQ(WJdOz\_rBcqAHd-5]eAJcChR#qDDrsqHq"P'=+T]\(oĩ-}#*髲6H{f %T4 FE @<ǀQ[ݶ-o rG_0W`EUxc7m>XhS\) D 5EͯF7b,/ð[_LQ,z0Z-#S9bQU,ƄMpFت\@DE?p)FF?A!U9j*"`)9T=~~@eFө]06Զ[? >ke]PHLEJ(|/VT='CJÎENj:/Tc[Yt+s_qE:NME&~McQ #UJ-jه_'XE36>Œ>L2*9|4&;ZJZ Dlϰs,Zmaf_0p0b7f'%WCE"@KN v "ƁXuC|wuZ܁Mk* k1wZ.Q j:OktKGۙwp ՝*?bn>2{NeὃXqdntp2AE,X4mUK*"$-jK$ȸ87Bhق~>Y\pD> E?¾r&U2Pktb()fp5κDڛm_QEV*Bs(]@z/,ꗿ0fS8V^#y<سDg DUV.2 ۽>b^cG" ܀*g^TO&UB-\=P+ubFF?NJҰu{1&,65Rk[Dx$GG_5ׁo#'7-ToR I,%k9Te&MMYGZJ'GƳF X? ;֨0iRn7sթ&~5/ktmr )|9CEbpp&ccDQFAڦ9ߍ1o6C=/"IPèH@OsQny}j~Ojs_S- #bXi "`n#gu_l#N 1=B]w̙6&UB-\`b}:1Taut{? Ų ڽӕp ̥":6(BYԯ)B]:nD`kI%% $PP AUxQk"@@&AEi&Oݔ"b%BV\Gy֔JU*.X:EB5^aJ(Y/Y߸H}jeWA2pnT I [У~Ϥ8AA27}M.t4D~\ܮ8C+V~&GΧ@ԷUXQ YdFjb}E21F ldXvTa7%@BR:W\@ŧ*JC6TFJNxkCDpw65y񓪋e)C6M78=@⛤KHCYYU;˚*b'wDmZnz8Kya"o׀D AiBhoO~IR&vưNݴ=J"kʄV+.I=ÿs 3GWU#eqP̉ ,3ӘKum3ͫ oh2 HSFz_b=Dz89mvg-DS$}Q*):.#֠Ha gcSPM e0q] p9\[ (W.\=Ic' P@Vww8@%Zæh`4-P%1~/ l̹"H#5}߱} ӓ&FBc24)A "T([+^%eD"7^OUQvs8W`&⠦ʡ2i5S`Wc ꪓqm5 1_`O17Xm I^BKT (@@*{5 D@ _`J"¿|]tHMMEL~ fc#W$C=ӂXԦԃ:ŀ9ح +!|_uPw4,qt-M3՛=KI~8QEd]Ό^_ '(4ޫ0dDb Bf&6D67WzZcp^@XS [dy2͖j6"5XQLVhDž}0iAUr_,j:P-T@z$ERvXKIQWOıA$*OEZd<ީv^}Fk̅Og$R\a‰ul\uyNX!,9;hT Dr$JO=BgrO=̵J5eB`PIWgLPtbӷ2>|eOc*zu= `v}e3lMADae >h,29 WO,w1DP|!3d X+dK}Cq?Mm;0ITKD {IPU/ǯͰ,BS5Vu{6V/t wfﳹKj'cma`t(@ Dd K"@4 lT".tlQI3)[G <c`p!į6 r_5,ƣGB/.nctZÍk[W'H?V (6 ͣ Az l;[= sZTYL$prw(lR /0A15+vx8@ɍjMw^7ӺѥٚYkzwgmS,DR}k,U$mV.^I&o66۠{/߲B(cW E@^ȉJg)lp,ASc|MD:(Tə:"H >oeU51j*煮^]ʈT+bKu$.c*>!AՋ,_YwkgUAy#pW x,z hZyObS 2AP&Ryx!$,#Q5~/ lū"j D #~"PDCADH~Bmt&vV%"҂)ZdK]j([F׬I=P,`m#E)D.J $})c]wN#UR  A9  DUɪ* uQ)'/ilr APFd<wECe>LJ!tFDz=a~E !P)"\|^zՕ `Xcܨ5$3r$@*9zl  b%&iT2 )k4SP%O,^^+6A%I6 W*5|k:[Y`408"l/g>€`bHMcbXcsa`sWE3|3qĢѕgY!Šz9|n\JGDpr"Br. D%x~R ק4тJ<ǐpPT+CWAjsu])X~UV:2$k5w5[LcJ2X*i*_ZU<(3k_B(mE FDx!V&V2r > h0?Ƽ{^" oa56{tقi^AXr&R]ִEъ$$! D(TEC'  $2*ϊOA'V0KǾT&0@}X[,yՄE&+2psE񮪽T&*NUF %]Hx|ޜ!4R(R%o{3@ϝE` ϰsv0GRq{;Z''gsu`-#j" ~/ d2ȚXq-[{XbǞDQ*HHOPU#|=l$W/pjG,ऀhZأHU\B*'*~lZq5Z-tF b˅zUo鵧 "@'3AU"vhFpz3VXU%|Aѧ}l 2#^Py{'_@ۉ.(wfjOI܅CLsHUFd Mt*N`TJBmЗ=Wz9klU2"A):t HUbbK{2aS/zO-?-x/CRxMevO /<ŧθE]XZ?JtIUC6AaVr0KmOlK*u3-X@bM&71g뛐"D-EraQ>wS׈m3$p{DMZAU"PScAUIL_Ȳa.+F*"@L A):t"`M*dq*{"M#AUvƁ'T&iEQ/ cWtDb۴}e UMl 2p{٣|iD",8AUYa'.2x $ѫsܱ]GZQT~c!+}amھOPU:/h#TeKP7oE]j?_y6=XwԲIسmӤ'TkfJN@v HP.wYs57;UU Mj'*1RqWFJ^3/gX+,Uߥ],s\Ѧ"oÆݬKS& pw(~ ÷ƱN9*=ֹMeHe| LѡsH@*ZUT%3AUY3U~LPS+b22`% <ŭA<_&+AU05QP[ihSU$bA%AU9@E%\(BU"UEe=œTJs@Bb۴}-37I*[LD'"EG:l>F'D A>!DE&AYQE*7'*[Э'V$Þ5QػQ-2d̿TXʿSY r"dѴE*C޳:TYjk =q+A_wPۘ6 WJH#'Gu4/ cu-/pҶߓ~TIR hZ5Ѵy!D f&e7&ʪ]I<"=qF͉ʡ2icl\}aj4bGgGcʐG/T~[@V*E,/'*IAM+@MЬ{/ DH~sZ+"@HP%ADl/IPMxy\,Ƿ00)+2oHLMC!@]VTEy$*Lxnȳk}ByH DVۍQ NkU#y2T=EV}}ŏPشbY UZE= MdU}郇]4_YKܼk)/QXͫ*#^Pm4ulԈ+NShQRj'SAX*!zfNAвE,^Ϟ0i5}ѲRhA _e}qF3:򕾠K\SЉx0hHM.G v5]튉~b)e>plȚh\8pÃ}L?flm|,e[R6{zh꤉[5岾>. o͑}Ɏ"DJx|n:~]?>Ʈe4迵g{a̘8}f0. ||!ח1pb EVRv!A HK-?Nڔr8 ń>G:~otj*.!xwNp,BUF;fK5",ѵAfΈ[f" *O9M a_OSrxSΜEU"D@D *6LZ ~ !NCҚJ.U_fPJǏVݩwJXD*U |4,DQҕ:LA9QRHs//).3AUAPdJ]0KgiƴgadJTc$BVC 9ԝĬN:9H<鰰fB`C;v,$_V£|4,DQ(]`\ե47yFe< >FFR=ᛱZ3ȔY$/XT-V,+6*"@ +Aj )&vECڹۺb{RX^Va7E|s&f7 2mشХ#Y9%uF"j9ћ c tsJ/Ь@ՒE#|}67ʿWnmߪZ%ЁA5Vځ;&A@2Bz.rhl$m3=?-ZE'ʸ3,8Cxց}~?â)tN,RTuǶ[g׉GԚM{IPeSWF )z>Xf184ћ7$=!c"TaY&jU`bQNRiGB * Q8]T{|X^nJUAUas4,oA,) )k0-!D AU DMR~q,@~P{S-:}+)#Z@PL?84l Bq,lV)\|Dba.T"KX_Hhy 1Wm YJY췊P?3UV62ZHAB`[K[?Tސ^h{A2`.a6C:ea $$2Iuz9nu j(*("} 'uƝĄZj)ei/I*H Xv9 VTm LaK^*!FJ D,F:s2q;XrFr Lx%\Uσ^PL?4pזXIE>ꒂ/sƤ :t"$66С2=ipb)On/7GΡrymta@0aqY:>x^u4&u5?"P `VO@.$<87Bhy;/CAo}cϊu:+;ǐA]4"dJ,$: 5 ^>ك n>o4fXԨ,jMj}_+/`'9j6KXG}! صb /&㇑<c`p#k~ZQD6Z3swga n]aΰj W2ꋄaQXGe Xħ,UV(jb=SF-O<((h4&]85j E3W?O a\˥L Þ ; iQ2^Ntfn:ETi`$~ dN@JiD RYwGMZ1TPd A҆79_)2v+~PNz9Nom'~?y3F\G8!UFa&.5{h^Al> {Qμ8љ8b>_ ]q ڔGJ~CĄ@HPe&(4#ALAf9ken^w,;}/>c):rkX?iN znXz]u)r0Ԇ{ >>bze ,6X?`{IPeަ FR]g f 2jn$DGb"* !xH2=s͇;Pkaoc ӈ]f`;{q䓘 B/]^e]wl':N:E!RL|fJ3JDTXP"-6 ._+"bz6T: xbxw,#I+)psZHP+@ת! \^h~gShF}nn3IqYze︰lN,ņd+7^pXd\d/8ERt :\q"1KIPeՓ|F[i$ |h!'R lǎKӄ0n`EK,٪ AUxm@)P7 hTX@wcʙ%5_H?î`9a_ƎU#dwZUtmߠcpbr*e3I5&"X .cвv}.\) p !juC?f!Ajx T'g{}vcD#G=H_ պLx9!3 Z'bg+_^p"Yd\d/T޽X*[.[  Q*ye՗"@HP%ADl/IPMx\q>_$22mR[pTRսM8\H-(NgzfYdogBHϮ-"2ij}v&!.h pYI"x / Eg$4U AUQ>HdzY5(atATʿhO+3=[x8YHhFQ+AKtg)b)A;mL%x$h\F\IGD $qg+ OLOc~ \d'Ugkn{JG]3=Bt5hC%FFz}K͖tx|2H_hN*Yi; D +$ʊ'D!_`$9W?B$Ĥu,Co6t1MVs"t(_1p5K7fUuGs8̰쁻W+Y&;QT߷i|eNwӕ=w,uAPv\)=f6M0`tfT$'C -\ĀMofvuF-3дdXNxp'y4yYSԘ?`d KVqAZ(f*$*(uʊ@7<ŵ)HURVFժѤ=*۰wked$OIWW=ԭ7sjR "QJE4]fΣHJ…;O_/}p]nێYٖ MS L\jU-vce5'D HP]b۞U˟f7pr$b=U}Q9P Slsr낍S36=F?z-U렆JG ܋nƨ*Vu<)E ~UL,I # p#(+GjuQXaKΦTytقTnG*GӲ+^U ڷo~dKJ/kF~U:.r-[;Reڴj" q0H'q&8VP$Y8IP" D ; lTe\%Np䯜˟~$99%?_=rJ.{Ng%AUNm?T-oKFK̛q_yR,E2o{IP7s3 ݐ K{湙zyߗs3#+7/ 9H/IP%@G>-$4_&A}[H/Ӑ%!2 S#',"#s~/ EtCZJD/yGjKJD/yGjKJD$:"@dL&A1:o!!2 #B4_&G//U< IP%_ߘK~>1e{IP%?H-R">&?R_R">&?R_R">&AC" c6 d(i$Бx _< IP%!2K>-$4_&G$iȷL*Ɣe$0EG~_)_K*Gjݐ1KZG1KZG1 /IP%cGiL#}[HiȿL*< _o!!2K>-$AOCeT7,#):;GOLYD2EG~^T?R膴_:򗔈_:򗔈IP%ouDȘm킪{yd2wvQ| { {٬@'4N|EF@+oAqkW}ڻϊ@aw(Zw;V[*AUfz=)b+8|/Uuzju+Ĺihb_DNV% D~HŘ;Pjt_w*:%ǃdlu[H urMMV/j o +kӚAK(N^,`DPCY|xln ^gg8*$i)CWnD4PfwSt*߿c&;r_)_*QGƙX$)r*^hѵ*Dsۉ:m qx0.Fb+On_]jF9eeظ3\*[>|Fadߖl_{g#dV]1tpx}sj$UT W $`)^ z](%DnHš9=a?qS$\DmO}ն|UUd1N[kUҠgSD\ފS7ϩ>vejåa/._nU`Ƀ?SҨU-Z7:ezdPQ%žz!_ Gn1]tJ_/cq.<-REyd$fx;9+Jv.!5 MǯKC `wP66ElF#c\_sY\ dۃ 26Dp$:w0|VF`_"ߤD@)GEuڃyc0Q}| /)"|{3Zi5 ,/A?~ ^cEW/pQAW?a?A=-& a"GEsW L8!GL(p/ %C '`MÔ[P})?b wتUOa\BMw Jp~l;imy4*s'lWP5C+`pV-%CgBx_@DmM۔2ncg=̿l5CVVRVu{oGc3Y[%@;{FPzDS`86p6Xa?v*k!}T .[UDMiF= *Siݚbgl_=F/C^avTeM2y0w}>ٶmgL MZYitAQOqh];ljae]pH|ѻCǍZ"8T\FD V+R\F+O^!&S۱^!#@&1ɦtxM@*0\V[.DI\/3u(dWX[x]P{+ ;o%SXu^M;Tm ^0 Ц _|JL|s$z jMQI#b/*bQ`g|ESw1 tEGٳrX_عLPSbbX&)>_㜳xLjX"vQ_zf*vݤǙ w/,e88;c¶e&5 zw0ȏ_tz,"7S?+/~e߇qGUQ)@bE1,c_QA][9d/|}1x/BW LPUT=/u0|MS-{;`K{{qNmUAU™oQU[5z6KW(5V ç^b>Ee[|<<հ)iT$Al/S|"W.w]~P0}fXgK &>_ѫ';o. QD;|f2Oۅ6CZP2.B|mlmfy>Uu8AUꃍxwv;&U* Il w 0oH'> gDaU"AUQ <:~ڣ,qK,>?捗W?Z?[Fc@0 8*0]W}gЏ}s1 bM/tlq?/Tc8_{CzXt3Ca?ɇPuBOXrz1\J&~j[]* NHU8w}PO{ ,_p\66=h|=MAU2Bz.o ç›fi¬7->?عJ FV b,\pА %p^ eWFmqJn '{OQ2vO|alQ&OWN{e--oi7ggknW•s@q  :,߰n8kV;mz@z w'=n, T)b1!_7[]pTa0KIxdv`MRg'9V{o? LAN.&<`/n>D.cxKa` a &>+-_CY,E;u`Y$fn?_/㿲),wgPhݴϜM؀$Jw?2߿ػ%,R~[**-B$vUR$ KOCRn.^~s3s{sk̙33y~߹3g=&@ff7^זmq:޵C˽זt?Y'׼[[h*dZzkSMвF2ukK&PU#^Y:rI`&ə!$* 蘫~FC| a}شA˄E!Fޮk.j:R%Wԯ$=e^-@ pW)q*24Prf z1&xǨ%3ߊYy(9/ի ~jj׍JfBA s(&Q3Us5 ۮbre]?ƚ@-Ӻhjp|!ݵR#M0Z;̖š<w TU%P˻MQztڝtWޗ5x„`̯kѯi@U(h Cʙ's;- ξC(y)@gUUvSիo:3CH@U.x^!āV1]|>w,6nQͼTDh!Et}jzx~lXբ"~23qקW5rY,G1-Z_:i& R$hN7`2]MLypU֌{*UF. 2]k@U΂>z{8}5o٪L^7h(徨W2ncmaaSyS҂w p/VU7T@Uѡ\.uQﳪطA*Z=5Zz\1-LXΙA69CA쫖VCnbX 4᩼]\uefYZ1g릗df=C;Pճn3u>wvTUgzhnWy2]R TzXU3k 쵺y_**nT>K@ Ug"PudJpB0,ɺT8Ks1s^z@UoQFsPi/ԷM /'P%3Cժ.~8,ApTe Nde:6V)Pվ۵f_D$ T9U*ߣE ͪzgUjj릟իkůUϻ٥3+tرm.֖>[{(2SnϬݹ{UP~94٨ԭ+8<֝{^UP 2!x[MpqT >}^ Ma9\aɹU\C -T+hK:ֿ:_fb*v;*f@UjNlUeFsjG}gn0U-PC_{bMl =/յtJ+& |Xd8|!4mW*OwyU/>9_h>0+U}3ۀ,M7n{hTmV[/],zWҠn5Ѭ /%PocF+ebe>kVs)mWWUЯ{Ǵ8]uU&r-f  Pey[ՉMn i[ڜzbE?uxc2οHpRv%u6Q˶ޯ{U̡~'oF"g6:0sX6GUC㌯bT>ZUy?YTzfQilfQ95E FV0oէzi>zM]6ۃGx~ZӁ $޴LIzYg|982JR"z:(T7MP >V;#رzozm u+F:wujx5fKF*%/+Q*.'gv˞fjg2Dj@UV ]ޒZZ7=+;W+.h]J۝j/of`:Dgf|u~|>'U"v^[7Iu6K{v%ZVjklXatTE B{6A]CE %1vDxviJۅg)_93oI=|]3k?HXգgH4UW%q Tг(غ\jIǜ.e]svC޿{^CWw]}N~@eG{KTE{MIU750sXSuj7(ki|qLX$PUG !Ptt=ܩ*Y|E ;X#6Yl H\]9w ^7)jLVKC kC*du|8{5j8%?!w2bO5؄L`+͔oiͭ_S:0U]4֞ H^Q`Sff>g9ө^V/U%Vgif:;yJe= Dj˒1K6XYC70&lT+ڥi 7jH*jymzm\9kԷoMZ}R}wx8{6ÞzTI;ԣT< \f~bW +]d,۱@Tҙq|ʐzS(aاM*V9{eg3Jl LCrWu1ܳqpwpz*iǗk`Xgm=tBfZ7ustCVK:?|!m>uc9PFrdžk F"VI' $ER}L/+/[~ت/ԘϤ+Xk^:y^Cj@@'=q-:1[=؝E~c[-ө^QӡRԐafNͨ_ҩ^6]1]H߾]D;Ĭ?56֫/*  YlP Zzkc VtP/ڏrdu\_C{^~_y/&َzi/e'f| i5W]ͯZBլy}/%a[28ZAlF66-ztֆ:ia>~]&( p>4GV,4ʹŸC,>5M?&Lpa_dn'SSTyV_E0^]-YzEMn|U/66|ٯbM&"KTg|D.ژa^4 3͐Ze^W@ա^7]Wʊ:o^2[ONMn}J Пk_Iu,:5PUʮtL:h`FZ6/2V!Dʭa搙dT&AN37KhvڰL}e/WSJ--g<`SY՟C -^?T>QJC ulPOW=f#?jv`]Gt |!m"U&̶ZM]~вS}orM 9hSkVx^!ā5К< (I\ .瘀$H*Vė%Wn|)[USmލA+q;;88+٬OO3k"P4&jᚍ` \=m kgi|f*38rK^W/þ~ ^-/c{7U{vEٿuf fF9m'Evӧ]}X@-nrFA5㳲"U1z_Ul=4 *̨euwZ~rViݽt LfRhg/U&~[{jofyn:߶,%ARW9k&;!}.kW8$SzTۜ &@|)RW͙;XCV5VC%nt)R@ }= lnۓ<\ʿ:m83mV:FGW>Uv'\6ܑSc*WԱ5nR{&8nڣ[y)ݓB JUuz⾫Wyi̸Q*,I{CߓU:j@cxeA֫ K!ӵ^uy/ |MT=w(@k跀򻀷U?.@p޺^Vk_ުUު@jM@Ujoozkס-WYc-Puw_HO^RS#N^^%;7^GDHD|Y@=@5rz5_&Pe-$@ְz_#w [ee-$Pְw@I2t컎zWd-^tν@}m_HNJ}z]QX/S/:+beUvׇ!,.Tit쯑˭a2*kn![H/S/kn!*MHcu˾$kJcuOlB:VĶzŊ}z]QX/>,p&PeqM#c-^n T_#w 5_^Bְz_#w T5]&PeomG2^$YW2s%Pe_}b[ұ"v_^v'u+VĶzŊ}@u `6* nkn!rkؿL[Ha2FR/[Hʭa2*{ke>w&ZDw{/*"2>^"v_^v'u+VC TY\p_#w [eUBn 5rz5_^BUn { T[d-#LǾ먗}5I"LǾ{ TWtݗm2>^"v_9U: yy?? }>@}>TR#R;.mUrԩS'񃹮)ǥ.ؘd-Z8춉n綥z^8Ӈ@}>@N>>/nUb֖Rd?DdMtg=Ձs;;}}~8=M.'}>@JDE?J WNA*}>@}>@%ޕ_+3Qk__bӣ}>@彯y! ld@9!        i/@*         r$8G@@@@@@@@ P]@@@@@@@@pT9#        @ J.        8 @@@@@@@@@ T}@@@@@@@@U         Ҿ         *Gs@@@@@@@@H{Ui@@@@@@@@@G@#9                 #@ʑ@@@@@@@@^@Uw@@@@@@@@@ PHp        i/@*         r$8G@@@@@@@@ P]@@@@@@@@pT9#        @ J.        8 @@@@@@@@@ ]+F|IENDB`opentimelineio-0.18.1/docs/_static/.empty0000664000175000017500000000000015110656141016112 0ustar memeopentimelineio-0.18.1/docs/_static/spatial_coords_system.svg0000664000175000017500000000136415110656141022124 0ustar meme (X=0.0, Y=0.0) X Y opentimelineio-0.18.1/docs/_static/simple_cut_list.png0000664000175000017500000067071415110656141020711 0ustar memePNG  IHDR fQqX iCCPICC ProfileHWXS[RI(ޑ^ޥ%`BP+ ]D"( Q,, @EEY]&t}{N3Ϲs@Ɏ]P*'(3SRG(Q_LL2 \**\q:WɃ(&@XڍfH jBH".2)2l-eA f 3Kx380]N 7'ͅ>yy+!6O.Ord3ǰ\[rţsBf Cb%5þ`*-hU!J%n8$A?`π/Xb qN;XF C8]+Ϗ r"yeBG6(0n' 1\i$O1⫢py,VԨP+l aPօrҹZ| CdX2O1ʁ q΁O6@2-Ix@4/"h2fڀ h4"<8ƽqO<Bup8@b1Ds p-)pCitb̙ lAҿ7q q \N? 3q瓰nIwH{31>ĖaX;v`š'ҕ0:[[l~ y %+`U1*Z3ctFT}w≑l?7Y;Kp#l(B #`q.@Apep/ x!!4h! b8 n7D H d"DE Zd3A~E#gHrE7'CP7 Gѩh&:-FKЕh%Z@3eڃD0)bfa,,K20!6+*jkփ `q" \!xgf|ހ^|JtVB(!II(%T憐H$j͈L!fWOC$IdE"EؤBR)iG@V$AT\AO>I&?#+(+(x(D+pf+RحЬpEOaB1xQ)ٔEJJ>/EEECEwI|Ņ/(*~R-,zzF3RiYC:nKs Uz74b z+J ʦ,e|*ʷT***y*+T\TyJR5U T媖R=1,qѧFT3S UV+W;֩6>KJzaJMOt[>n\5}5ye4oh~bjjhjzk[jOҞMx9WձԉՙKCgHWO7X@wY= =_lz'|_0ՙ~\f%9hcb 6ii0lhf`#QzVAc}HƵwMLLL6753M2]jhL,Ԭج9|yu EVee+Ŋoժ˚`n-eC)հ]lhj k&Ojlk}bf7*4 MxNۜn;3#::qquԹnq;}{GB#zxx|>l"o ^;zi;{| |>>||{|Ye{o/?:t&n|dT4<'t!$':goHnR״_o ?ZV_wcce HƬƞa[=f'V,99rg2vS.\xۥ.:;NΆ+W_mu̵ۧk篇^|#F̈́oMs{;>~uVz\zNv<{t1''JҞV<VyKP_,x9}lϤϕ_,4 z$od-dKT4#7{ tK*(E?aL*.: a5j/@T. GY.z-$7(@<2232|.I;$[(mߔ pHYs%%IR$iTXtXML:com.adobe.xmp 2406 1054 jiDOT(iz&J@IDATxxU՝OߩNDţ@ #XAec\؇DƁ w)%ahDX%H{$t%0Q2%)p49Nrׄy>5|Ys^@@@@@@@@@nYfIC         _,>         @7 P͠4        Pg@@@@@@@@f @@@@@@@@@         ,@aV7         @a@@@@@@@@@(fPC@@@@@@@@(3        tY Js         Y|@@@@@@@@@n0Ai@@@@@@@@0         fu3(!        f@@@@@@@@@Y¬n9@@@@@@@@@,> @^~e}V]]][m        IIIw{Qe:ވ7A(ʺ?@@@@@@@.¬.F>@@@@@@@G x.¬.F ot       #0+L@ Pu9       K¬%y@V(j       X,@a#tpY#@@@@@@@@ Z¬h # @aV/sJ@@@@@@@zX¬y@= b?        Pe_Έ&@aJw@@@@@@@0f1@zY¬^NG@@H8zyQ.Fxԧ4o˴ӧOmh,<㷽op@@@Y&  @ Pս   `@ceFN_;1)wޘt\CmMZS@cUF?8a덓}~ @@@093 0   N?f 6DfM3Y*̊h[:@@@@G(VE:.@aVǭ8@@@1D} h%5W353yS||ƬVz[r>@@@zC¬P @YQw)ec-,rl   09' `Y&@@@@¬ SℍzO&28Kߞ>mf*5'u_?/ln7a򨎟Towˣ4ni:|''_˼iD~=Jmfuo͜K?_',_nOtƦfW5:r_Ue7t?=wFy#@@@(̺?tz_¬   X"¬B\j*vg*PU飵2k͎R +yiOizfMpL7=0UwHW?.|R[״4Yfiꮶ :ַiY]zkɫ;w~?Sg=ԓ[7F=Oճ_TVW"   0 fr8=   =.*2Y+F6U`z/[U48Ycx`&<ۡ&7>%wj oRժfFK&+#Fs6T;hO@@@(̺?tz_¬   X"Y &¬mfZtxY?}R~nO^K#`X9   @P+P   m Z5mNmPoeׯ0ۊ}    0 fr8=   =Vf-{tdwnkMj>H}o}+Y'` #׳oEQՑ}i})y/r    0 fr8=   =fUܝ9&Y' gkFǁy\VUm~}j/k--z0N7xZǏg"O}hϒ-:8o@@@@f98t 0ˎ<%       03ZY=J       YNPg@@@@@@@ P弜#L,F       t@¬ q Гf.m#       ;f;gEf)x       c(rL**@a#n@@@@@@@Z0u E¬m'ٺu9st[4sl{e=&9۞h|jϵIzζ'Z&_=sm퉖WO\l{e=&9۞h|jϵIzζ'Z&_=sm퉖WO\l{e zB6@NP 8'G(!W{B|%V>ڋ|'XWb壽hW{B|%V>ڋ|'XWb壽hW{B|%V>ڋ|'XWb壽hW{B|%V>ڋ|'XWb壽hW{B¬ (@a]IB|%`W/e]2ȗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`Wfٕ/E PeWR0%_v -|%`W/e]2ȗ]vE"_v -|%`W/e]2ȗ]vE"_v -Yvh@fٕT.Lɗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`W/e]2ȗ]vEKa]"ZpYv% Se]2ȗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`W/e]ReW(@a]I”|%`W/e]2ȗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`Wfٕ/E 80^GJ~[gE=0 =1Y-ϡ#/L=UII:-Pl˗oߢ{>/~Yzѱ|l׭||{_E?خo=}}s[+s/h'+({\١ccv1ԨwuŌZ% ͝ls±;&_ykiO>Vݕ+ҟ_g>YS'c8"_*.rlSXۨ{XScFsDb`;Q߽2fM-dNWͱU:ߚ &}Ccg|q}/G?'>)vpzKs-{+EܒN9螇jg8힇~{{Ηy8:_1{WGy˲wLH#6 凮7g8[it/SSSJUY(̊'(BH[Gwkjdhu qd^lO|.qOA09!_+2^Is֭0+'a,\n0a@1LI]#t[M͑ѧMzGIzj>ԌϨ2Y\d||NyUo;VE;n0zV=dNWw3Wkua 9MQ>u_mW(?mnhf雱L;j~E~.ߧח~V̩%#y*Y&X7n<3JbroYλac:2nzé>{NΗy86_|EC >w=ǝAaV&@z 80^E Fk87lW>d3fr^n,+)jèw5jkNޮイ\qD0Zl'\Ǐ6Z f\yKfwٱf)MĪLuBN{̿Q*wJT"E=N1ơimY hf+gsjikym~\=__s=o>yay؞ƍSy85_k|9S>t=+0OsNW/U%9J_ݫ6;⫳[>#,HML$CWkӫْmEaVb|{iw5yo#4Ì9}|S3DYGk{i{fȸq=gy</pjzéj~{Cg0H5 pY%CuR f]OVOב-o-tƅi|ӪJ:ϿsҲ%kl_aic[(9oiT ),jڐa0b3<|3M4}?jSU_ZDuʪV9b5'+438iJРw%/1'b&& }X-|Ű7V%YSz6ۯ0bzg>1ZuOL׫UZDyWhtRTgZO6g%J!g/^-3A`dq',KU:M~7$O]JrRS읣ʚ|o'+:563>{1C}tN<0'E./R6 ?G?3'[NW딾$8YP6qx33ؙ(DO 3f^:} r__y85_N|{N͗Sy85_ї,Na{yiy<(̊?iY
F%'j1?O3T FBI6If%Tԝ֞WN rT%R:W[=Y@3Va)"ԣԶLE#ӕR&жcsd:gz祜fyP g9 lvl6<~M*SstdbټȫseRWꃟ~VοYcQX|~7?Y3V[քBד6>ZWL̋}|<[[`y؞ƍSy85_Q${NW[?wl|9S_ߣq|}}ʕsyP5ϠG f+#c G(oU%9JU˵l55⌯x(|XUl~nbbSUa ˈ39KNN_a lNJ3n 8'_}zb&G23'Yܹ\5[e j9?7_DU)'9]fAe3mR$^ƍw)we:;KK*>Ny})eD!G\Sܻ6?._۞og4INW)+,Vrp5"I}NȗD2rffif|]:qOd6_;+ViLojBF<ly|c R|`ʚ|晙|e6}"w-ɉ#WhVM[Sf9ר鹝/Ggي`e^^ںQ󿥧_2fSfb|yj>Tg̀1K2Kё%5y2ˆVeCF\&jƑe?X O晞UNs;Zkq||C`k~{竵q{N͗9S{N͗Sy86_ ==_m_:Y@VJ匜#Pf/iU(%3.tb?l=WW`I4-NuJ2=_')WvDgY"%yj?7?|$eYٞ~7ude2w:k'+}m+%sm0ˁm/N]ROY˾Z *>c]9ϛT7i`oSz H|ٕaXo/wVOV_i󎘅.s|ӨWonYJnEwea8eWg||5֛߽|{/:%{N˗>ȷnC?i}4\5(6l8u&f?FWw>ǍǍ/'!}ߎz)_NAaV@]ɅYNt/Hȗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`W/e]ReW(@a]I”|%`W/e]2ȗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`Wfٕ/E PeWR0%_v -|%`W/e]2ȗ]vE"_v -|%`W/e]2ȗ]vE"_v -Yvh@fٕT.Lɗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`W/e]2ȗ]vEKa]"ZpYv% Se]2ȗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`W/e]ReW(@a]I”|%`W/e]2ȗ]vE"_v -|%`W/e]2ȗ]vE"_v -|%`Wfٕ/E Df9{t @@@@@@@^vs]wO@ F;B@@@@@@@@@RRjkk[l :*q @3_~YUWWl/@@@@@@@@f_Qw=s]Y]         @| ⻰@@@@@@@@Y]         @| ⻰@@@@@@@@Y]         @| ⻰@@@@@@@@Y]         @| ⻰@@@@@@@@Y]         @| ⻰@@@@@@@@Y] @ 4;m?t=D )~xr ӅzKM$W5DVNrIS<$uR'ΩPSnݟ'\@@@@@@Y8@:ݨA$W._!>g6}C|b[h=~Ul++҇S";Yw#~uhcyFM5Tiク+kKE=ZXpH.(ʳZа@@@@@@h!@aV 6 'PwbKi-]OT7gRf_~YKuaV϶?i/.QZ~Jgqm4hS5wKOu#BLS)[u(oL]rV/?|@@@@@@@(jM @/PA|)"wAv,+u9ZmӠ&MJK{O}{XfC|Yp|vch @@@@@@pYN*}B\~Rn4^[FL!}VaVݹA/_=qѳbEf*OWs⚨G6QT-UQcc gO>>w?KJ4u{8}WUm];fޟ3M/+%w75ЕiTr\%G9#/@@@@@@@|f9@zX;8˔TE_сMjb_Lˇ{.m[LMyD Z޳۔xo뉭5wCf_uJi+YmǕ]>]wƯ4{=$򉦋' Z^~<溣5h|% ,GԿisb6wRC|b5& jٕOW̳+g<)κxX}^m)<.:1]'/5[@u3u I]XME狱ᄞvB]+       p# Pu#g#@ DN_#oR ZZ2'T0+2F/X[9r8?R8;XPzˊ8WV{b+3#RSlvS Jb*3x9&~(;s ;ϊ qL_/?m>9^-L)[{%E}]@_M-^@3ٞž\)㭜 եm9LoLSrjKZvYYȊ,kmQreLϱK{/Wo@OVCy])X)RYZ> +7?4SŁPXiwh##֦9m@@@@@@@@^ =&ph1wyNƜl;O^SW[QؖV}»~FZ٦&.x =o[mЅ7xk 3_$lo`K-M.HCGqp^mަag#ͳ3E:7u&zcsvvk6Z:E2 Oƻ:i,:e5`CgC)wWɻ77]>U 1f=?;|GS@@@@@@@(CzL 2+gI=SQ4pfqf4ȵLTERp)>\Ӝzvf ,/LM."5frsXW*w{x+?ͻWL!v,+4Trrhoti.V} SR{'6=efzun[=<xanPi\   x'裏I9    A :a @",̌X4k"jy+3f ),jBtNC*]>:\*jvRUn]Qs_ f .a'@IDAT#TÍ.ޫ_@{ |'H۶PLW]gÁJ *]7[hm[^+ЊSwhUۯ~n{0+<ֲDfUhǢzEoVI[Ӛ`K@@@ Pu]9   tR¬Nq8 qHKcl%CZ/Vz I…Yb؈nYɜrLS kWԐ]]+V˂;pE5g?l,PmRgS6зԣy* 9|1zN詾.4d¬gt1xm#~-6:mhގYkɗFGάdpo_55]3Ԑ!->,OX6=j=hU!  ='@aV2   t @ \/|ʕ=,5{W0˿ -[k\;S>&L QmTQ-tι[g9x|_Xf/zfG_u5Y}C17{S/ 65;>n($9mtj+paY-v5{4Ux-9\ٱq^- , U+/>'   :u    @" ("&@)B+S,ZX=s{Z+T eZD s|TNgFp *;`εoQvZw@!W0˕] T{[96-P0%C1w4WEdkwUxCay]mIjP+[S[뭽xy Ceg>"oZwZzFG*~-ڊ%F.t61u.^0.oq8g C M}\.Ex۸|fm-1_@@@)@a\    Y2Yhh]AYC3ەݕ4hY/j=Ϲ4m#KKŧS}KE }uw싼Cf}K5E~-re:6k^aRz0KK g zn(ҍC+m^},-|R7~#2BLnje UaD-}yY09/}H1SW2 -]6H^]W6Zցu隲"iK3}QE{b   X3   @g+8@%^wpY@=?{Q~.oR٧Ζfr2 Bw V6,\oYhZ#֖D͐x˛]׿Lbm^7?NV{7Ϳ^wWpV&SnO.WoF².fUXN)z1\ŇʽAOS_Xfx[;GhGoSxƬ8jc&>g.=T+Fa5oYAh3d\t(4KY$B!  )YϹ@@@@=f2m@E=Ѻ+9I ujR_ 0@}TECɪ٧&?ኮ\fڐ& Ӑ>yO_<5\cka{C8*c U㛺ʿ:Q   0sF@@@YH@shGT|;{&th7-S;v"yo;Fu/i>Uݭ;ezN jLsW~@@[,G   8],g!@ \9bE]PED@@@ PQ)C@@@079'܀]9W.kQZ$   |F ># oG@@@0Gyi@@@@zJ¬s'nG=C    fz @@@@"¬6ݕoi)]9UF %uR 鶖i@@@M¬D     @:ZutL_\r.ס{꠺4ȵXJ+Pm"%ԉh@@@z]¬^O     @W:ZUSE XtWWNm0(i@@@0+SD     O¬+Uu[ɕ]W~ I:\O뿠i*_/w|-UtU~Bڨk[uhbJETޟL/\5a)@=2/V[YNP&4v8 t?=51|(m%'@@@P¬L !!    @f]wƯhC.wRV[%{Іj4-Ds\-ŧ *]7[({‚Cڴh_kYԨ]u Ji:jbJ .3    f%f^ @@@@h0T'x3Zisdž͙٬*ͽZۖY}Z y dUlizY)fҪʭ4jnഅ*YJWkK.,e-8DхYKi9ʼn57XP6}SCQz0/@ ?劝\<+_/@@@@(tN    ]¬*|J2]P n)̚v6.{8|RiaYmz<9ԔЏl]LW s^G,r-ӻW @kťµ?>%MsJ>X    (f%J&@@@@:%]Y.8wJ}~оຄ#YgVZ6)u@fb62*S8WN͌pi^\ek=:1%+#   @"PY @@@@@wfj|YRJփ+Jbbru_-fY3֢vgj0wZ4nH9h뇫6ȬjH[{$] @@@zM¬^     Y0)̊D54B/;р>fA =%0b`)YlƬ+*9k}5M],~_ҢύketJgGW蓊k[\m֌+:FS@@@H %ā    taVAv,+x:9s2MS*0+\, -}{ύ{`5h]_5q*6/]C-^<ܦh{Vo?    f%H"@@@@:'¬3 gNZ{HǗ&3c83fyU`\.**(l&M3 Vfd+/mZ#ߤWu,ֆzjxDa4Ө -#M{/QиkF3@@@ z"    ghaa7jþ@i,NYTB NuF=`VTq횧מӥWk*MjK5LdusHws[<Q[LQv,cyTWפIIтpGՅ LaV)Y]Ժi jupPUR#ܦP+f i[҃cUpwg^ӇlP/NЖE&W{n9]*>3=\s2O>.sJ%}%m=|D9=i*4K:1q)SBaǸT_u%4_JifF֝;}?.{6O޵.c-MW`1u6{]pțCx\9%G>?o^) =w,w~8'-7UeEg{O_ 1w͛= bvf[t\^Wp{(tث[0q h6ywg٧Bg(ͻp&:QmrY8g˜V* [s洷igCy|a;ͩk#1O8   N    ̘e: Rӆڲ,Ζ6O9ު/gsM(i޶lih!̠8:-;_G ?{uu-XVJwV4J@״[.}@p+q*Qkiՠm i,X."{u@-K(I%iww;IHb 9'٭=o29Z+C䪤PN~p=.dFXz=6RoGZ1#{5mT]V5U zfƦ=fHOǢr J-Y\b҃2;XȴS4C*/_cje,5Sjt*.'}'eOeF-l 2,2Ϭ*,q(lqo~&=nFZbi]Q־x> OU? -EWꆕ~EJ*t/( մq|9ijդS)ԁ懕&}U?qV?c85c'@@`,Ig@@@pO?h ĨAye@̨EyQ J6{ Vf\&?Oc,9SAYt-Q'G*ɑz8*ѹ͇ 7'm%Z JT#WF3=iń͇R#$XJ;BW(>T^i|dfy8i^8,}!@Nݻ5x7b|m>ze7QGNg\+' 7PP>vXj1-X  6 0bգ   / H@IP=pI^yF ).Eޡ`iW" =%B[{F,1bnqudmmm^楙Οʯ䶡*a{ fѬ5^UE>k%ڟIʼ@`4SۦO5-*A2}h'Y6<3ef&5> m) &z%L8Id̉Rl_& w*0Ee^ 6S!x兩, fAy  ܭ-=C@@@Y.T> @8U0LǖT]*Rُ=:N긱ObtlKjQESb)*26)'^YIW m xEnlV n޽;^GHg^MFpUK{^Miz@B?>^[W+H͌Bv8LOA1,A`0 YfW(5\Eiw|   3)%A@@@I2 px<'҆egifЂ*Q& =7׈/VpǏtgPFg}ZTKYVO?votF߰2km g\6D릋!W&lrGt˥P~ƴ;Y Za\5E{~}=[Yo+gݪ;z!5&~:8 @@@2^zIׯOk}si̙믿>m/@@@@3.d܌N!H }%aQڏ~]r#e?~<8#F5{^s ^ClڼԶѩ|ךҼh@{O#fsI$mahcsrj>TR۷G#f>W)~AlNɩ C^UhU5q+u>ap^5ևQzᆌ3k%ZqZ=)Fjkn6d+ڶD~=#  }?묳}Egݖ    Le @28U Pu+Uᗂr3Y`ƣ--urM0ɫ y36̒DYt2'9<$Y0>o+Nx QcM2k%]hK lJO}XXZ8څ_ܒ]ιNTU$pzv@@,8q?H>kt]t    @ @@i?*RQ|ڇR`i}2 $/;AsWY|CmCʼCQ{Mv^D:z\yOF+[U$i_+**mm<&-LDϛ[X!-}^!  +u:4u<@@@@"pV!<@@D96aQё"-zx̶G h^[ii&s~8ew`=4wЌwh1bѦ6t{1=av5#-bTAu\L?W_1Ǩ1ǘ4J_{D^X}(yԢ>Dwv<Ŷ-MlJܭGw\_/.~"G9̛j_@@@97곟l~f͚u @@@@3-@0Ls>@Z~O.]yjeE X~4zuU2\ԢQ74躆 *3։_~]pJiQ6,)U7x~E Xie罥y   p:}]}CǴ}K[ @@@@f.@@YOpYgk_-m;^    })@0/97 xQ A`G ˪f{\gF/]l~8:5Q35S@@/5a„۷OӖ@@@KY}Ϲ@,:rNmG/TőZt]iPY^"   L4Iz'ggJ@@@@NYv֍V#    0}Q}_|;ҥKG@@@'@0Մ!    )tƶ:r=Sj@@@@ :ޜ @@@@zIছn_#r@@@@f%GB@@@@3(qf͚uʩ@@@@ [!    @?xwc-zZFs@@@@`@@@@@@@@@f2(C@@@@@@@@f@z(zxvC@@@@@@@(0|p}ԝw1;"@1b&        DY=ӱ# t:k@@@@@@@<#c:vD. f t       cƌI`V'  u9       gJ`֙< @:a1        ̲x4 F       AYA #} @09%       Y`i fJ       '@0˾bpL`c;       Y @>    NZߚ=o5!Ӈ?rBDbIU4hbw_.G@@[kY}]Ώ   0P"G5_Ӛ;yEh;ݮ^wo6{6`Y5%   gF`֙q, @:a   #pbD?yZ}t]`5뷿Qe &#RIEZ2a~`V?(M@@@@ &@07 @@@qv>0K7?\=]K 0DhC_ƆLY ^O*Z@0C@@@gz^ @ 5J   @#*9Yk97j YE? m0NLj *PZmFԚ6 1a_U"    cY}\N   }},c`Uɩu`\X5Eđ#j;Zx(}a2iMٗNןbك    ^@ { ^@@@Sd6dbf)*[zk:|MyR3kUjFr̔[SUŷO㯦UZr]NfYvW(rwu&^f[IYfT,6    U`VV"gN`֙L   P Rӕ6;I.t'uL8)9}NZy،NZ$X`󜻴{r][y0rM_y6Y@@@zE`V0r@znǞ   ZUUl}939+/;}4ETwo^&psӼي X'Q0vӚ sҷy}C?W6()Ҟ-KF(Y̡,Sפ~@-f=kc&5lq0 eMN e3#eadx   @ ]Ot[`V@@@n DP[j'{͘PS?=El>19[Uy8ݝEovZ gSڸX`+x*,L%L%zPf&_J{^XܒN0֌{dg&ߜ Iф&     ЛzSc!= 4vA@@@'vެ;Kg9gj-YxrK]H%ւtS^.Vd/I[8bLUF̚fm*Ҵ;0?V]Ǵ "qy>b֖'u~tPBmٿR9~,q    @ ]Ot[`V@@@ Dt n٤&'x*wk l,i$wߍ3dHf+U6jֲGMf. )~al9DэyuvƈY\+^8Tt p @@@NQ@. 2"   Ы'O~/k^0,S4BĪ.OS훿ү߾]~=`nX,o̸T.z*,85jqL|.7}]ьWf9zfF]Kf#@@@n9 z[!   P̐V%2R9EeDe_Vjm` ] fE4vڲԱsoݦ>gI-W LW kEbȮSMexICtӃY9sVk:nL#6K-   ޳H @f@@@޲5{M"|`VFvI>eU[Uj˛+c%NߤST0ժMEt9͵yr3s5 '5!_j 3WrW    @fuM@)@0trl@@@.27N*?41D8*|2۲_sFVnFzPo>5TU[Ul\O' fIu4-̚YϜk.:ǂK?"*;=5EB2&g@@@zK`VoIr@zn   tEn XEwY]S'ξ:UEZ>1jS;RS:Ҍuwr+mؿVS9viڭk7]՚Ѯ3F'񳕈wi%nRI_~VXQOΟ]J3ݱ3Yfe8wZl@@@zM`VQr @gz^   tU }z^Ss#&?i϶u8T|Ü"޲D&O >EҢ5o?/8AUiܴeɭ|4:ût)= } cCce9zSΕZ۰|516U}YVUx-[j] @@@ދǾ @/D   @Y~n^H#ur*<2j 4YVm*L2N!J#Im*x+Ed: 3bV|f.KvjF3ׄR   @`VMYɱ@@@XDN=oѭ;ѩV}f_7AYJz1U)iKw< ծi ""-]ѾG5kCmrf,__c4#f:\e"6Z76pt"   { ^x# #   MGj>g$B@IDATތo5 >PWהU‚ U?w^:=0k=,sTxmVWԫnStB詝?u/KGb\Wz|$z,JX]s^M;[i_s6h7tKa>w<uro^vMekCX'>ŅzsܚZf~1?\nkW51E~NW_Nh KlWg]so57^=Z2?8Aڛ~Q7wѺfyx DlטȌ"m>ٷU_u%\[-}t:OejU|Wֲ뎇L=591J&*GS&o6+:Tn~HKڢœW$*ŠsFjgx`n5) Է6GU4n׮XJzLusڛfС:Zu;2?D DN@](YXcF>=|2W "7 WބFCSokD3J:/Ww?|̑gյߍOd(Quu&hF3~~ݺ|v)Ҟ-K B[/7yXs3Ȧlo<.-~6`ք/[^]~=gPerzxzyzusò콿χ  p+ժMEtr&$25: `#zsm^Cg=ٺ{_luЌD0+TY! G٢Sx@o}ԉ\N3#2Vɩ7б9Vth&* 2o[<5ko}/7 쥆8q}ثC5.޲\lwMt\DЉz?ԿYSox8ͥzտ|XQ[YfOUkuvOB߽ڏP\1 l=~}/CX/>njFLc~~;!1O?MbU\W]U2^1}*7KQ6F fwzuq ~s鞇r񞇳r ~CL6][MG&M՜/}!6RLi{k }&BsVo}H۷[5䃗V9*B^*#&l~cTsx9>:bV3j"'ųرz~ GbUu^*LxVokm@RF7vzu3m]ݮkZ9B \d5sz}ys,y^WyZ5y[/7yZ/k<\Wat+ p3%3C]T ;͖m즵;45SzF-Z0ZlXL\mB~_\"ͼ{|ުJݿ`ho\W_c⃺y: BԤ['QWt4XՕznz@H m~7N._mFf>2>(Tɺ0bVg>u0˕{} pzzz{{+ť{+^>wσ`VJ9 *n[̿݌ru{G qN;7[\ױ48z_ 5x+P{׬rƎ P "3AO9Rm'Mڧ逶"g)]PBYgUܮY+2TgHgx] QWi vTC5h^X2 OuRRQ3=̊S/VfdfdсGvh[u0H,͓S/K=rP/UcX|3:R3"Xy~7>Bg0?*ϭi?O^i2/]~{vZy^WyZ/gT tzus{{+u}GqzuЯ;<fD!}"V0UL<2ʹkm7>褿;ْ\Wg\tdX`͟m|fzUK٭{N3_*~Tڪ"M:yGlqaRM,חjWZk+-&6 fc{GkMؾjbׅ+a\k\{UYFMndw-4򷏛9Ks= fl j߭u?>ztuNP*3]fBe3ȂU)^ƭuwiwgr"T6s֘L+l0r\WwGuܮ^q_g m&.y^WyZ/WyZ/WyZ/WyZ78YDGьL{i۹ȝz=:׌Vl.9fjS[jGWuC惣x:PH;6s]]chw_ed 5ӆ>yqV^7NMvdg&߼|v֭mw:ܭW%q]ӷlW[vy^WyZu=W=W=g=CyJ\|AJ`Zhdk[f_A'͖|LNQƛQe2C37IEMokэqۉzET$caSun넙"4$3yh2?$Y9 ^~7:2c]2w:ӱ .OFvVvvrq́{\_.p^r~Kt힇Y=Y_@ fȑZ<_O'r1) TGqN=ye|JRDcz+[s^:qPeX5_EcOu"3'V383J?zzVZ.CZ%׹ʊjs}EQ}mCi?[~c`*~ASܩT[ixLP1.+ڷ#{-S,w6gV&O쯶3jSEɿhE" =Aݘ6MiVƭ]Q׽3E_ݮW5]f{ҧLvq==%y]/y^>?\A0˯*_@>p1S8? :G#GxItRq®+zR'&#cMg Y_]XZRPl'F \":q斥4t %s^ɞz4{D_tz+z|>(:.<χ6Q?isPv}vNWw~ہPdd=P*z^{^.oG}~T/yK @p9bmG"^v Z/e]^v Z/e]^v Z/e]^v Z/e]%eWh-8(@0ˮz%`Wk]vz%`Wk]vz%`Wk]vz%`Wk]vz%`Wk fU/Z ̲|0^v Z/e]^v Z/e]^v Z/e]^v Z/e]^v ZYvՋ"**L]vz%`Wk]vz%`Wk]vz%`Wk]vz%`Wk]v`] ,Se]^v Z/e]^v Z/e]^v Z/e]^v Z/e]%eWh-8( f9=       <g{wOˎ  һkG       >|-Y]b;@ C__TRR?kx       6 s9*..֝wn1;"        fewa)         cY=cG@@@@@@@@@ .,E@@@@@@@@z,@0t        d ݅         @f@@@@@@@@@@@@@@@@@ӱ#         ]`Vv"        = c:vD@ܮ99y~_~k/ϾK@@@@@@@\`V  yd._J"TM7=+KrP^_QYb!       @_  p~@ߢВeF-lx$]^7,V--       ЗRs# H"E:֘v&`V/@@@@@@@~#@0ߔ Mzc+a iHG'(w4>]~Ct^=[7o.grդ1I"}Zu&^5tvk_kBJ!"kUگK?m3I Zݣm?Dۨ!`l^ˢh0}+zu5ڥ.jЧO0MUG>T,S/ wC >O^upiy[?iXfyDۿ~4l0gMbLk;;T>1entMbh+zwqtf|X9ת991Ռd*Hya%uir%^?kH=tRZ @@@@@@@`  p&kmsTq皗"K5mY+٬^O-K5 Z6)N2{yd._rKk}$5yD#._n>U*Zy%v\]{oU.*ۥAF;J5|՟z~_TZ{|%Z]u0 zXS~J!\.|t\ݰܐ¯NJD-׾ϺrA@n2ޗ!t=?ZTnW~5ol*ocF=|]8Nn @@@@@@l8~3bӊuxLk57jL Sn>6ߏ䩴+WK\"WV '7{Ghe f< fFw9R}?ЭyX]1S}2+L)R?5#43^uYT%Yֳ_,\J%Yn^B-KpRW=fD~Z[r5nr,IѬ]vb@} >km_\uq<*QA-|i2WTLXJ-YL6]n;)#      Y p\yg[+GKbpL^uY*;WVXC}-(Ҏv+T9vC65VXWcw?D_Pkv 鲨pU&۲+jh~V&{t[q}5-4j/{nY|]`͞^mےH;Pۼ@3%53#aװ4^cRp~]lKw:+U{35b8=uZM        01k ;fe,2KhOkew;uj cDM5mPuF>1بL5fBxĬp_>iIHZ!UD[zK3_눿ע/Jߟ>BK^rDҠN̴qqlبԱ:~1+0U|lD"-M ]>?:zU).Khkך4?XHOoaTjѭ@,ˠAЕ+͸Yҏ/6S'Fq؈kk4 7= *MWl/D8QA Nߛv@@@@@@pI`Kդ/ F_{oFB*( $ D2&|c.\b[Tq@ݳ`神 %ϫE:`L9 SF '[?Kv/Sb}Yky~Uv p|̖fj[Ԃ%\$YEf*ձMk1aۅ[eykluQ,fʺi`a`Y͇]:d#f}τ] ̞|2-;Nfڳ~g?K@@@@@@@f_CzDtz/P6_kY3w/ۡљTg5Uu+:S^|40Ni[a7ٝm0&XAhyC?]U~fz35?jWa!=|h5Lo8LF@'UJJk4`Q*PdrHE*]Ǒ01ZAYX.9~iF`}Lj5ԟ0y.Nѵb]~S@@@@@@@- h$ϤamhJs|"v^ϫC'Wyky]b6FpYAŎԸ+^J)ڎd-yK^yq:{       vȎfJ@F` MEA\* gn>yw'A $JJ (P[! 2lj8^@RWO^$2)_kG.Up[y@Tx Q+3+J/Z 1#y ^Y"'C^csxW(e @jgUa=&USlϢ񓚀\42ŧku2G<(YX}L;QPj50YW%#)ΩN'       `*Cm 8hû4:0)P8,3#a⑫mGjy%}^WlkLmR偟릜{ufz%!s9vq 6=3n\W-cfNҊ+tÚyB漯^rR'|֔"1s*H݋|q~jv~*3^8:^tJ.L0Rg/ |+W/+.8/`cj25JvY]˰if\+sKհ}R4xTf[Fhsi}h3t'PXƇo;S>Ɲo|E@@@@@@Υ{@t FJdFR+FъhWy*samϲhJ%U^ue|t2Ĭ 7FJ;yZ3ᮊ(^s癑=όȔ,c*ho+mPW+KN2ɑ39TArX^颼_2qj80*o'|48apĴkrĬV'GZSڼʒ]~WM=;ż@@@@@@@#f\@΄@Kq555Ki>46USGZxCa/Шo&\m'zhm4aUXNUnoqSSSsM]x֣zq@@@@@@@ ;@ )`ܛm܋\ᓲp_PUHLJyzqU`YKtbJ{Q@@@@@@@, .Pq1 [[[51oӛc:u~ڈ\׮  T42 ZfnخF       ] %ApT^WA@7s |Ih,"      X)@0ʲh@4W#:vNJuGuS8        50L/@@@@@@@@@ :؜ @@@@@@@@Qgz        gP`T         00f :K@@@@x'c}۝B@@@f_Cz    kbO: O@@@@ u     Ё`X   B`V(@@@@@ #    u&9    ^@    pfT    _`7     sY=cO@@@@CY}ϩ@@@@NI     fǪ&@@@f|E@@@@fYU.    5JN@@@@pC`u   *@0/@@@@ x    ̲4@@@@*@0kV~#   `,;D+@@@@@ C`V/@@@@_ W1     U^f?u?*]2C眢-zcy7)gxts =1o Sn7hyC+:R5kRD6*r~ꆕo){8PYX.,#O@@@pW`g    8-=h˥P^_Q5#dW"u7˖- ќ33g`mv3 Yl_\R]z+[sBޤ_T&W:J7 ̘̌5}_}UCmmKɬY=5jR/tnp>\7UVKtasF+][֌ AE[ bXrU\}𞪎^h!_f-mOO}1[_3Dh^ E0aדfb 'Ռ    X`V7.>CG@@@@f;{2+ &M#4⊘e{eAE^WZu\ ,Qؠ_9fۏmfj^BKE ҊH0k7Gђ[4;h{f8ʗ$I߲ *Ewf^;f(yyZa\LM7e#ħS| t`oo)w3b1cVR]6"  8.@03<@@@@\hS0'K/hҍUe V2f,Y3#\Yyz|ʍRʝYڞSjfz͎d[.MH(lRMʺLY fyHG̝Bhdz4zTYXEV^i^}`yE/vUX-\hkIUf zSwm-x@@@fPEƀ    @7hK0z ]7%*y\3f j_DW, >;c/EY9ŕZ;&oWߺGG^NAA-H0+bC@@@fu,"    @ %-eSbf w;VW/ػnWߏ2bvng0+r3SZ:Q71k-F `2W3d33{̙Sfy> `VȁE@@@3fu.m#    @ %-e:QYuu]0 'ؿqh>}"@,8ZͿjGnPΒ*)T^}@U@3zfeF93fEp    u~9*    @,{OhTe'|0G?҇JY~3f]L/<5M}{PL*pk5egv)o V)hyfZ̘`    u>96    [-,^e"z,mD̝HH*%Y[0`tzsQ=^VVG3Y^?eR3*Y_@@@8_ΗۻͯniD@@@ f{    $     &Ҥt@@@@N`y7   th     A:f@@@@Sfu +"    @g laG@@@s u.z@@@@ΛFρ@@@@ ڀ.     ~ү&@@@C@@@@fYT,   @7 ΐ@@@@pA` Ud    +@022@@@@x7f͚g? >׾}ҤI@@@jY]-@VNիc͓VfV_`fߥۇ\aX:Wku28SEm?F\GNP:tQхue^lh{4rMGF|:T]GǼUvP.mAS   կ4x`]p>>ڻw>ϧE   t_YݷHezvC>3ۚ}{Fڠc.֜-h&q06Ұk.aU=x ".J\(W3w*eN{iݼTWjjx˚7MWm k~F$ySݬ9wުOxA30q3@@@aÆiϞ=YC zڵ+)#   ].@09 t7]&ꆙ.ީͳa}4eE7 f}Lg͔rJTv;Л5"Rj\&tL`Xl)ML)NWTDyk , t"e5;|C9EhgZ K fxOc.-9 4o~% Jz@@lxo?쳚5kV$    Bc @~n ;Cf;3ٷz5Ҷn/mY f tTO^_٠ +Puu jլY> cI`ڪm[1ٱj^B,pØ|X]vYو   tX ЭNԼ݇4ueٻ> &HR~>jD7>'jߵeOo(z٦FiБʸ$N{Tݨ>m^s_E}ctDh{Zq~wIzW_ѥ:H%{[eCWOc5fU="w̱S#;tnp>qiԎ`ϸU.F ǘ׿WlQ/e# -pmygwjj~~A}e =Uc|՛>}ԧW?]l}u̐!3ow+0&ߧ؜P_']=b2 iY@ k_{MlV98>WsxW= 0hݱKu6kjY҄v -25_ 1Y0넖L3tYًk#i8U'i   }ZfM N4IWvl @@@sCCo7ǫgWl*gGo,ͬ|}bULxN#14OVAaV)'?I?<@@@@Λ@$ A9%U1C4DgY[7{Λ񧾪$J3ny:ad֪<oZFd榎n@7;WoZkLTU4aL`&7#UHRލu1 3$(?S9š3yvQ㜒Ѷet_~lv3ny4l;=hmoSGfY3?7Q-Gle^x$k7VYâVƳ   `y\pAw6n    @: (:C_@`R4eL$4!} D.9(p(;2 ¡m_^N12x3gCfy;o)"fWx Ik0fӮ(+󿱳.fp5?t0LbhK _ y2c8U f%.X %/u3LWwr:Ȓ@rD4pH+bE߷V >&%*s MY q4?%XP#  ޽{#1w6n    @: JjpR`Y-aO@UNYhډ]NuqD5y)KoFs fe-'*6)'oKr#!"ɂY@+&h,"~.Ld<΄ՙ1$,,(yUVz7Jy3Y&t"3HP5IP,6R,VY&/q.2Lcci>@@@ C?    tAC=afBKE.IK~8N]/kAů"MɣSuosyZF\IHY#ddaxGN1ҍǩw`Zfyڍ|Mw^~~c 7Y`KU6[r0|T_Z f^xʿğ2eU5).a؎  v <쳑Y!   &@0+*B@9/$  45.I^J~ctL; 3F9?u*_}k}qy}-sf5sJMU#"KytEݿy=RϘ|H33"P[Q [-zG2W$VU3tRKFu~J "rUΌm _vZm+3O2EWou1Z{8  V ;vߣG@@@@t n? @$8UR3@͟@U AEY39 Wj-JOθm1 roɹ]fK7;SpUmH=hL]G/u1m f f ;OiCM>/.?҆WW/ ;gV&4љW I2]x1"? 57jwn1ٛNTY&105h,/11 }k#@e??;tRe{Ы&/zw #:UUt3@sRhJ0@y~(h)~c$") B6 G@@[~qC@@@Q`V:V>!SUR}&dgwn\$ؓ$vLFAL*0y`L} -:RWx3uyɎi YBؐS~IHJb+^?)*5KzY^L_6EM-4ٓG*aCVv\8M9&jE74iJ0+vȐ}?;<;W轴_=$@@@a @@@HGYXN 9L " 4Ap8dgll%03& xEH9%Uvc/? {Ya|'1U%N+K 훕W(rdlIv44sRמ:/NX/+,Tq)0kVYK{ڳöN7DC9fO :n:ݷU꛲)qvA2NcQOیF3.F/f4x1iNưKusL[?Z=Nq6/le 't+:e?s6덺ŏogyySt}w       pf'x \tsO$ےS|pjV5U*VH|bX`U环MdcI'rYUGCN/?ewg$yN׬VkrMg t"e[.ˎAK@@@@@@e4  5ZrW+lRSߡ>&u[ͻ;npOjJ-ЊŕM_-GͬYfM&aPnyN# ;"      u+FHH+O%*q+ bSMK#1K#^{(#      4- BAt13RϿg?wہ3AnЉSz֥}`6ԁ>"      '@0+jB@@@@@@@@@rY#        @ J#@@@@@@@@\`         ~ү&@@@@@@@@, ey>         =BK~hlI&       Eo߾Gy-'݇`VR6" C_[ߑ=@@@@@@@@:@8&n:^] .@@@@@@@~G0t@l0s0~@@@@@@@8p`d"AN`VYs$@@@@@@@J`VWIs@ 0lF@@@@@@@bY#ܨ#@@@@@@@@ V`V@ @0W^}IM>̀s q4@@@ ~!4/C@@F?4H6kScvr \7A-psi߯ .j5 mx>O<%j :!   нfuz3ZHCYiX   &dY; [̊ƪ|lz=񝇵:y&}^˓?m5Q~ l\ S/n    ,k@ fy   @4"fS:\燜Υwֹ}>DrNuo"Z>%i8#H<챭vӡ̱>O8#   p#*@0\y=   >|{ڣG.}HPVٕl^?o]jګk'f Hy Rϓiep9Гc):}ۻE6hF`*YqL<@@@E$t}C@@ |Nˉٕ&f UueLLewiJoÑ*Z1k vD5.zynb`̊ w@@@TmƊi)@0+-B@@@P .jI[=z_+/7 iǖ_W{]xݝ3Tߤ=G3|sk`VnMjTRNO/Í<0&lmcuS/r.j`ZfK*s+G1_mսU nڡ#0-5-ՠ cp՚&C@@@ H!+@0s@@@8`Vc [:اu-u !J U,Z2t9sR?SދiҎʝE >ȜP?ʈZf]a46Vkkh>TSk̎޾~7ʾ4| 8iz)KtVsiWjf&5<.5̔U晲.}H>i>\|g`Afhefgf2{uDѣߋ)ƿG   #@0˝Z2T`   &j%4jM^TNJ5C&04ٔRfF"3b]N˧Z׎F..3W44,\fyA[K4&k e n򅡎Qe p.k")~Gw܋3X3uDC4wVjl@@@@Ya =G@@vGk:[Qg 77Lܱ<_ɪ`VTW$ vTuҊrEf9`ֆ"b\(k6[̸4Yp܉m_w1*7y^+6m$1)nYx@@@iYN! l}D@@LF]4*~HW}MMk{Wȝc{UpJk4shlW{ͬ^[k_01iI:D0+   tYݧ֌T`Vn!  t@Yci)f䷦Z\:h+>X5v:-28nߖĿ>پڬ%͞hp]2 |oEL2R4um9iV.kk/ݱ겇4Λ2+sYqN%^&VjJpŎhyB[fa;   @w ՝X@ -feY   @ tx0F׎7䥛U|@ɧ 'L&-ՇKrHu- `Xk6|DbYfkpx:%*(+ඳ+tƽe4+S/ٷnK}+Ͳ“kEhcFx;jF@@@n(@0!#@z Jz@@@`V^=4hBdE?Ԕ$ W prlhv}FGY#ڮUuysf U٨YL0k}&4 iT^\5o-MʿvIF-ת9Ûgf`f EϤER^g:fG m    J`V*7Et UO   ])ѤEdJRM*hg w Z>%34&51m}~,X^wƚ,`i؎ - '𿓟ׇEݫٽO}y+ZKSƤNVAaY6~ÎhYYT؆   fu3^H;YiW:   ̒Y0,EM'e3vSׇSoj^7OVt Lׯn{I{fykf櫡Zfؒ*ivL澶CsF]hc;t\EF&]{F_3.̔hf;@; 7Y 06yef23b Έ:p7n⤥$'#v[QK>NȬk5'ghK%vDm.נqBZS븏   K\&cA+fYY6:   `@jvѯ`{^I}6q lnRmotR݀+z߯ub ^ unMޣ}H^Agelۭ#hۑ @@@ @0;T1"@Z J9@@@@@@@%@0]l8YgIK       t@n+@0ۖ#      8,@024C`u       b_@fu*M"       pfpx@`@@@@@@@ ^MX&@0˲]@@@@@@@ @0 HtW\lt8-vpNmzu*o7N:S^S'WvxԫI;Aթ8pNmzu*o7N:S^S'WvxԫI;Aթ8pNm`V8 к֍i{'؝-BzI+M [+Ln^iZݢ^)`t3J¤JW&EW 4LҴ0)ERfꕦI-&M7S4-LnQ0iziaRtzIҴ0t ,je]^v [/e]^v [/e]^v [/e]^v [/e]%eW-8(@0ˮz%`Wo9]vz%`Wo9]vz%`Wo9]vz%`Wo9]vz%`Wo fU/z ̲|0^v [/e]^v [/e]^v [/e]^v [/e]^v [YvՋ"**L]vz%`Wo9]vz%`Wo9]vz%`Wo9]vz%`Wo9]v`] ,Se]^v [/e]^v [/e]^v [/e]^v [/e]%eW-8(f0Q;*^ʟ?4n{~dѧ^٪97Bw^czw ٸ-xng4ʜ[n#U{~FB5h-޻5^V&Y]WP\YVES=egSsuW/ŝMMohm.S+0"aUZmj8qB?jҨ ;NԫV%k[47 ߘƝyVΉzɟ֊ӄ߽r'd.իn[*:L>}C](T=i}Zݸv^J/PCSn=SӨ ;t^|q㚇 @c^?a}Z9o\z"wttcZss gGɽ-2iSyuxF}w p9X|nոW `jHoǛkcwӦR_TMmNDoRT] 3aؿj]WulkfA3_!/Z~vT|''~(9);4Pu;~QM&ise8P2'ίcß4=z֑j<||v0ˍJ4@.s^{a짦{ z|l\h"S- ľ]8|u&T<**PYOjMcRS3G9lW~7>iBY؂fBf6Ecfvmi]zyzDd~UZgYc~r횇jCGy8[U+<\<ܭjYMZ_p6YfB"; r &-ՇKrjs8L q_lgffUmfa"L*]y+6詥>?Čg._{͌#Y}tx t]vɅzE d>q{FN+W`V,O[&,miz(k6fՎ steMݎGgt$N+>]_[0AUgRG5Ё]`l.ի'5isǂ玚|1SO{R n{W|ZL`]wZzyiw&3c絁;ϴ]gZױ*"S?TnF/jjEhK]*鬍 7U\ʼf[``{lh>23f򘆛wr1A6Ӕ̘HT.ko hf-ɈB4f^T{?F}M]&mr:Ws寧k~z|ysp`{#,*ML$#apUP6?, mS.Y%Ƒ6hћ7Ì5e:-:8Z{m/eiz@Ss|zq隇rrrJ5CyJ<yt,jb זּ-[Zz5hE'G}E+,Wr^lXJflBX\Wuy[IOmВ\6_`Vr{gx}u^u&8Vϯ6!:w4@Du*k8ܚ ft GUܭOkOo4Gj{ zoV P8j83`^ܱN]i'Y?OhNV{PnӴv| 9q~j, oM[ųnQԳspl 4{˙ +dGY ]ͫKkȬuMϯ̲jy55g5W5W ̡k׫χ] w&z7Y̘-[}L }ǪT&}r=1uh\W`\cڱ)7yD!`.ƚ 3[fm!}$GUgzJ. mKC?8|jr^Mffk3۴XpfH÷{5a6ze9Ϡ;JUn6#iT ZGdqaݻuI/<|mk)7?LL0+6F|?hN^YE9立v\ίYr^6%7wܩ/SUmШؓ,#е1s{T-۪Bvh~rnΌ-T YP,$ffA۪JqS]l],svmhdJ S?yr3bcQtjS5qtl{BϮZoǜ2]^WyZ/WyZ/WyZ/WyZ<Ǯy^?zݹA0+r&r8?n>%˭P{GM^ܪ 8?pT7>CܩI?yYzsw 5AсUxoPDj$s^A`O3ݺ7k53<.w7 :X{ mU-߶(܅f{?$:؅zg 쿲Usn}|L>cV~-IO'7+>}f9ט&ܩףي4,mTdi&[mhf+gf+.W2F=hN>k >(}X M̲5fИO#v\׶+ecZOJ?R5~td{B?wz:o\j睃<\<\']A0+Tq8o-9 iz퇏iĸA'زn[s^E`1 ˭R/VhZ{ΔQNCr^OZ34{Fiʸ*xqfF(+ފO4y=>g|RPikGWܩTSnxgVs(XR*x͜Z*{Oՙz5ժl4? `-ohSuzqv-VPE1n+Z/^%]a{Z\&YǚAիk_g.}Zkɓ6,ޟ\0R2w\WStwEWZ|M'C ]r|>Cg,^ҵƷz߷;+;Y~ͣ;׃Pe5P/ytzuܷRw <fS @wp9bmG"^v [/e]^v [/e]^v [/e]^v [/e]%eW-8(@0ˮz%`Wo9]vz%`Wo9]vz%`Wo9]vz%`Wo9]vz%`Wo fU/z ̲|0^v [/e]^v [/e]^v [/e]^v [/e]^v [YvՋ"**L]vz%`Wo9]vz%`Wo9]vz%`Wo9]vz%`Wo9]v`] ,Se]^v [/e]^v [/e]^v [/e]^v [/e]%eW-8(@0ˮz%`Wo9]vz%`Wo9]vz%`Wo9]vz%`Wo9]vz%`Wo fU/z CB@@@@@@@ v\`^W_wjjj @@@@@@@ WͶuJ 3{!       ,e}#ީ҅liR>})k_5 6׭u&4]?r_UKhFPl5ڸE{~u#5:i _[HWd~)cUtrk1[~IYl5u9bx]Э{$vtjӸz@@@sfu/#   :7?^,аK\|$+Keə^ɧ/$;gZ e5J*y\ń̫w-lQvNm=L 映ODE[^lt6?yn}"qlײ1ᠣ둯|YḴoYŢ;ry=o,9t:Ei{f0+__ܝ7N)Ky9UZdHU|^ݱYYbU{fo>?SU4[+tWφ5/ƒ4D ף)MΈ&Ц{5`R{ƁU=;Ÿ#@@@Sfu*/#   9 :G@^036ͿALy%x:M}, Ԭkx*bVCP9%;v0s }75uE9*qٻ ;?R6i{׮yP<1Sc3d!20;ݐ-#^R28oJWn^ 5䡬T%b m45%~v^=ou^FMԛ+j}ྺΟϕ=%yǭ29d}]nX$!wi[b%oͿ#zȗg?{蝶Q$ F dcr70k meEimN>nO"le֩z{+4   f00#   (^ +$PV(}VXCӣ|EՒu%R5*IDe51,R&ZOKfWJ+?c*LXxj*a)^3YDT55XˢT?n]GSU_w\bɆj9,c~yI3GNCGUĮ{8c.N]DcԈ~txJG P7ə _y,mW Zg,ETjizzhz:KuNQ@@@V\;T@@@@(@YW @yywX{NɿkǠΝd%,lwcև4]WʫrM{eJ+zBYrYRE'ν{:`dQe}\xU^}woϊrC`I6͝۶Q/cFqmV)OYެ;^+Fx sRzi9p*''d;?#wޥ{Š!O&^BR>/wh,g*wed59lt fmi&yuYSlΛ_̐:qA^=Sq?*zA{ kH4'\|ys{뷙XlӃZN??+Soo9F@@ǬnX@@@VS&/;+g-7!ƏރA4rLBt弄oЅqbq9MK Ma$'_ɸd d;|b"ߌnҝ1^nQ<fzsrv|}[o2/w&}e)7]707/sy9?iW(O y-y6r,TO~ /()O^zff3-;0 *[y)c֋:h!M+5]vni[%R.6]>wQStX? x">o'=hYCZE3%yih_SW^tj=gѻV}˕'g&@@@Ꚍ @@@@`5/@UDDhtX&JOD>m&|*5=tgJ2s{=|]Ւ56VJspFo龪|rXSDsj*D{߫VRzA𶢆%vBI/]y6UV'Z-Z.^Da!_T2d2MUr.5^6DAKu*eRe,Ye2˪Fq%2\8tb @@@;/@@@@`= z,eB67 &JnHt)DKnp.[=5596I(e(y֏x 'T:jOt/:gJ$\R>e t#|'#d^MW%15T/G?n]V@n.hIVu +݋⹜9AK}YJU  脪T2=212wi/a\ŜSXvTj!N.60K8YH8^oOzѴ<;VTv0^=i8RMN]I @@@`=0<ʎXvmPUcaԸsoVcSWT&Q瞼mй {\~9< \*T0MUUHF6O͘    0kXC`<o vKfPQX-6Up{euC*6꘍vw,#hkWư vT%#( 4U7SF'V)UyZL5#'&Z/GNiz~8mwvY,0eFv`YC<ֽ[آn]E   .܁Ybڼ -~gܧٽa'vp ٴI.<$y^?䛖c͗GmW_f~;FzlY o$  =#|^_}YuhfJ})ALx L,q' ̚{G2va~\Ƨ* 釠rB@@gySw@fԓḌ ?-$hw    @Gf_#X|Œgoea~PEЅYzP4)O]假6 ޴z 7ܿK ? mˣ9yi'0\$?|PbV1KDqch&%OHpI^>[3$3"z}s=Y ̚mmc9v`Ru%0_@@@~ 0?@@fej_JjzߺweІ!  ON.<${aYbY=[{Tn<[IKޟaOa̙'e]ۨ+/A7yޅG%5'+瞓x̢E`V4U)ҩ!.)Lw_7tX՗d,Ƽ|]'oIXH( Z=z@"PbD?uXǂ#GuI   Зfn    YsrW r+0#|EJ'yLkftp Y}yV0<-; $'϶_yy!eCJ+3Od9m3Ŋ@{ފ^x*#rrq]:}`vS=PMaX{F1bvY ּA@@_wSs@@@@zZ,;8)6'+?geetHպǬMNpT/xT>{ JNN7Wn~rߧv4\ݓגf;C$;Qw=w]=f^j.{.`+{@@@zXy@@@@~&0+{:-IyVȴ#ɼouQOWT2*5vqJW"0@@@6YqR'@@@@@U@HN:YerFv5'/޿U;K8U3wg0    0k\    F 0kuܜT=Y & 27Wg2&B@@@ֻY}Q>@@@@ 0+    NZ';b     @wfu    f7!    2 L$   +"@`֊(    Y+-L    p5f]"    fd         0kJ   YS     Cf΢   Y}ө2    A   Wo    FRP/}KJ믿^n07    j     ] dYFmwI9rHG    Rf,"    TUٱcGSY ޲fffd``    d    K׾&w7M`Ї_ ˙   YM^     dzK>O7ߔo:,D@@@VCP&@@@@Xݻw/~ QJ5w5G>tR|    k%@`Zɓ/    t-o}KO4p??o|˘   Y-N~     dw}Wvc˗o^rl   ,Y˩IZ     wuׇ>!g>####+7     թ!    fFrI9rH<    k)@`Z7    t-PVeǎ׷effFN @@@@ 0kdI@@@@VLk_|;ߩկ~U^xˋ@@@@5A@@@@5x뭷ӟt o~kZ2G@@@fEx    =!{z9/]奐    _f    lo}[|0u"    q8     Wn7|s_՛"   @oR"        @ C;"        @oR"        @ C;"        @oR"        @ C;"        @oR"        @ C;"        @oR"        @ C;"        @oR"        @ C;"        @oR"@ _y[k\`^>,_6xuޜz4"<{V>*[y^._\2-m{#۶tWӟ\-^Mn{g׶a "`m/˞ R0]/vX?g_<%}@>2V~I܌L;aˎ{d߮%pLGZ}Ϗ F b7wP^VYӟȕE[(]67]~G:_|Jq`֏/ֶ$=uβf~${8ysֱwL Ej;i@,b{:[εƓPLNu~ QwCEAޖs^ )hIDATV+9gyk`O;tuӿXfJI%#A!Vi@\@ּJQE o5ys'YMY/ĤJX RERg"Uy[ڄ{#Jģ 7י&rq6H&T ʩ|E2}ri=\D8.vXF*ewxбW*XNa2eՑP5 Ćc$: rհ>&UИ(tHۻ#׊wxk@^Vtx{h<Ѱ8&ͪ|a;}V %3@ Y8g9[RNsWLw8~ØwTۄ Lx RL# Lf-$ mj%DTߑTؠm!BcYM;rR:YPXL?0OqI^N[x9o<붵doOǗE>K>_pNJ33Rɀ|}71_\';wsϾ\dsW.wg]'7| ƀg.-riZ:=v{"9rRD%ov%A-21S/Ü\8\we{ܰ?9tk{$~v=Aμ?R{%/ȾJ\uN[tڷ}N;to;ϮLg^s[塼H4;./=;/K(9*%f/Dyi{q#r \wl8-3ģݰ̜;ߑ;<.!ѩ5I;2FL'Y_yгz/3|r펏=m"H./2c/{?!_g9k?Xsg^wqqY9b ?|pp ?AadyVS5/88o{?/'My#oQ'nM>Wn_-=Hg^sN:ŘNso[?)y}NªCfgdn`lkP)_|Y#MHrE.s#gCaQ2ٷ-?^i9̽#GLf2E  V#5ƺV2ya|_Rc2QPT?f?I/IO9yuB!+.x57 KXw͘F-%e'|bmu6q 蚇vM{,PD׷>@{u͢?뛚ǖκl0YQPX%GԔհog̪ ~M$iCQs1\.*SL! !Vtw#׊ڢk޹VTQ"pO[zh4\g Tٹ6 אŮ5q񜥫UL>q*3]RV[z,Ρ{7G.iΫ!5b5W+FIYU}k @ 0a,=(M*:?NG ??z~TB79˜9rCPXyϦ>Gp, jJ?j/W:z`={8)#!P|Ae3 'eC3pL uٴbZ#Im\SkMfTPPLc89V+aai<Ϗ폈۰ڍW1iϰ~;Pw͂t;tύ'ّS' 'T>u,%xssnȍ>ήS^Z:9XtD" ] 1vdž> yVcH~l͗+L8%}dUU.RlRzݼ$}tg}Xd PTeryUTcܷrsZH!O3%hfOϫ'mcҟ {ۆ.]҉hԐ)Z@pm,UKU8EHioX-ls\9}oL# Ѓ\+rѯ{$Rӽ>0lbX֐ׁ?N% Y69+kQjsD2g!CwyD 'TA?}!M4 AXs|P GT<ncwT&ޘeYQ5殪GHeÂuz,r|?_E::@(f#kS#ΏqwQtkÍFjJ=?;70=Τ l{M6X"fZzcwIZ V' Q0н??;Oz_Kb{vQJ'@ 0s]¿U5o^c ET*7P//HLv]7sm܅]k/0n= ~Kwy i4ceT Q+t.w:9w*L#ךw'8{9\lfO=(ߙ jup{uk୙'9sVAu 5;UU>nf+d嶋;MsԽWvnWe<e  @`*  >DR, *}CUdqgf߇S%kX>44ƽaK6t`RfT"Ua=WcY}3\``+!6UΖyX<5C 3Hg!#s,tKFU,6 Is[W}L!jjY [Cur}e.9n7T:n-.5sVݱO^U`V=T&PHa.{p Y۴bG=XٜIXFYf[&6]iwpڃz}ͅݝv$'Ư{mХs],wXn1{w0 v$ 걫D8Fg;vY+nN&4J6utjSV [sQ3{ GT,jh?;ur=yn7Ԡ? YZUefytodeЍSbn} lϻP":ګ*e5=]nh)'1YЁ\Ӫ\6/4.cwr}1R48}gqӵ_&p`=Pp>M˺ H,hNAFcϺuz׏;{(cY"?YwYLTRrN M8y63'+*9fظUeP4JFʐ~yJ7pQ¬ @Om({|]Ƶb̵b[kE_q^_ꢕǬaCI2_?|MIɤU:|ݣyJ+?; Е}EF99&ھB5;ǹAm{$:Ry0Vݑ+<{4 +-@`J > `8A2? ;7?7/kntvp[o,_tܧazHjRr\i]͐1(gDk*}CN 䴪 JV㥻O<uxc؃D lkTi8ZH0hq^U* ztaWOek*f58+zjodz13#h\I0xL/mV1JU?xX+:p\64Ԣ}sLjCHIMWw~mLv6CNO2M&3-wŽt uCldLp< xZ}RAYu^TU.f~kR_1 @hΰ+ڤyYk"ϟvߕJkwVysmw[/]+zz>N4Gfuo^Z=$Ǽp@`8gY6b+ytMgĆܭ,|acy0_?# f2y ޠGFe͍7}=J#%j䩔Ua3cιY2{AqmMLΏA߷vqꮟ:?Uuot>cPש )0{Hne]g].ѧw)5M5}x ?91\T SyGtf]b qppeb~} 8 9g߼I)iqpEaX4H($JOUI~(JsqTc?[a͎w$! 3u׊\+FkE}NprU=˩\> yaC^STtX"9 }C-c笀oyojo+wysƦ=ApS4σaoF1c?lж$ +@`zRҔ~ʳRﭤ+Unq"e/k.^^ks|!!݋Ȅ[KMM;;Śviu{ҽ8 :ϱS>vPs=y6ٔD:=TU&f)*{鎛PvYF<+{@VX&y@.z"f ANdBQ+/P1d3.v TKs0 jY=\逦nԤ>i~ȿթ19p tӝR)w'0XVUcdcuKf'T{13{ L}k:\o'24!Tt>v!%ΫL6ktOHB!.OU6P49k迀2[k%h۪m7D&sy0\9Bg4J4 *mgLܫ:4n`&?nRWs$,Qfm?qs񜤧F^qך*| kS#x SkEcrhvrm+׊7h7۳nkE{ }-|,,ÓsF>gy1g9?;{Bs>;a6]Tp,Jnl*L# f9 x! pU s2=W10S nj܌j"7m;dKU[݂jM6 ʎm 23]Aq5g.#?ocɠNL@g\~yHn9|lۉO>GH ʬ۬t`pIʩcKߓ{opjLYQ":x+:xs_N ɭƗY"2:uJ9E=Ǎ#^V>-qYw̓zٜN|AN[ǏXnBN|yw/[KcS=|L_@.Vt-B֣,wls[埢dMI@`mZwrEh#p)ߓk^#19k{i.ɂi5eBQI'wA?{Ҝ'apfF"o[`_Ó// yt9&+6 ? `]S/_pKC&G+ʁH(xR=.'ԨP=H=-ѽ}ȝB?zݏdl=inDZ @VJkE,׊ &@'9˳O69SedII@`Z#xErcq;Sp\FW46'/}Z耬K8FɊI@`-f/c@,oYQyz/{=ޢS4FC#r$Z3 G^L AT !@`r( @_ ,{Wޱ=A<d #>dʔ{ɯ&w?Wv .k~Nf@il\֧P}@@@@@@@@X~ߔ@Oy7)sss}Rc       @ ʟٟɣ> d:6D~غu+AY!       VΪT*KYKcCwk ?       Z@)d:6D~f;G>é/^ϙf@@?>9O3 Y;)w_p@@OT, @`Z=krB-@`?@XmV[@CP$ @@`q%5A 0GwFe 0kII@izzQx@o]O@@@YYgMN Z! -@`j r l6ξ& Уf莣 f-;) " =-@`VO> Y}8  (@`V 3@ 0k ַY{P:@@`ZmqCXC4@@#\Y?dFx@IDATXwE Ć;V,Ni55H4+֠%v  @DEAw,˂;ӽsν^vv7/{^)@ V୷⎙)W)@ MmosS<6+S HR HRt*`?pe*)@ 'ʙuuLR HR HҩO+n⯃VE`#HR(fH.m /bÑ^@,egl&c:VPea ʡlJ(k*HE..Sʖ/CER@Z %-lHT '=_T' )C mUCM2eɟ=z +eΚWfzeˢb `iA%R HRRy DBs{IيlvQvu`FkFdDyQJYuW9ʢI\6 eXܱL•;ڴƍ5V⮐!62"_A>6a^8p,'57n@tb-f\k®AEfhf/SO D=DɌvmiz)P*jtWe oA U K>:~=%/)w8:ǯ|.2$?H{^*ѰiMTbF4Nڣ>s4{Z]}+1fTK %Fͤ(n,̼1N¦2``֢,f-+Y0+cX{ {#k<أ5U’,3 [`L?X6bJ/wj w,IRt+@`V)>=" j*rpYAY]EɂY18,ۯ);NW˷Qzlo$m@wCz z'OOkiqX's ^fwt\ ڒ՜YKJ;!X+&9.['ɪƇC}ں뭕z(=lQyCzz )` e!HR(@`8}E&n+ء5*W,($NjEӲ\+>暬YQaEqJ:ʈ؈+s b=S4?pV#T_r 6Y7b>L^30Zi1,UZ`ؘTu*Wt!_Ҋes.wYwʖ-qms+B{pw3A@vdy l^p үaɉ:,kQ[~g%5"Hf|"YcNhlj|#+)=ıZѴl`O"fED`Փ[6NX- y \Ej-|' dytڇ༧iy?]o$ ǪO88Vj#bHᨂx FII+E,]z5;PE~w"V47?Pwa bVvLW9N F)¬-9O60,/zaWT#TxL> ׆Y)̶yLtKęϏHr? P*fU]O3s(EehrA{u mS0eQ.s<vl| :udvų} )@ h 0KJV;Cͷ 6?̔#I(+= _f/`׾d T63bz<9\[{JVC`]] 9:a Eh/n Ա8LV}n酶MwRYYq^O9os}R( * iLR@  Gv|#{|w`X Y\-\fP! T5z|Z_CYJ` Yyo/j̢}MԾvx_), d. $ז,N *@ 0`zQkR \\7ۡv(pKK`hWWb\N#ak%Cj@VJ"bVCdHPWrgW. cDeangqY$.N=b n#z=6%fshˋͪc4o89xo"Al~jGq\,N 1fRЎ^bi 5-tZxkL~%Ëւʕy5$Ƕ*UaNj:.\LdU+Eks}+(4|>:nL'dg7* [g7SCoT=]haV5 g٣,V.pe*;`0 %hK)@ h)@` Y2 hGbi ?ci ռ!R·cL{tUo\ZCV ɐ7geˡZuF|<;ߨ} x0CqRj4hc|ʐ: zycyjE n]Oh%/QS y]Dn jZu3Uut # y3s*`vN[@! 0!@iW@;rKK%'+p?8mC`V&TUQWHIWwP6*SYF<A[ѧb(|4Kjq,7|e? ƔMHVg]Bg3 ~wuUEuqP_D_=w W~!α2mmc8/-Je)HR(fT1jO @+@`V K#00\ë^,c<IF < nWLSexail+Ymi]q˅{ ZZAviEY\YuI-Y"qI w_% 4éhalDB0+YXˬ vlmkZ®Ѯ9;gMJskk"u~> 5XM"Z4mseZ0XOūW|7Hs2Yvʣ2U2 *6"Y4&u4&'[ü7YuI9 2B cD@s=z{x ]5gsv;gM#B`cL+"m:N/sBKQZ<骐cDZ@0EH]P)@ BA[%ooLv"`c|g%_2\p&*k3q~ (5RCRf`VG|Xˊu j\v:bB[T3Y֯W1K 6fYú K&.2ibbixcMc_1P@vڠF Zz|Xk0[+QZavuƑXp ϱ Jc!HR0Y)@ qz\oa,J"VMDǢPJcж@)`Sf-DђZfiF4ZP Q5!C-,A, ,bPMĪEږ(̊ k 0+3!>UC,Bmz#|u HRY&4AC.췰`ȟGHT:7^U5lXFGږ(̊ERZ Y=د`%AcЪV%m%Y% : I ZMOoa)3.HT"Vj"Vٳ(TyP=LY#M(?RGd^:FU]g\cHeR  )@`!uh)@ ŠY r Q0Ha '_¾8 r3!-%%hn e7#85IYrxOo8xa0X>:賛MCLd)  )?/aii}q{Fm}VU9陈}{VݪYVhX9p{YOc)[|;`SpNmEcO )@ r̢A+P0Hx{ Js=vBK}, UUCevqE 42`X|$)FRE*/|w?|U]rXjIKXsgw84*H7YoN[@د bV>".(LRUHLI{}jMKT-#M}^үf)UĬY,h(=LS()®(PX>z賟IR 0>)@ f 4| %[0m2zM:9VUK/ +:k.Zؔ=0L=?P*;0`VDݫg>ѽ4I-_G.[GO> !2 l!O[X0K+u"奊u|Pw&MY͗`Б$TC OxbP HPPc !8}^cQr"O$e Qq'ԱŎ~}0r"zkɫkgj~nO/=F CΩ8z@RPY|Xk|zEj&7D0&E1 0K ^"ISR@ 7.6_ܿ/sQ}/ױ1G/z1i~ ɵtX .V9tZZf-RY^=853#>tBKQZ^$i VYM0]$A  0ˠ_X -HvQm4j(}}0|0 1ӫBʕruHZQzQbel / a-+> iBY:0| BcMr[ uJu(@`GǒQ!43gXãl59L~,A `j~@r?~F4 :בJ@N20kiNeȇcUT[ 5ƀp~8ڛUⶳ66V05r٦ި@ F4ZP HQЃ>?6ERE4 qK+dlwpˢ0s?B0^^\7nV1ޞH#ߦN+"akY 9ݸ%bbj%UqrZ.?{51wVE~D`ĻM`x}G%XxMppK5DZIug7H`Rz=>d³W+r@.9W73#`7-\Ջ2bVKpxPWծ2CMy^ )5-0sMiR H _S| eQOA1ȻpRen}1Ӝm aD`J8RDzvxWwYRO%"fm0)7|niQ9[A}o$bRF̲B8|=zTՙAU_B-`um'tR,T$H 0+?h?)@ oXްE}FZ^WhlXMU][/ a7fG Tipn-j7Rm^*f f_bv[=e`Sءꏯh;jm܌Nfs7O)@ z 0K4H ~ f(wlٱX*Jb`Qr]?h>Gp^eZ XV6ƇFۤ@q(@`VqLcR ?fmQEbSsdvR0lkβ^ mTm a¤2TL\Z~t|*Ciu䷏^*jMf,!jG;˫bS14ØԆ Jf @ %Y%!PI{bvɣ]wS\|}0KVуU}aaJk,IGYYʎ"t@O?`YKWL`<4ѮY:f\:D~0h\/~ѯX*ā{j;P*C,|'0Kn#IU@ 2B>q4>JQU.pQdy}0KV6K!u0o>56+Kiwk-fl[8Ml0νLovT뀶{[@=|#XAҋ HQёzyM #*iF77dU> MU f*`iu(T+uL9t|jlV2#k3>CDetARgGvYu^N8H74bO% ǪOPo.- @7Q.n*ni7 t$!ď%X,Z'B ueggtL$Q H+@`V5#ތwoiC+s;x*^ݰjVCua>u(PEa)հ ,0)'rӑg9k\SeEES=Fȍc"N=BUo$>RCi*śƋ%Ё}hj c;;PswQǣ2)P< U<:(6>'.pӲ27Z5,c*8I4D @%i3KA~>Z 0wh8I~=&kC}16fbER=O[f$K:qpX*U{} U-P-x)iBeR  )@`!uh)@ ŠY rQ5K޿ :yHOqbJ7̒۰#|*JqqFoܒx[0+_a{@%`k#Oèz]ƹKy09Fs\ƨic{^3x ^36M$Yj',f@ QFi߽ƠƮl}P|6TT`m43>')x٩oٲIXʥ Xc 0m#.}b >uknWQYhIEfmhEQ2 nt+= xq#dw;xAǹKy'/!ֳ5׎l5q= ^ DbpX `~g4%rph.ʂ_H AzRפЎ%,l+A$Sp=l֞PCI=`RQYrVȸ_Qj0V,\K"n͐:0qilX[Ә݋ %ٷ,5sl>L9kKyZADGц61,6IR+@`_ *)P U{<=9VP\d> GԮjhPW[D`h?}"SYgq`|aԹol#)pl&kѴ#reuَ[-K`Ǝ"r:7ujd#.t9.Ԁ__QS>K ҧ bRTHqx#*:jj0 -$Ž`]eV\|gYqa+!3l`q޽q61nYA_<[cd;3d?~ _ߛi|p]dF"rq^=Ц%.i^]]U׫;d( JfVϛSoUT2D!hӶ&+WqIۛ\&u뇢?VYbȒjR`K½OWN~!q33q<1՝nHi̟i 6Vpbwƽ_~ xuUQ'ͫnhՒMMǣGd)/cD f4) =RY[噘})ظ-Ϟź'xc+Ef)Q +~Lli|+Ү\z,A7%Ǯuu6[`V"`L9 Usp:캬` <V~6jYbKIRP+@`Z z'HR 0/a3~ßK `%vԊiJq@*"qhC x{r) c'oj@}={VѴsBT{o+\քYD1F\Qk5( 0Kn"#IU n,fͼ, [kix`Dz`,R7+.e0{ ~ɵ{q^M{Ξ.QGG l^p y/ku".9,?6``0 iDHR@Y-d FDcǔa,02 mo_AVR.c H0{t\*D#4Ir +{=1ų8꜈,K4OZL_;(:\Mpִxc ƤIR@J\06}V2<tϋ48N-wGJeȏ%Lehxdvy@[rO9/־GQ)®(~2(2:H˨NknC)@ Y)DIRx ~g#Fgñ{-pZ1ӊHZ*gvbleKo,KBL<Jq_@nth0*b'ί-xJ`Gкs;ޮ;z z~RBJӎWʎىU914QQ %'+{Ⴠ-. B1C1jgşɪ;, Of q0W3wlWB$UF >wZ3av/3ұXSՑ~eRB#n%ь6ra\cjE ^JG4Jnc뷪ztG6F-C2\_{"FoMёXio6w AVGb񏞖=E>ǑҦ+ CV9ώohy2[`*'tRKJ@q)@`Vq)MV )^ Olp.=VcD0י8FUĨϣ$^Loo3K"\mEϊ'2HnCbh['L~[|+*A};l8EqFX2?,%ۓo!Y0e;-=)R,()P U(᳓"6b#4*uQ-*U*`ru#Ykk=)z(_PIMsoM2s "3͓mba:Pqd)}iIVZ`fe j!9̒KiB@))aY\fKݠ:ZTy巊6> Rܸq/FY5iaM!62"3(ٶI ya68g#..T61_yR DDFM)x2_PoToX D*EZ #̐2 E|BhhW Ռ<0N<*ͳ|ToPBe#NfvaNg씥F%kjzuU:^'l0v5|E71;MQk)P UrRg@V 'qOQb<2_j7CzMP>,8D=W+UBPHo{d"1HRx z JR. M_ %r@)W@U%) RS@%)tHI(@`$H JR+M H"R"!HR UX83 5̒Gi>@Ro-)@ f4x 0K<"KKfnI+@`=H)*@`)zl"HRYE:YTN 0@&@ 0с)@ U,ЎRY4m)@`\FOxbP H"R"!HR UX83 5̒Gi>@Ro-)@ f4x 0K<"KKfnI+@`=H)*@`)zl"HRYE:٤cq`Efm`":#[[q R{0`3EvS("HRT*@`Vt;Mڀ{B sƚhMHR 0qIR(0\Pteg;V.nR JfjIR 0 Nr0lzT8'BV*̢)@ YlkU$ fÙYn(7:lG25dpxUm~M5VeɁYL#{ÞIPNU4nf*Z/i ҍۏc#\}&Xm-0%Z Vf",I_1wYTꆕļ!ұ<._F0Y  K#YNy!5% oa6ֶ($=yזEh{L`V"O8sDTݬa@]T2$8"KAP`\I_םcp5bmUz:l6ssyZK_7cp.( f3/G}pduX̊+XtQF 7v?g*Zua޶5c۩-wQ΍ĖC`q Cm/# B."IbYݮ=8ԩ"tTJ"Y[q=|7\Tn޵o S"*]_qA{>Ȫ+\H.$=F HDA1]k.S(uʍC}x^";rRP;zX*MH`,"HRTux=)+U|Ab-xC]i%nF{ 6) {oFTYƶoE1ؾS:A^F^LLPޘY2ԟXS{ʍh96 HhWBUb`J%Cm[ǚ g6 Ƕu(VkM1рY97k KDE0L\<䌍@#Oej55 49Sâd-fQ,İI`V6"2EEL`MWX"]3e%tS<47u)T+ҵ'Н-Z2+'L{/cփ}LM[{9i\v71"T%{~2F"r bRKZEG[ZFf4)@ gяPN+ԶP_KE̞8y=^Ān/:IY,>{ 0?aD[Op5vEwG[s&hzM%nel\h/!PRQ٢&Z6P{/_Yij1. تSOi68eY,^̾z4vc:Y<퀻ɈL`XK֜X\XKF1P5üaq{dX"bjީ.%B0'K_y?Xz؎? PKKZ`V/܃3`/4Ut;S|7wd ěP ~pYZ^1mlw`ְܲAı{BױWTDMCfh9ݟ x~OlpSрYiB讎 fIVN|pA!ȸ/=qˊ30jN{(D7LD)>y@0G?üs",V:C} :0 1K)뮭!x2슫y>/qRy0%_փ뫷 ܏{ ae~3,Yl ק)JnKiE:"a&y_w$0?\y KܳL 3ztl5WYf)@ S@ `沄a{17oCmSɟt, mM#prRwe%Kv'-ӳW\tL5? Kޕ݇tnlg>N???)|~hɞK:>뷸ahݗ:ezSY*>XZJQ h;ow65RCFhعwڻE-50XaNhX皬$(vuMyvFE)#2g20k Yڲb;3:[ǟ x?scqʎ?E(oc7FQ|t)50K l f)W RBHq{.*ڙ^j(1 ̚ R Wf:b.0K_LlCLRJ2nm^3ώcykSfg'41Kz bߩw꺾6j:>[R֧ >:|B`cQ$eni[_ucc!,2/"v%YM`VIOc)xGϻ_@Vּf%~ditg10k'xYrI+2M@KXZ1+f!(Hi T@`)Y/C,e{ummER@)ޘL O/>7ư=+{P=YזYf[X4HIO¸ tdQ;iC[bNH&8u$ҭI_ꈎOp;mFer&fl?nQ\оZ|K)K_V >Ţ;[bߐ&1k0bY&Q%0d]cа,e{L)QSj`VX|=2q<%p$M0ˆY:Lxb/J DbHi U5w'xog)˾?zi/ًSX7 1Oh` ՘^+ː LkˠVu,fmmz?1_Q`c/|b8gVV.\ 4fǰ0~?M @1+ 50K-_n3$Yc[wXH޶@߰{:i)n"f} &CN2] rAA`,r+:/^JINOEe"IWNV&. hE B/-&-'_zO*RTq{h#hK,/RS"j "iRdYSؿnһG˰/s1~Vq|4LK3ؿn0=9WTP`ERW fKs):a#=%,rGZyl3 L? {.M0+HX18>\dGvY!\ދ,%Romi`FY]=] ճVd8 *HwވՇV, [Ϋ4g0m'@>nȽ9FS֙{XRZ*22^@V6r׬a+ӢJL,SA V)YYq+Tm5oe r%BO߅U-Y cppa拮hRK[h>n6Zeh>y$3/kS{ҮokWX_g+fe>v>5i~|j.R}co6=Ì/\Yag`!<t)="um+{H$2cbuY8^XK:y2eTKsBK<܄fp0upvҀtᷰCv͵3V,nlOfF V4x_ ~-uOa)=!\3; "w}^< (.q?Y'ҩ)5کp|A 71eRw&6LΒ\_;v=8nuDERmbR][g̿>oVl$91Kz0v}Y>@ GFذ4kWDdu_›E,et'0K!,(ˋnvfGZMT~ௌk{j6iijBX{V.b0x6tlV@3ł Oh>mpjeSG; ?&NI_ e/I\Z2+L"NO)ElBMp %Zx'7ӼI:H$ KŢ!<3IL2CC&ɼFp7X:W~315 ,RM+|K*  ]Wx=)pD#K`"K xtbkא!uO\ė>,ec|檋OhgD?ߐV, (uLy5\+"cam}>)#TN0ԦBȟ30| s-(jfRbR854 ˽`Żnڪ]]ppckm[kbz_׃&U%rj֌,T_>55h/Kse@yGF8|,hӋy)M*Y?$ +ŗWIoUIR+ 0 H\V]Uw?qUuHK[K P*J)bc).EJq5tbXHXqB aI t5ei ak@RL!iI{sܙd׫̝{=}f9?μO?Jƒ@_Ye ̊MRƢ)z:]3Wʴg/>.\ԷZ06Y7qO}q!ĈIN˯=^>x"?!?IY׍Ϲ'5 TEvgT`VtE})U߯6W>~!7^&?W ZbQ_|g׾?i9?S噫X\K(ޟT,ÿ z,~q|xiyV):}iq&0+S$] ayVqQuUW7,t a1&R,_*ŸZ@pm}M~ȠQO*7}ad|y㹲^)uks-o ΖI'A7 #*4KX,_qY/6KK|/;K=w J֪Y2rf}N2|٨'h,CR*Sߑ3'J5!kV|d_ ?GaES$=/VۍnWYa-|E՗~U߯_k(9y ?zVpy_U^H,z<{?Kۥ7+ g}lt}dyp5z31"=.AYz. k+8ܾ+6;۞o[Xʚ+_W H/n5j2ez{亇`t_w\rR<"36WsY_~gz?jS_"[Qx@STMC~fȲǽGtMBYbb8bWʙseq*?>q" y÷RrQzTK}xF_9N80 gXj…:[sgV{F6}Hw}хru5K\i$W'WyMp(3WX>u v ozdB"JeYY}|kTQ*|!̷[R}M},igck(9s\=$#\¢W_Rz2euQk%yi6ӹ_|ϙ.rf@|be|ٝM&oCo]bB mV_k0XъK`Lq~mw˛SWO{x X՗U[}9߯b:g/n%{ț#n|-3:CfG{]U籫kA--zW5'̿r<L^XA}EV_{/us V ys,orԞq~͖3s]/~ryG%;S;e::`\X 2:&} 37ٹ'5sndڹJI?!]R~T}Ff~T@^sq .b`@r@r+ZhD-p$XU_q̊VMm;kQ_Ѫ:@r+ZE`V"@ ̊VE}EK Z%0+Z6-h-hWh#W[_Ѫ/U_ lS_Vn~Q_Vn~E̊V}[YѪTh D+fEئ%%V}"p@r+ZE`Vꋁm+Z-/+Z-߯hYѪ/r1 0+ZJ`-hhW[_W[_Ѫ/U_P_Vn~E̊V}1M}EK ZE}EK Z"0+ZEn@ fER ̢%b`@r@r+ZE`V+Z-߯hYѪ/h D+|h D+|U_fE-PQ$@@@@@@@*^@Ӵ SueC@ ӦM={D @@@@@@@̙#Y˃. 0+!7p444Ȏ;2%       DY@ʺ+,fLdž         Y.,E@@@@@@@@  0`:6D@@@@@@@@wa)         PYӱ!         /@` K@@@@@@@@@* @@@@@@@@@]X        ,@`Vtl         R@@@@@@@@@` cC@/0ey-2sp<9|QrѨsAٽp9j2ofɰ: PFm2}:ΖC-ˎ84n7 ޽L/.   .?2S,Xt,=pٗSݑ7m>$Y>b1@J:U ʇ/:Oh^^#N>W΢- GY1X 5qyKCk~zr2\O*R+K_{,ԷIT7͗ڞ%r@MuʢӯN$Q# ؍?/L_>N4tɦN?KoR #Ur$;d7W%bhMK?My оM y" P*-kw}3*_*[$"UM2K&>1"WNةҷnnvS\|N;-c=fo@`*ä+o9J8iP+뮗wne%q5),E]x07 #8 jYK.X]['%'uҧ؍W/Uӭj_]Gs7@x %u]gX.Y[{=uwX]^Ig}--)#ŧ@r jeϛXA͢ubzvYWʢ*=,+!ݏa߶A.]p+:fDԭs:U ҿkv;//6o:X[V5k){a1a,oZOtuzpwcuUcZg)٠:rJ;إ՚,WMsȻaWVYM]5ԺĆ{&0N*$UWYx6RZ{ɄDl1u5JjrʒleSMZSFڴiҗ]tDԴ8&cZ{YVȽx P4(94m@w!/5v5F[4KiCCC߰:Z@t%eJPϭ[}Ή{dݶݧQz[0/CF*,TWґz6*QӨg^֓ll@]3{q/.]<j<оơ) ^ o u1 Q'PMZ[{ r`*MkkmҪ]APUM@}!/0:.,:ݮDBs]L7Y5km6vIc}Z}2oj[*3G/gsO蕬zScZkgð]km/̊j}({f9Zk{Z j zjv osITեoQi[S"Z֪,+W=I k}ݭZA@>PҪm9'X)`T!KRrq5;-<5%Ev7}LMj黓9 ~f PU:n+p_ogޯU&Wk根udʿ9dگ`O;YE]NiΏ ڀ ^5 ʪkL-4;y,ZC&.unW^5εK@ U:  [g`8uj Jhz O9J6h<jJ?2bկi@+~]3C* @Y*+?;Gx ;J.ܮ :PATâeNe6hgejL 970 Ar~y>qJԫI*(Hø}srW`&dWjL ӽ9.5ث4iu5ZR݂ѻ_] jւ RRg ѝu;[w]3Z 3 .s_iCZPeoˉF ^=JcZK]} ԏg;l&g]vWWd ~f-@0ӯeeNsn̦Je9˷>c5IFoqK(gkõFTW:ҕBlV7{=ǯ'*Y%ƭ|y]WܺGa!D[hG ujMlM,m '8w&d̸-:ٜf,guiY'iڂ [{q5fS[$:vdVSmhfTތ,ugi6NB K <58 ܗ_@ZslxH\d<^3Q#p2GpwDu֣V3'꼷56"Fif@Eg ~ DLdFоn!*0+e*6v`TkMMZc?ү7$w\LSSq/5 Nܵ}m kڎ_}dX2a;,x5k?hvN NU B ~Eg  G˚rߘ99˾m`Jk1gBfƉ\^Кn9@wmL0R*0hhJ鄕%39-Y׬j)u c3.OF9<Um_9S+]5*Qn0>BA`'Ѝj5tvZ9kV '. @?*C%I),YnQLw0i j'}Vb ~F 1/ќa:>?ﲶ _2V_N l]gIz~ _K<^gncܽ!Mu_lDҾF& Q"Zqd" `"3g1왭#bB׶`G~S~O&'H*k:$5w63K}J](o5gn@RRF`_y3 yDηk:-fr׵g5`‰yqwMKr{/6Ux ºu[Jd~T{ٽ  Nsʝ1NDei ]N{xk4W{ڷjuSwJa̋@?L+ vff\PNPrĭAs:a[F#l5B~*M+H~JVcݑ![N3cאϷGŵ_w0un+K1K x @*s{Xz橤(TNTiΩ.16ح5Xwmc9 UJhtfSҮnh:[{WvUαmj3[ݪvgoAIUjs&'ɟ9'MKƖJTk]Z}Ժ}WNA՚뇴juWzjvkưRZkV2]ߦ 3rp@3ք.?V$YtY,'~*,7qKfΙ#sfN˹}(%cjYsȼ9ו64b;_;A9whqHϣz̚4qپmPRz&U:O&e:Ol9r9FR㣲mpHƦϑ5屓m[6ʋO+i{/EHlW2r&m'~nW VΟ7ORUn9pJ|HvZ{,,FpۉT0DCQu\JWqu^I۫yfx ~Ƴ^)^WVйn1]?T][񷛸MT߈JMJڗZk_kAF毠ݔ:wA)Feg @EUL!@;?>W.n ϦadYAUR`-rw NY{J;ltwUz^K}m -keֻ>v^'}c7bo\>g}&$éx8e`O]R@4KM5CLT( оן#q 0+5L@2lyb<_eƌwk.9FVQLOqYwrM=ƢD]Lf|{Ւ9NVfN$Rg)<)]ɊCJʼnh٪La+җ=I3NIY@?,;ոok 0U_S%~%@Z.yTYQϔ@W`rԷ@,JZhIV%h̕2=DL p<,=;g .@0a G=D/ѫ3rDI(yE(v|M2S䭇S,>ԾaIvD9 y=J$D$[1Sjr _=Ga!D[hG@$D@b.@ x @F*, N5vUJ@P  3 Cp@g" EQ5 vװC-@`V# PNDKJ 1yS<@B @#@@b'@*@ @ Uu@!h8\ DK3ZEn@(yFkk! 0+G@('%a%Q@ ~Ƽ) !J  }]R @ Tf:  D4@.@%@" DQFk<#]55D@h # Dђ( @h?c^@A%@ оƮJ) *BUd@pp"z  V}[@(@#F@.@" @̊v{@J"hIXI@ 1` @o @h_cWY2 @8{":*LgsWɡDF_ ?[?r1ϮZrw/䥡ɬ#ϗo]Jf؎U@'So+Rmse*@=rgϓ4=o'/6Kο[Zϼ Y@ 7\E7\(@TW`* P$I2 I '#rfHc\|lUHQ6]."PQ)\i i_D]) PqQm?mj+Ȉܪ+T{5$_[sDp\pNޝ< @oL&ToR@r D}ur~r7 L`N @E DDΏϕۜUAND*0?.5R-|Y g*[Ha@ Qm?G]&ֈT5.QY*iV:)2M#stY"pN!+gƬ(~3 @nmJ@\Qm_,S~@fbM@*F ',=ɚ1Kё2gN4gBYr=R+wQP@ Qm? Zfsn r#UңJ>d@oVoD@0 D}MϘ0~  0 @ҳmj?Nn}T"gȪem#ɓ.! -GeQyM.4|e?\ Tg,O:U>roafo"O>GIW}%OV3"[Yx'[4.[6t=f~LtLeܾe-)O{0ó/@ j?G_'}joeݽwˣϨs`cgJϷ=<ȓfڹS?,]n jyLߴh->Gռ#O;E8$OcUktArQ 9idאNwn,/9E1K72sәeC3{srGzIJSu4q(H^ޓW2;Puwzxng#@,4  @&~ k THD?wmlju\}`V뻞h6mYS='UZ܏ Jo5hQ3Ihd仵׳`/,*·HR  mefaLj_Gג$/ZzoMKF*uyMUHAa? @vɤ1Z8kWGN1t6y[]m?&n#[Z;zZrAzzZi7QئdC2@o0_}`}:zkm=TV[}kP_|,:@%@#aOob @U.pw[\zss,C@z"lLj -mZ[KCoA+kͮD}<֧ Ekooꪜ B\A?]Qzm{yLkqI뛴vޕZ=aa#YU+c_{V[W$jVklKﺿOTi--Muj;5A^Ԡy$VUՏcciZo+@OSk䩟Fs?x>&]@ ,Aa]2۾dmjڴƚ̠c]hkv9'3@'cp'触YmZS]FЭV:՞m$M[zWkZ*0T@I+ njkVhϘ(/>Z;T_iǥ lNh5v_H1I75ջGM:+|S 7OkfFt)}zp=<_pr@ A%@&hPCp;=kZ@* 5yZgPeu3VzZ+XH/VZcgMk'oc}֠s3ˆg^xO̫ڵ!8yQjr4WS|"{u>WN鞭C`&}Vp,Wgԏ{ ]eک˓N@ i?vIg՞YH1w:JoyC{fsFI sӜYRo_ՎXixgmj4p{n3gʳXGP}Ww"68g^T M'4QoYޠ榌osf)k+. @ToЯ-7m& & +ýý\W Pv^d@ {"Z9CP(nmUK֚8%1\T:퉚TP[!F]MBl@}lz;۴vMoڏ԰bU3Oox rςgYnI5[ʸd x3]_+yN[;[擎e7fw9mNU lGؠ56whʇwB*%˚˼l2+_3a-c&j|Ɔz jVWc@VgV{Zaҧh+M v=gf3sElnsN+*m3ܷ~&QۖW>ܻ9 @|o[`a~/oAI@` ;:5ù^W 8*)0  {"꾰N58jZgukAɛ5V j&gF͎Idͨ;0KjWkoijd joY+;,{׹`vOvMIj{N L'}餭GlnLo!!~ǿt 7~c 縞*X^ӢϪfj(0*v [uꪤ7E^߈ @Afw~pz h!DT@@ LSw" RUm`ʜ#5%7g1w6ہK49Ԍ&ھEKZ*0;Tqf TF,Xy״V;5-j~o=8c2+5ƠaI@n (LA;d%jaךkvcѦ9MԶhDj-&3;o <j'|e;/K/l W/d{*3Y-ǀ} K AC>b P/c%Ai_Wk62{fׁ} -R9}.O)5('{һjG> Ȳ-o=+[ E-́Q{>q};W\e7g)1]myxgͲ sb*_>{3|fk]fMGg @v0srFQz6AORq5v_ʙ0ݾo{RWyUFd֧9װt\tHCZsc2#6k : ACNo/69F i_' z;wu=$up=ܫ+(YPg \ ѦR-}Dmօ)>#jl1grRZ]UrBoigdU54}%֮nMjށ]gԯm}{ ^FD) N:j&gЌx5a^rt_)W>z۵ڌe}𿺾Ev $@ i?A4WdǺ-mU:'>@14u6ezռ14mZiS}Zc5DTfxVlnz}{ \F{֜eӝN6xYA+ձ+>ҐXk;Z'-]VPU`/ @otߠ5A i_݁Yu[]s]g v]n"ZVݘ9 ]uS(~zB@@8{1{<%56},8tL`ǣ#dhD<}̟w̜htedvQ;>k:olsCeY˂ʘkO#ep$~{֜2oN^-V:v'*CV]ɪMp)I0y1gJiEOFUlW/T|dE Ac?7@ ~j_=z8DTYQϔ@w"W6Krq[4 %Jl P~OYK.VU2 g0.B@@Ao @h_uSE !@`@D Zy詿ʌYDvjd:I TU8Es%'5])P6 . S@bо\/Ƨ4@lMX @ p"Z@ ,M@@ /yq2 h_1 @ff Y8.eC(gdI@,@(k,I @ [l  PVG@h? @c@KF^\ @ @L PY± qD4εK@J%@Y*YE@K%_@'@Z@ |fN S*1#-oI{޽ך3<#}{4 @0P" DWFt뎜#CrTYT۔@˗/M6uƍ35  Q@@R (0#T+)# S@ vmreMr饗No" P VrSv@F8@x p=UL*)5  ٳ'?z @@47JJ -ʮJZR >   @E0PZL!@(s@@@ o&c@@@(6a  WFq=I @@(Y&}@@@`"B" PVeg   @fM   d 0Pm@(z   PjJ-L   !@iET3D@7@@@ʛ @@@`4ۄ%  P\$5@@@fZ@@@*BҊf  @Yo#  y 7    -@i K@@7Ij   @*0#  TQ@ (+?;G@@ 0+o26@@@@ [l  @qoד@@@R UjaG@@J+)$ eQV~v  -@`Vdl   @&,A@ ('!  ;0\{222R꼑>   Ps̑뮻Nʊ;a! @oD)Ax oݐ3@@\Ÿ>w`ܹs *W_@@Hoxx"B3P" DSF4\GChD@Z`_~Mu   PUA( { ?@/@#uH +Wx놜! ؗ/;.7G!p^dH];$eQ=0Wkț'JB@ ())"` Ͽb" TEylF  [ @<̊gR į%0PZL)@(r Ͽ̊{mS>@@`r&7b @f @D̊HE  1P:" D]FkY}E`Vk! S#@`8@`B&M *) ̚Rv@iAll yUS}E`Vx PV*EB :# PJJ 灁9D@ 7^?/\S @ƙ  5!o"@ UAMQT=0@`֔3J bc#@CFX@/cu@@ fŰR)DO9FUWRE=0@`V? ! Q$ap"o  55P ́Y{wʞ="o_aϸ=2{\m?Mm1.{eL?@^ǞkTA=g_[rG@hv?'tluҒ^Nddiʎ?Xz2cm(9+X1Ұ8.#z\w)OZ̚L(L=0@`VaSS;@LYzj_fM9{A /-7~RrgfUlw?~m{\w(؀{}~rC,Y}|5_oOK{2ʷK^O[E at=w,z;e)H##[{_'NFdLst3AFjD # fm'ayCTNC1/@g6I֯7K*0km4 /xTgfj5 /y"J8Qs~# O{6_8,,Ty7 & 0kR"V@ YNF N)7;C@"oTdS)p5E0?'xޓ+Ĝ)[vI9T`փw}R'0=*0f *~qi7g>t̵d?)F٩KOr`@ K@ fE=D ZY"G~r'}{rCCz! 6Y栣?c}K@i@U42e 0+>uII% 0+\uJTX QLMB+>"0k+b-%r9mr9iч_j/}[z! zYs9S`"_f>/Uٿ2bG~InWB3_nY 0¤Z`m ufIj٫rWΞlߓ[v \.{Bu;Ƥy;F{i1ҰӟˎGk/tfN =EL̊IER  ]de,  @op Ͽr ̒@3,-T϶Qr'Oգncxa! *`^)ovno ;d \i'4m4o%('5_gJ1pR)' ^"PYfUV}SZ@tr䵭_|\иzn775q`ޝ[?+"g&_",Z*3I.cў{$K~s}_t̙;SAK /PMq!jeloʎO__a}\>}h,+=/39Sg4uII'>"0+|C((Y4v9?W3ԃ9q`֞+}Jydڌyr!Ň%\Ƣ;__7w.Gcԏg;h3lS+]sN٣2w֌Tמ_"v0N;P}b>\86 wViSMn8[ ߾?,pcbGm<@̪ Jfoɧo0ʋ/XuE]6|闹vȫ\+9ho˱+O1-sn?wM]F?~rCdJI+eMٲC5Jfer 0"@X%r?6`%rͯ~3oc%g~V^Yl].nUiM?)ӭ;#-]6}{̷;߸Zp%=\[4<ūʞ>./ "0 *MJqʋJ`7=-֟/f‹WYth=1tάFzf2ʵ_ wJ@P˔\/U 2df}:Voɟ6%Ҭ|~^%sƬ]KFg{Yv\ujYtl?^%P+S'u3y9A.8R9dd왮rfe CM ο^N\tL߻K,ܿ ;ǥMtˆ˥Ɗ˔'VL$ $ $SoߺVY&*FNNU0?s{+04淡'}Ooo2> ;,@`>|BPUA@ 7bY*$/BR)d̺K3{YnWAVW"k.SZ]# M rW" :gƬq5+άL~Zf͓~'A2R|-$$0;}=o<"_*#*kk]f=~lf|J rf{2.Pg(kA`i !N`KdLO0UfҎ7/ n~Fg >.({Y,?t_O%љS84{~gt:վ[3u9Uu^=uETuXeqK?C:1*Z'j[jg|Ybi0+XjWcϖIHH Zp-f?ۃEaV{xm@; 5sRW/~ q"[ӽġ[qAhr}pX"1%-? -8v[HjFBdzOV`F5}7}oW~Hj+ w)s.L! K/ ^ \uYR%Bxw'bH PUfgIJl,> &3\.3/3-7^PF a|hqMg¬mBzgCx%љcKSQ'ܢrrnZ,;N`6`X`WZXP]UuuN&2 WΙY猐_r`, uRCƐ % paIS! Qe h :AGCbiBi9rr*]=|hRYr3nEBi!Y< bb!w=Zrrv:f3Rr!>Up&TB,S,du_e]]KOY,=8 _yH(̲  00K=;]٢ݹ7O{f16nwM@u,5Ó(;]ٱM'#Bp) Ξ>.ksg̾_a;S,㇐FhSۭsDf-+bIbIFu.wx9ygBXnK=0K%H0+҄Yf)`>$@$@$`vo݃/ )Fa&TI,O)Οg>te#gbõ.#F/NNʑ\ٱ2M-Bp) |' \.;WK G̢J@ۮ/vݝ$l"Sj*$bIF';?7>eyg9ׁH6(Jߌ`~L=/$+0eHLC|, _B85$mCNXwX:Qdȝ%{?K^v9{, ,:jѧ}R*lz .Hʩ2y~I hׅmsD6/0kbxaWP'٫f>r7kDFsCm30#<&s#@aֹ$0+%g8%$@$@$`UoXճ_f#ڈNP% b#_I/3'^yi2ϑ齌b,U|҉"KK,Qc扥 / xJj@˘u >=R 7#лE$e:&ILLb@c.T-ټ/!(׉ϋ _B/Z a rKvSeHO,= 3 ps|w/Ǔ05n-0~? o9.vw}|W'6uD]n]t,4i;\efMW:[ (::-xG!J\OՄ( &|Dc+xq5 Wc`~Jhr[OSI(j%8F-f(5h Xw0׮/ lږNPf~*۝ ýsp`Cp}'܎[!2ʦGf:3:n;uwىg:b%c/Nf3M}/ γo"e+U@aV2B5=sG~5a{3ە]J}t#!Y[Fp>&hr[ZÙX'VBdE+_J荣h7 o+ 02dGHL, ue YA.RG:ntU>뭿< ݇qO|+ tvQvA'hK. (2PZdr%wvkpOukeZ9};-lݶ7'\sqѿ;çE 0+HY! 8 ȁ P0Pj|B  0;7AodYFm#0 Y#ݽeyC傮I:!]T1~>[Yy.LE3UW㡇^MdԙM#I, . (g}1և&\9B}Z۫dæCZv[ް)j+!M[1u?[FQf/# ( @aV8]$0> <}2_f?#֧)|EV{5\Tqأ~w!gp`NlmP 1?FN@I^{ Q⩀#/%wql;%>}ث&'="Y⮷mR s_]>;KrZ,y(< G={ypB¬`d%$C@ Pp'(5Kh Xs);d Yr M!H |Y Mc|2_ꑅY@HYv Sܫbgakq/YGe)x{5?g%l!߮ dTYz)o")˶ lzluOy )ô>-t{<#Yʔ`XBX5uHzRQ)$@'@a] YY8%Ne 32rfY"+>},{+0n1\}=~ҹקk*nXvg;a=ڱ(!V؞^1 Y8O9+T='+X/Kk={G>YW^<eOIcoXq^B{{.tnM[Ju,[yם\P}"fEU 00k)b@yE f'MJۄ0ݓ`?­"ԍR&(N~/a/wxoĒ#=K"z.kN5S,eK戥[ZqKrCZZ*&:u[86[%3.c8f =K ȁ s2J A$@$@%u}˞?¬- 6#ൔl|Mb@yE|/N w ae3*?-BHfJ7ߍנR,8ѳ$Ge̒@K'li5W/uMV.kD,9q aaw !/!v_3;G+,ߞ9HiӋ߼ G^U:O$X+ 5 PeMW$@&#`XaV;p.d]SΈՀڊ/$J YT͉e؇ꯎTiїNG-`E#`@!@aVDRR&{(5h. &tM6  L*J'p_~UႎSo@_' QE$@!@aVdVfy`>$@$@$`vo݃/ )F$@$@$@mC¬VHHYf5IYQlvM ȁ }cUx @8@/ B$@$@$@$`AfYЩ Pe>b 0+2\Y+ ȁ <0Pj|B  0;7AodYFm#   !@aVpf+$@$, Ë$@QD¬(r6ڦYmU1P*lHHH oEI DY!cq     ,TvH|(2h1 @dPY(5h! f 72,#{ @0m8_|ه}_⯗?Ű2Qj:{N'J_7T"|N PMx 'HHF y1,#yƿ-w^!n-dߧ#B d_^XGkDe`Ҵ yuo|Qo:X2iPsW lYw c0!anaf)0]z?'kn;C^=ڈiYOM!vpG]{Tj 7;v2Y6i-%j:- 2,@ͷ] D`Z | *:wFg}G;w4?Y~mS_cJsHHH8 e&aֱ'ILF᳒7.T:˧ᆫFht7끩g@S 5=$ íGk=]qcD|:OPFu g8`n07†uI_-`ɯ`F \õ0>6/Ft느=4%smKF<:=6N˙Z 44(PN5V酹kԓwL-β9 1Q]q=e)5X^٨Zsǖ+_Vfy o?Q#`{YFI>{JƯN=V=gn/o2¬f~v%y;wXaQitROVahf5Ɨq2   |H r/E0z6>K~{|:TK;`O)]VM{xd 9kzp}0᧾S5ڧdu/܂~1In[\P UKΧ'` !/.>pl;?(.LckǖG_f5 i"*0W?sk[A=paoVn8?Sqįލr[_ u~6/Ξ89!j–G1Y()NK ο`dD6p (s11f3W4`Yfy\l=K/S$@$@$8߈^߳'`aJ|o+Nԕav0 !,iZ_Nn¤!_/Ԝ܅ן!L Bk 8Z?smfY AuNA0;CdXx&#A9y8W_h ۲dه,pxT oc0AA#nFWp!uV>ٳ^ *QSxnsƭ˷+biQ//e=沈 q9bfLvsu[zYkswt` Ybp B$/D"<Ox-ԯ拱ʋzȒeYl6E1Y,~x2WU%^jB/,,as~L -(L^ng.j,?wq׌1lûj eaGuWFø~Nj T4z6ߠXt0z7<[_9iL{uR(={֙oHMG6 ~pm8\ C>7#90Ỳ2%/$@$@$@| H r/Cgj:e3Qa/@E![u l9+s˸7AbLFlW,Y,YCYYE,"}dMaq;#"7:νfð)z 6iHy%Νtxj07 Eo⵫Di]x@ 0EխuB$bfQAa>9^;o9ن7SصȚyvWnk< b9m]Њ/ %Y%S1Jh |cKA콫!~a%/ }GB#ѫo*A`B5.RJk)atKd:q{-(I(j)~K ۄػ5@w6悫ȥ ̪ݛH,ğ&>@йo,&V/033͊C 7EsJ1K|EYF6k/s$@$@$M8߈&omM@~20K;>fa8gWcC/c$ A}~{P:X,gx졮v=0As:ܗ0uB"D7GƏoAه DR䵜atLr;ױ}}1}!K_ t |#L 4_{7"7Pd<%!ҏO#J%{(2eLg㧀~~~˵1vV!YaAYB57/E5k'Ѷ y' % v^+Y)2kbH"'Kkpp,Wߔeva3BZv.N|8/LƭYEfy>5.!̻fɏ1|=Ƣ45U"78Us0zX:~a@ylꐀtÃ%!v lmf Xq|;T@$@$@$#H /+\[uKB `Oߝ®oi(3 0+ٟ0K=?-+<ğ0+P}g&f7[C{Ȇ&&73+x·0NWBPV{}oѫq|$[}-I>\ |v0K~,²#x-~"l `͝wejf0 "cC1¬D%fEjzܱ4OcG6ir Ģ;ZW[ @Ɂ Xn|'O 7ڏ=[>:,Y%Y~Y-/2iϧJBy(jϧKi%aVcᗑX2Ol7~7:W:|WW-@K7L:P:chW?ccԷtٳ0Dzg *DX!sHDJn~TTd@rfKw4u`A,#J=5薱Bl[4|HQE?fy1ޱƗ"   fi ߿ &(|3?SX)W,25*g $l-8|IN\G= uX 0+35_߾eD/tVYW[BL,YUB%ˈw?=c`~N)f oG)Yj~xb&f ⺅WW((jwwhWe   F{PgB@~20>n.饹˸3ȍGPYIZ Up߳"KdtװVaSXW {!g K݀;f _ aVck pRW#W>h<;V}YcXfM>p0Kbr!_o+oKh_߭C=g|_HaVȱ-bdl- EK0)=' !%+ Skm__)j(zZ/nRZf1\ߕeYB"} utuK[HF;2 YbfߥV_ƧM IHH : p~gۆe~aP}8)<3?W8æWoƺr!f_?rm[B`[񇺔mCN_5B<'%/8U4`WaWpc+vy^ U;X3f&3%O`" 59 _93𬈓/?F"Lex~\_o<'$-`G"%~o7>0+\h(AR-iQyJ(_&YE¬p=vX 4 H^]׷=jݱo`Ƀ20 gĞy@q#oM|ҏv} pGi ˩NDfZ:Etk_%N#pС8_7Ln h/ DlxV:i OTKl"jȢjLVܵH(; \r`,;Ji! D'7u߿ B!d/|biF%a$"xĵ-fm!5˨_w+_N} IW XS0'/¬̙bЙاT6؎Q@.t,NV_weLxj|@S`W~~%{\7_ \G;Mb-M20˭W_b|I~Ǝ:Wsp 荦c;Xj ?{9& 2*KĊ/0+SGi")pV^FYCt*,H}m9^镘?->|B* W'!v H.ƚ{F 7ȁ 1+/Ӧ$@$@$8߈NmC@~20Vg.BLmxl-N)!e TK2=M{N>+Ze4%? o}j s`&¬֍3![3ACشX,mbRnɰ˻/9)&saWؚrŎNƝ3}lt+0+@QԄgOs T<|QWI2T+YBpL^Ǯ= W}ʧ%20a< :uYG:,wP]]+dZ@n}Beoh(L#(28#ZH$@$@f'=HL@~20+8G38COk5$ S:\+,c!urrLcFrGWLSN].bvj1 DA?t+_YE&MaVxTus2$2Э't溃2Z7ZϮ=fBk+4^,M$@$@$:7Bg;H XՄY20S9_֎_M_֎_MS hx _u|0<>-dߧ#B dH/eH4JY>sI_1E_f- |# C^fE:8t8x@$@$@$oD*$7,? lGL}$[H4OGf4_1ːn h/ 1 } f cf%Fx wQ Pe8僄'HHHL0eu$ ߿(̒tm:&YW0=M1̢1iˠ `Y_<~DCzQgb# }_/_&F>CakL|F PedOl2h! f 72,#{el!%0>e|_2 _l!Y2 $h y2[¬x wBùYf"YsIq,]f=>q|    0|#@Y H/ $0e`۠ `AO_uL` z2ceaVxHHHHHH ( 3T@iJ   7t8x@a% Z1+#   0=syfG(tSNr ˒ nݺjmAҶ6HHH pg#Ke$@$@$@$`VY+V?Z2$@$@$@$@$@$`KO<|46G Fo  h;ok}>$@$@$@$p~>daVKF: D#J3 -7ږ7[#     s%@aֹ$@$@$@$@$@$@$ 0PǀHHH 8߈4aO$@$@$@$@$@%@aVxy6      (%@i:&  6$FfS$@$@$@$@$@$f"       J D&IHHHHH ( /OF$@$@$@$@$@$(Rdz$@$@$І8hClHHHHH@¬0@d$@$@$@$@$@$@$@)   H|#҄Y? YHHHHHHQxvHHڐmM @PHHHHHH(3@$@$@$ioD0'     0+}:&M;   h  ZC @0ntō5a ܀?0\M~uND\-? 1b,@C5*pwp?d'c;-jC?v8O,$@$@$@ @aVJ0ƿLHt;10'` "ï0p%huK.!m hGs5)}+ 5sKeįZz9|[HWBe~n)f w     h%s7Mg4[IHHH XfKHX N-B/=<:Q3mw8cV ExgAW xmN fuJ+NJ]u5 b1fGj- jh\6I's XeYjkшMY~.ω`dxL^t=&cmxC\J,8ejB7i[ǐ5m $vu%k'    B~:ի4'r+wkpӭ_ѐ5l|^NCB.HZÝuXN$㻘Ĉ>܎[']Oe{ hࢁW⦄[0~c F eXc\=ndǚ"cs7?h5D-%E+T7G;nr`,(АnR3%    :3foӱ~c DYc˚I -ŕb_H/`}^p VCǣjeB/qis7z.L] @ظqޢ{wC5n$@$@$@$N_CÍHWb;n )%ƚRGiiRGn+~&Y>0=:?OD伶bG;v猻)ǖ`cCdj|m2$@$@$ N6z~1mKtd96:{6"hґ !PZޔRMȝ%e:6ovfivv2|EQa* N4/e;6osd"&!: hQ%:r6;r2S5Qӳ9Mў&ppHuouEmJn4SΧd8yK~VΫCq{(Ћ|8j"8.Kh.Qo rGhO[EN~rǎl2 $    rbCG޽<׻kHHHH ( 'ME>_Ŀ=j"_aV#K,s0֪vW\-5Gm{XcnZ~S$@$@C "¬9]!ұTDB6duP-![h#Z݂ZBW(qU_ːBPfeVl8M4R3?6H}mMBk=tRiOwg!#  0KIEJ+uktT-tUӪ0Qe<Z ;9Bm}T48rlFQ6P'R+GJ)ޓ+t ;QcG0%Z~vIOBp(nzT|)kKvX[_$Cu/&./ "Ktbعi 7;2`G81=_' @TO5QF$@$@$@&@aV>@Hq%ƿ100_> y)UOlO'>lOsuSsȑ 6f9+}  8\驎&QI ('ѕH4֖1TET+ELGD\3iAhBfYڙ󀩶f4tmv+gbbP6Ϥ'MJ$8nz Dϓ?O]#2t(S71KNdKA*[m(s,^ IHHHHQύHHH"A¬HPe$*ƿxwQ\U隷q8|Ar#~="(Xhףԧy&wg̣wE>簏J{xK<{4k]k酞۸G$@$@$@$@$[G}n$@$@$@$ fE*$` H1ƿ\_LaD4Xe n\F%Vȝ86WS$@$@$`)f|*6gW4mɸrO(M$2yZC͸0+4; cPHUɎf{bXe .fwNd+}Nxlfi6-p=JcgeQ- MhST4sg+r,fibBC}9K,wHHHHHPp#   H0+RdY/ CC`˽ _'/?>^/P5 Kl8(;)FS7G$@$@$%"*̲8Ij9vd@dg/֮YpfRQ,\E,Q^㰫(eb$C$iEG tpvzWv׸;WԑSe. oebѢ\CxlSEP>'.jDLM?;j`ODzj߼?wI>WrlT}lBeOV-NԞ*U w*VOryc     "qF DY"zI ƕ\u2TƿNxz-ވLG[(UuxC͖%.RlM4͞`b;XsX\b0<   aV#ϝ)RDL6W6,XL̖7d5Rg$j,3E6QNRck!IfCr BSyڵٽ-EkH$er,:NU,z^<"(Mqѻ6߉^c$G9YY<0kVER)M6gf?# P{&PvמoCkxHHHHH 4559?HHHH R(̊YK5_J 9>JOxtO"+Y$ %& d@i;Y7G$@$@$E" zC81-Qžھ5{}XDGna#3QB 2uإM8B#ZJwdJ\J#EdXH4Qt#-K%$d:+=7Joms_=DkTHЉynEU_^џ=R?lS=\:g. Jv7O,vGznZ!         0+rlY3 E Fƿy&)WSޱ#S]POS)棖>&VO2FΊEl]"銯ḚXm$@$@$Sz-~0FdkjEum-;[>LC T7[!ԦP[]ZqǘnӳBcn}.Ȫ[kgSC5!,A}%ľ4VieL: { ?74_#0rP74+,btA!鼡xA,&fX0`[dtF4юCA N؆G0wLNckcгOOT$ ZfE-sx-VO뎹yU$ Dh|Iz"J1N|5`˓`׵w0W|ڀI1PO+NJ""          s!@aֹ$@OYP1Cp2 e[HH,I,lzl-:wnSNaT|NG|^m:&]kBJ(Yضh\X OeȚ {*sZ?б@U4ăc(-Zra?1kzHڄ+㏣Mc#$@$@$@$@$@$u'x>`4c`I ( ;RVH$N"gaYX`( @ĄYe[{qͿI͋\ Ǭ YiBX,]!iڝڽ=~!`@yεÓWb 791{*,ZKY ݑ\dUaA\hymyؖG1`r`~.fhgo< ~˶l K #ݻSՎ4 @PY555q"H¬e$$ƿBYjpfo=@,.@EBƑ0CƑSj'гY_bmsQ蟅N2 VP23g>| 3g@>x}-$@n$@G P, 1j[pY kXo,0X\ aU$@$@$Ю"19 x'ihE %%YnKff[&lx0 +WSe =}-vz*t;J~ȘٌYMG6`!;A= bFtDnnK|g!x3 @wyZ-' (_-7    .i_ @X0cVX08'Ωⶸ9@̣-n#qƿ6`cq-"  0l7;㘻L<{egFWEQߟBᢾqո=D-ӄ;y]\;!1e[[(I`i:/>SSW⦄[0~cx|Yth ~n Fwix|r2 `G'0S' ?}䪤Z}w]9,?0iخBi$c{-ň~ o˘`% vCh1R$z'&/Uh+AEcq}S&'~*_k-cݜK'dsL,i8)̾fMΏz 5^(jYRX0d+vkpӭ>?m(/cPc%;Q|s|4܆5Ġ\7έ 1F:q-]?cCiKϯLQq]U~ekX]W+ƋO]7  ڋ<ۍfEO   hY-3b h- ZK`Ka3JK C]ťXˍ R bQא E(8W\H4Mk1nXףC  A ¬O_žp X4Z*mX[y&%+f Xuew,,N@^X9q)5pejd9`CH/¢nщx1ɢ|6[jv-nqe1{l-϶Tgڻj WF-rBFY;{^{|Y Ng2i_--S&z_0!}3X4 ƿ0M@?a[NDE1۶(TA$u ɯ܆?jejh̳0ܟ0 Wc{-ÌvƁ 90K-x1@Q*)/x癷!|7ryN<0`oKo`lVO\e{Nly}';+,AȠyN¬#e$#@aHHHH Pg#L¬f$ ƿ8؄. }\h8"bNQXβ`G7Su5)-J,n4KSDMK h.j8 X.߆BbOD޸XdZ [w iA8}dO^^p P+D ]"\Q]B_= SYjеdaXGM hǡX,nCjbLHYUlN^?BnB(X?3Yf˜>Ķ'wR 1kf>CnX& 0Kےd~n9fu{k*[CG ";;";~R(24Ieʕ ƍㆣ%JV#6Wi2Pmy}'oto] N kN_]؄>vPsDF(g{?U ".HhZЀ7*6A ¶Jq+.բB BxU5 B$ d33Id2gvKfΜy`yTXLV(: nfMo*=cB#Vkl VC-\ ]9E+}WjZoNT!13ɭf <fW*3iqҩ5fC:X[s be&7&кa`uPmO,j,jc]g\3oB=^nӎ駹6n3[u_  [Ra{o})"@0+E A7\+@0˵e`  @&,@0T@ ļ`G&&>b:_=%*h~~XKF]׷>br{ߖ{ YjR^Zxv^ZL C->XPMv%7]4N>0Ipv[$wjW%g2K=hf?lR٤?;)*ӚNu5ԱJkִ@6!1 <`~TMSpl3Qjs r Kwi,14fٵ|؅ViX >bc6͘ES(stsl^Y6/P@3&7 ]n&1s|ѪMsM3=_&*Z&qU֩6>0Lg@Tkwm5ٺHϬs/R0׾;U>?X H >55\ӥVpr {kq |6LO L5&Uk#ܷF73uz1~  E  28@@ .Yqq3q ̊heaGۄW}ʣم2k߳O|V{i5n`c8u6{jq\]Uuy/AOĽ3-Xgr/- IfUjݫ?iÆ /y0xt_hƣ7&r]pƞ`)Á`0+֒_C3+پ.fϣN[Ƕi\rOx6+ooP܁=6jbh_Pxb5tN$.$-h7\۵֬f4 q"sY3/M8Y1̬NnZ`Q4SrD ovlfkQ㚇o53ɚtuըBR=͕9{giVV1\謕{ ܶSQ̾6̾fBX+nѕf2Ϳd=a^!:t\^` !@0+,vE NYq; =TMfɕཙ=rB[/Z }6{{uj#R^ fyqߔ{i_@Y fKsr5Zj ?"BjMыx7SuN)`-ƋYf6٤bbʦ>D171B-`hƂ0F"{o ;K5,YAf Lk9C[Uv|_j3uהRٌl5(@09uZJ2'eMj0SvԵǩ!98sW:^'w9>kG}n'vǡ/i?,_(|DΩy%z`hSUYKf2cKn)OxoF5:\`V\` U5|ÆGU7/鮗;V|CCNoKքN6zX=})KC Gt4j·:2d:cou?a5A}KJy @ fR5flj1p˪$|s ;-1 F뗗j3t Wo1Zt{uq ?;قYq7ͽpx@LcE  %!50aّYo=,`R{Uk0KY?z~77>+蜭EQWt][W&o7.͖kتۺgDZj|,;7٬:|Dc5c5 1Q̄n\.r10.m Fh&2_?v?yE)KӑӯkvmZۣ {}lBw?re^[\QۇFƯ* "sɮT>J]sV|:-&H ߉7f/{5 ܅z;OV [a}&檨t͞mj\x` $_`V9cz JzhЌkʴ"ZX]ܫ /(bYdKdYFsc M0kNOV|z1;#ɱ__.X_ռ5΁hõ< "@0C9iJB3̔u!wqCEC{߇y/t{u`ch?'^`Y{q[GK+g[9@ .\EW(B&LE)G˟z{4ernfɴ3MD7&@J`Qb=oi~ϛOS} 1x\b-T*f(O2)kʲ4`KVԵ2=##Iʝ]Y=;SSOA}K675 2a2ڐ-q^q?{MjaTʌ6` hyUr2˜êՓn-| 6UR7f6}KNھ3ێT~;̌\{f֘x?4j4A#SG&ac0Gp-ד>\Vd}cI-Cv}ٚy^|]=%܃r1sU}a }uFW}q:yҦ~@-@0+/fSk ĺЍ_ٸzoqFKn;US rr̽е{4f?>k{_#bo7xX`V=7Uqy4ɽ_@h`gF/~pMkmS<-[4Co Tߙ̨T0yh t}D"檸ԄeM0Rgb۠O=ɳ#mb+^jfGV橼 23k,oGR埻Ac'=I%f)&]b+xϾ\c۹@M2/\)+#Y&XSo5&4|xm/#\ΝGgkLαi"q׫hPHl-O]0ɗЏo̳,KyYR*ӨN tmCk!:taD]x둙?hA=pX>y_jzL[Ϳw'sr֌j۠[va6λ.GR?ݗlZf>&rlr-Ӛ{+))@0+%B\$@0EȡB>d?+EOe͒EYpu) fuܕ/Z6Ϝ{^ߚݸtTf^rmHYo jwFiIb=9d+4ep^@Cfu(?'w,9B{ WGjjA&LM `}}إ~[GY,a1=ضݫ߽x H{i$q7 ڈ{i! ,nQ=Q]}:wP{oo;go`i4Ӧ-_P^ihygǘL888xC5[Zͣ}{eN-srȊychởG=%]oR<ڇF5=Ì!#ΠwS{֑NUn\?48u$o%3e;aטφU:|6:O J%-!K`V,- fYGfj9?,ghb?>v1l=BooarEg󿒩'h.jӡuPYu6tr졥0M]J&}vcbOqB'hDM{q1,4s;  eΑҹ {T03sЎ,dlhRܗTޔiaY`Go>MX mC5^Q7wo2okwҤk` 3D.` fc0[[ h-\ ׻˶钟\[9Dc$s>@ﱷₗUih5-͊dY/j)q,!!`   K̲k.״I %ez` ˰Qx4~ jw8AT0\/m .&X᪜C?6]YMzuK9|UKn8SCo翣a!kˢG/IƞEwoo>Ua&=ՠW47tL͞sv65iǵZ_4e,Sg6ۺW7f3laf0&HvW HfoϮ.0&׶O͢[3,oT 6, thiH~ :VA 4 f])SE>zkܿx:lNp'KS5K>~K.}ҹ/pgy3Y2& b`LPST0:p'' z:p@3r=_ki؝)'Xv dLDp^}oȷͬiWտKf^!Ԫq,wՓ   fƌ@2@}foڴfYY>۾C\Є暙)s. ͚DFʟXXR=S3wUѣ]:`owDxurx]}hujBY 6::}A ~^R|;WCE?Y|>>vл*M= f}YTwil ̂#y}cpލg ) @0+@\+@0˵e`   RY.-,B +-@0+Ѣi^D*Z4m9ߘ 6SIE,7X0E Eo~:9"[R&rG fYZm}u?v|Dih5-"k[pY'ortmD|?l9g}MW y@RV`Vʖ@` @@@ fU, ҷ<9Dl,3+g1z_zꣷe ʙg*cY qrK!]0Y6ಁ:O6(bS|;\Eve/Lb#}|m3W uWaWuz EL5aMD)#k-w;]lcß N *G rj7  tC h'Հ?\|έ{ϥ΍+V/#fR3M6 /wX-?UfhBY?уゃK2?Awл`ejcuYcF+mS,7벳 @"@0)N Īg@@HgY\}Ǝ4*6CYFYfon&3}C덈vl,yLXPXJ٪ghQV`<+5uPgzi.UD4Mm&4Ҁc?՚|v)LZ:ᑞՌkm\z5;SUkoGw"oL@n@뽴x~3g@ ftyf9t@@N`Vڕ#)@0+=Ψ'@0+y֮>SDy0+rD"ڱO6Mg-t-?J5}Enaheӄ 4Z}/jU4q](8ebO=|EhYR^Z<ʄn>_w\Ky߾~3,k`w]o}u)e53jÁ7":;ٚ45>4sLܚjnYKu^^fBkCkG8@'@0+y֜)f_1  8[`G@(@0PQ :J8ZoY>pY>h?R36ϓo)kf&2LP3D> D^ٙ̒D<$ւY>2@=G2Kflj`3r#; b[z;=mó]{ƈWGxb<® :S)5E@@rI! @Z]U`ֱ rOpf](0oҍgG.XЂwC`3^it s@3L0křzWjjPW# sjʅ--xX~Q)?Y76=8.? a:ll;xUUNĬ]Ew~E#3ږ4ilSlZy|;~<[Oؤ>8FXzS7cjkDb㼁 Y*]u,Ǖ#  @ J #"@0+]*8;J`VGs^@@ f^M{f@@C`VzԙQ"i/@0+/Y`V;<  Y*]u,Ǖ#  @ J #"@0+]*8;J`VGs^@@ f^M{f@@C`VzԙQ"i/@0+/Y`V;<  Y*]u,Ǖ#  @ J #"@0+]*8;J`VGs^@@ f^M{f@@C`VzԙQ"i/@0+/Y`V;<  Y*]u,Ǖ#  @ J #"@0+]*8;J`VGs^@@ f^M{f@@C`VzԙQ"i/@0Yk_;i[^1rU4pְ~yu^ΨS+(ጯu z%`3D/)@0˙u   ҷH+Y*7hv^YO7|#^θ3% J8+rF^A g|%:Kg 3F@@@ }fo9 VUn~zrN͸ZY=^Y-/,g`Eo%ΪE@@fq ,g_4;^S3ΩSE%^pVo f9^Yz[@@@` @ZrVEE095#ZY=^Y-/,g`Eo%ΪE@@fq ,g_4;^S3ΩSE%^pVo f9^Yz[@@@` @ZrVEE095#ZY=^Y-/,g`Eo%ΪE@@fq ;YuZԯ֔18E߿}z:ER̕tߕ^[rU0^_[+TN>$}gkҘ:)r| ž*=U61W!NpdmbuM߁q:qjW+<2ԫ^˟PUWFe|l: ,ԠWCK}kKS7Llvt|ٞ柭@}]9Pufs(RS?/&5B@@"@0+!4  fhmjʓ1sKT,7y4z).ךicι\j:G\?QݙgG7 tǠԯK858+P՜-f!zs -TUi5hMӹ^j]yMtu= ׈T &hZV͎{M0q%p⧫   @&@' -UqN=ݔ"OuC{k_* Zs1X*_檍kJeL-*#VJkxS7S_sud龻ѫ3~y ?&eRpc[)?uSLH2dpA.Нt7c%]I?7?b6:k@nwT9(hWyIN'vQ._:;{]?S o@|L.ٶ[ S3肬+uvfy t:u=A+t/=Ow3YNv[~^v5}D@@!@0+  f5hyAwMZ$ޫƅX5֩cgR?q|ebw-hjf6deggV%橱ⓩ%D'l{wB3Sy 703ef"}…:WnڠJz $}VKOЅWWKUI0Γ2Ϸx[>E S +>_(z,mXnٳcK]|Y_sA'Գc]*uV謱 V$^-?/'^@@@ uW  pW0N 'fjjiV]#[.KYq.g_47l]S f%-zh)|,7, ۞j{ާ .k{[xv7M`^Ԛ3303PTz]fxRG羬WK~7Kq@4!ig\h{LPBO.}s~G^.I"^ͯz=rzܼ1W+,ԋ`VjD D   R* @ n f-1)y*}B3l\ t[z2D+ d˶W?u/L0+`V_{:x*}{-#J7)nۇji|5 W83ɵ=NUYw6}&Zُy 8uNAَu]"f{u“l[ ߹f.ne0Z+~ѯ f9|t>r=@@@f#8E,3fJK`VʖՎ|=]zY2<.h>rv,}3UWH6ZMnM%wy==LL\ѭMjX.dBXz7Y3#պC\ܚw6iӫ՝|7swd+nWZ͸fE0S~b]븈`+HSY`V*W!  f57a  b,']T-sV]N?7KyY[Hs&r|(nl[2CzJM.m^D?ܣ!+ƏP~j60cϳR w|:ayN3υ\«t9N=om׷a.Z0emRizd?_i>yryw|}ꑪ5GH0+(W/@0+񦴈  ԥm@p[0kYpjiʪhLOsFMҜU)-^)ԍh&]+?i%ӾzOpck'M:3la "dM태_77+_-7AMz%*-Їn0 fkYeA5^o>q_ UMo%C 5$bv^~B0).ƟS!  $@08@) fIUU IEU~Vڭ ̵Ra.ͻSӬnE3feN &t:seB%5n}.>þu?;OKwg;{ɫ5 )x%77&tmvkt 7'Z{^FSi` ѐ@mko30Ao"HzsDŽ5zTfƃiH<;l0iۗ E<إ!wξy\3L'#hE?/2\B@@/@0%d mp[0K [U=[f,U*_:[.KOhh$2ypag貓<\$xuҴGhon#pPLX.twOSGw?_h7֎}LpEb]c>o&JpEzWLM4Y |&ͻ<3VU2{ڦo«ita{ƄW6+>cnUW6i/>ew^EYA0ud)ƟSS^!   GZAHqjf#P?q2q/Cr(*Xjަg"G'<897,۟jc`WL!OsEk:lfC0+Z&5^Bs^ }:M?3D#e =/١^)kcwG'Wpÿ^W-7//Y"F qny9q:  ԫ =Bh7L 55xԩSztv +8&~pW}tӍmKsأϾh:ONR-^)q[ŸN^vKMM#d21;bڀ.?/%!  $@09- @rJdr/㜨Jdrvru(C㜨PDI&D`V$i܄-   @* J7@ JeRIaNIf%2) qc;) ; JeR^IaNIW(J sNB0+a4@3~^nF@@@ fty @f%J298',%vDz%J29P8',+Qiz%9Qg!(IA?/77a    R: @ aFEsRvY LJCN sNBFWRv0ʤ4DœJ% !L@@HiY)]: (YLN;99Ή: DI&nl'9Qg^LN;+9Ή: Jdrڡ^qNYf%Jvh.M؂  T}CHQ&!~ќ愝`V(7œPQ&!愝z%2) Q0'$FIC4f$l@@@RZ`VJ!$JJT   кm}Ef   @ @dtMJƩ8   Pmm- @Y Ĥ)@@@ )@:^`޼yUWW   P֜9s4c !\Yl   :VAG@@@@@ )@@@ J &M!     ^Kv@@@ >     @Bf%@@@v Ĝ@@@@@c u솴  $S`V29     G)@0(8 @@ Yi@@@@@G`VݠO41K5>fjLT_wHNf>M`+˶kY6   {L?Z꣏>}x@@@cfu;gE@E:522o*')ؾo"V?ij3*pj7- ƺ>"W^NuȄ%){f /eucpZpBTbU" @@@x饗t5ߋ/:{lD@@xY_z "y!Q}Կa5{?h(S룙&]Z[iɊ/ZG^G :k5%yh?AB8OMy(*G~b]Sl>d; >d샴{5 J1+_N}b2#Syʨo}-Ҷmun'GE%wk%P }URQ&<ְUuVhKuWNVs53PgAoy<ߺp BOZ|mZhxlVƯEgڭ{ t=$+S;_YS[s7hŴQ   @ 4553Ч~N;MN8ᄈ@@@H!/@ 4kmW}/¤ѻ,?^nqm6w( -7{}mʛlWxGn:>T]kI^O?V%Fa깁>[?f}!h6\ooh/"7w:ظGv-oQ/__w|essCAo{ 2ޕ+}Plr]`?xnl_SG   @ G?wqY6   -C@ yVtR=6zl {,@ )1G~I=keFRҥ+{lZoY_C9 N+if|ZoBgN? ݇@?sQcV6Ck l_mQx kՅ͞y˽yQum]BQ ,T[4|`0K޼#Bucy   ۶m ?Yx   Kby  hPuz2 (]XϿ uzLM6ᙀ|lװaôiӦ#B@@HC@ y\%j77??4Qp)CW{W-}XZp7hawmy`9LSِB3$6cV\ eˮ=c}mg~}^/7{;()zƬ /j񛳅җeٮle/Z+hF=s   /R_#z pL]»>pOCeJW{ˋsm >ᰖ9{+v֛e},eo?CemfZQPX(*/ *z'<ܷ`*[|n-c* ,\2xRk{fKj7xs[^HU蚲j] w;<~7{Ct9s7@@@Yv?9@@@ 5 I;EFqwn?nZ=?UNa0ǿϲ]iB!'3Qte fO[haOȼfY-5* yxh|_p+{Pibn-:CZn:md0޻,?0W}f3fA7ا9 x]N,_k"y L0?Pe̘T+   Do^<@@@!@0u .`, NjU˻tVNh+-SSu*lSf`,ѻkRoN0e2  $fHV{wWǻ0۱ڋ fS&~k,`VhUO2 _P +[H/8eE.Y]i[3Xo dm[9E \E`F7%Wofދ5+Wtx   /@@@gguܨ @ 4]f6闊zWUz`X?얿=-u`<`RL٦櫢~u2GL5)vM2T35T2A!M=~sscƶ֋ew6EA~^#mƑmj" h#zl=CG (܎օ){2iŴfKV=p&^:_'%*{+oZ0Q#cٹ׸>cMvFֆszn&fbU٩{;lG_9s˴1=x   4&_O8u"  )D@ yó 7h|u:<3يreo^2f < ޒzn0V-1 YF]%gx%}0FyK7aƕ[X^ѿw[4} ?46amw 1V;JVZ ,Ot8ЧY߾b؆zZ[vVx6{sKf*jv6o5[^: (;wYy%Cg       3f<@ xTSW82@5Su}2UF'myQ]MgwOLL4hj5vPށmmis4Ԩ^':Oous,{F#vKiӖih 5z[ݻuS'Tqɚ8xlT[L>k:okJ5>xs\S552ZifDK@@@@@@Hě" i*PsĖ0KO{l߹@MW*۳\ccK%{+4y.=G Znܴ_XKDZ       Lm΅ v_6W׮СC 7_c#k}Z&"jnaO֥d`c2gLBϛjpܩ zQ̄TZA@@@@@@]f +" @* l]iyBA,{_ KP%?2f@@@@@@@^A G)QM}>Q|4X3Ke      Y\         @f%@@@@@@@@@Y\         @f%@@@@@@@@@Y\         @f%@@@@@@@@@Y\  pzw^G[P <_ӼJ}Z?_t׿;[w٠˟ѻƩw &*5c`,m/PYp.8.C.խ]ItIxi@@ ߯D7M{   Q3gf̘/@ fB @\Poιve{Xq ;Qi`|vne.ez4<֫~]!-^[mduϨ?p\˫5mx| ϪVݫ~~nG_s!  kpVmmkp,'W#$\`‰1T9Z3mxo6ib*WjWhTF.TfT)Dk t]dag^kL+4V)h|d}F/?dbw0y̥b8g,8 O E!Y.5  @* wq-  Zz|  rj7 @T.M<ܒ (.hF=[u[lޫ~ǗQJhW?a*G%+k](k(7ekSw>ElAhʾh)Vq&^W^/Vnj֚yi.jcg(Tjh>[vU*4`wp3_@@ o rl8 Ҳ p/Y.%w,!]`4bfEnQ&{U325-˶kލYy \IO6"c\a{W&V?iYu6jƥe&jȞU?=x26jXh߱;esncI`c/ӷi8nb_e6-0 NWnq>IF:\뾮>伹7^d~?_="̻_ ft`Wx)zwp Q44Yҙ*H9^2N1GEC !Z,m )QY+Փelcj%Y Y ҹOyyjؽ;Jԏ=\Z\~b[X}2}G<ʹWrTg72̌\fi-ݐ̫çWāgskɑĶ)VŊ*|=)9 6Z{xuq-2Q[Wjݢɣ|ڹ$SbW92}gXkУ|>uǻo[H: [.mJ8xQIC?Q:ZYCI u VmKdz+vqGu\cJy B&hɪUWc1\*z-IҜxۧ,Y=MZ>Wޤ#8cM{" U/hWUPCdf*YC1bK].`NvZ6vڰNv[h1335lЄRuZnBdΟ1A <im)N04fWGMFٵru-+>fg*+;6dPt_F2Js2r`V*l>qj=ZMλJM}85u{sUI:eއ]C{wu_ׯ1aʵ&rt%Iz?v̲1} ύʹg6m\x[d[  @  E  5B ϰ@$p"ą)-@0+@*prJM[e f CUTԤW>332kfoffOf"2zބ"3K7KY;Ӳτi03Ω}Z8N^lGݻ4=DbVY/Zy><`~Dn}<3;63;)N Y/u3:eex$}rTgIq~4 OJ$O OlR k 2{fHF^,~{Y$F׌9E ,ݖSvT5cL|4c`P,V`-a‡Wv}j;Y:r6m֢M *>-f2g%4ٵrQpMh9~*<>UődW) MZ N:Ś=}4ah9<:!WKҒ>"kf=^b8\C83\&(*m{mM }];Âi궱cG\ 87͠1YCśuc3F3]dqcr=Z7c̭NFꮼ*Z'dxh݌kZef-pp~N"e @HZtFd4 _t>fpq bGzΜԳ?|NG{^ :\,|H`Vh3 OfCkj3}";cϜCmgf>axGZly"X'٬k#YS}v(%|ǵ$sYжug5=e:ub\1䛙*$%{?%*.gfZiffԺ:7? f1:iόe7L0&dBKp*m,0N%j lT.s]ñquXX3\3ב- *(c>SHdf v\͹78qG΋\?-?\DŽ욺c|kf2[an'i\c+1t"e @HNFj7 _<fv} 3zҁ4jNھ h!w#4+S *ыzיF9KEIMj5T0K|)}q'q.ξxٺo&- DvsMH0A['GƗV5kg 9a!=>*l|6J]3' 6@gթB8frB:lB{jٜ<%GUW2ZaWJÌqzQ,3{,{^ӱqփvk(WZ;2<kOÜ\[]gixkio0.+,3}5!tOi<2]d|5*SS&T7L[  @2  L 0. י_x[40';9з]믷߫݁GGl5v̺ %E0(9a 5l4 SCd՘Q/U~3R5$ٚ.fP=sY*=ֽY ӂgz{k >`0Y1ԲY* phgp=d&\ԥݏ8cu4Le\_&{fCt7d p"uu̹ C 5T1GHi'aa 'a1f.cLԼ7@=Q$d2KEώsfX؁ X-V35Sy :ca&]9>O4q &G$s\9; XXM©DŽy҃a\dY.rirيL(K3̘jÙu4k-(xiuЌY}z2wet]޳_fgLk^m3m}419p`m;8n;}uLYгjiڌ,*uW.~ tۧꊡY{ҵ fZ}~i`\xmYg/V-Sp(Г*Q=r8ya!:lv=|v_D^F_A$ 5$A9v0S\eRFZ gQюz`wj \y:kG$uw艿LmWYP1t9j=? `־<-d]9_MݕjM䄕*H#ݲ^߾lՂ#&cӬʘkvpz`pUZѝM׭'CoLaa`(\>9ShQ|MksG  @?J bfŀ@`d ׃o ?lb3OTDɓkUVӗ2eVafT*1˖m0/*u/z[  @  d N0&3G5ݤS=,gxu2&`0k`;S;ނZ$̔^)=|XiAjQa͸O~ٸWp);E@p"X7l ~# pQ|u*-=CdžL{z2tq9V{ɴ1cLʄs vhvE;ϴ\Z0~+u+L?)}PJ_I^ihRԇSݡ1d1X ;QHKӸuqKS'^6-6uuRu_{;*oVtdS_S }V;ӺW5μ?  @r  5 0`5֛tկ̯,4/B8u^>qFw4:aNYf0?ѥ_Cf4u-j[>Ut>t\t^m=3'+.uesu{(1去43C1ܨWC\h`f\^0ow-JefkfDc۔5sh~U*Nv x_>K` W]mg{ti|F?T "x=>RzFVI#@@%b` M@ bY޵L32g;?̨>,cѫcFz[O+kfj yl:Jv&57݇¡/iwfh{-s>=s wPP\kƬ`V_7iw?ނZgy봿hllcp.[a/|_ fy":̚q%ڈMkcHϟOVGBhTzM0xH/Y R@"     u鎜 @L0Gid7!+;twMX+j`j 0ᩧ̘^c3gkwȝrծYVhΝfϵaÇݤ/w^rnt>B)ԣUu}T;]%}Zz[/nauoRYq }=ۖӌY2vB`~8c9¹ zjffZ9SOfkSx")w0;ֶ.3ӑq'k(>,Bʞ1ۈZ fI9n~J'w2!$) @ F`V O@@@@@_ ̺tGDU *P%gfkf:R*rzaUOx{Ynv豅O8F2Q 3?wTF%+bfPߞ?+ u7=9ʫOX[5l(,7_kͿUr^:Q#eYrtS͵Z;{ԐYzavߥ ̊߆==gZu˯Wk5b5nT3 @_0t,     py/f]%g#&t 4k9]f9Éf9èfE1+" &Uf -jK3TmY+tʯRwR8`Oc=3f銴Q΀uZfd4|iZՆ*=uDT;>&/tkƬ_,eC1̘d5Ѣyz5iXqN\iB,w0h\bf #s-*]Bh|3c eW;Sk}7tӯk>RYJef۳`yTVŷ_εD]niwSJ%jݨ<ҵ*%c,@l  )E0+!KD,n@@@@b/f]1VB@T*&eԼ /gua2֎v\,Pp(*A\C:G?O9ZOXؓ mk f>kLo0֤OSx)]J0K=ZQh-)jsko[){2WTEY qV8wmRUs:}HYt$ܱSڸxM~#$+A ,@0[@@@@YW@++: 33Mg櫨v,5?wWg dKϗ>'ó]I嚏h5іG}j-)/}CO.<$tYw &=;.:Ӣ"V4ht`7jЂMvJmW;`mw^1K<6|& Im{oAG3z Ofv 4W<@kW@a     I!q0 pU}YdOYYW7ډfI=z:hOeN{}37tYj5̒7:xyg*y.M4K+ީYi5F׽̴e4`.aC>>{uӪ} zȞ'+r'o'5},y[g)w|g* #:Z3So nu/Ɲ+YjyTTk߄gMhuPZլ @ ?J#,Y     p_ ̺b4\Y35}BQwLX>2~:u~>ۿ?{V],XY1j_'V>Ev}׫^/:⛧kVւ.z`Na=>WsYN6ΨrFgO^w5WDz@ ?̺fe (@0k@v"    P_ "WQ,}ZĨȕεZ#xNF͘5QE?=ew~An>y6Amsov^8鱊殿G _qm𕏮W;3k}uex wTeo{oQԬ]ErU]^f%TZps fZ0jUr5\(S5d\F,U$GxpXfwU@@@@.S@}çzWץSץKxcTX[線}]tÄ e}f˞/z_kdZFV^W:kJ˼E&6`xjK::ΟF{&O5vDVTنﴎ?-<}>2}e-\'>9Jz%G^R/["9~?Jˑ'@0k՜#    0l/f k* Q|.BUiȪB}E.Bp0^FU"^W5E0몱s!$@0kH\      5Tb#8ul/`8ul/f V*1#8ul/`8ul/ܟf V7WC@@@@ _ JRI{HMFrk$6į˭+k, $ĩ=A@@@@ _ JrI{HMFrk$6į˭+k, $ĩ=A@@@@ _ JrI{HMFrk$6į˭+k, $ĩ=A@@@@ _ N0˯׼LnAw{>V'yR28rKO<O fߘ~9fx7֘j1IZHݟfE\B f%R5     I.b 5Y7Pq 3)IJ]>P)M.򗎵_N)U oП)=?[֨R^?.EBݬWݭnMpVjh[>bʗS"S"8bo٧)⼿$ZXaT)eOѦ%iѬ;TʜЀeZ?;E0kk&@0sa@@@@RO@*_^Xo 5I9Sw~l}U9h^۟=;OG^jSK]M >]Y [hКw=7S]L1I޲SuS?LMnG~_w뿾vSx6eޙw;Vj!o(c$ }d*G,354aAJcRcԨ_ekIeЎe8/Iqd"a=ͽZ>IK<&UIIa6瞗TtoӸjT7kOPX{ 4mvyK*,CI)Y)UN    p1:,1`ֵ:pxW&}AdyLӏ>^3;Z0ߴi۲cz5{C}|\$fwȳjv4h%{,_T)%#͜#;Z2˄|^*zļܻ%%[KzG.LG'ޭhYzvh^ R^QߨQ+=fARmے;56`VH YQz    @JH`3f%I0+%:CzPg-В>e&ՂYs=Mkn *AޟaMߩܫi+{Խk u3fPB%oFp/_gkl{(ZEC Gt i#z`&y]Jx=[0[+7pOk*=kLtU\2E0V7%@0+Ao@@@@Hj:n2|8_P5z'iR18B0+yn7_zY :9˔g=] >ǫ=wi9A׽K]XJ)R!XtO\]5Erm \BR^T$SG-E0+V$Ĩ@@@@@ %_ $0M` ܳZ0zF M0;~OD9{k##bEyok-Y'?TxѶmyī#/FN0~ro> sUwڠIJUrMe O;Md P/oܩir?9Q&U.zzH`֙NX` S].r'.*Aw" E̊)    \fF7|IXS89IDAT<]%:-=I=4w}c7^:5>3#bEy U=o6IH7_?S=\f,9Ck[cg;˫Z8G;7ߥg}6:`~iԨ!Ǖ;nx+Ա *L9(R!8rf&,(5Ū"{bǶ)kjID$%IM},;xJN q鞬E&#i#%W*3tݡ.k E0+qy$ Uf    \%7'),s8=ݨ29i<3GIR!8{k̊Ig;R63K{cz|Y{j|5=+>)w yLPΟުW~neצ^3,L0+ =Zj2T %DpߢuiS)X3suVGѠ]Ln')B5,GN~rny]³Q׮)KzYjܹRU-Yڥ/YV]Ɠ*R@@@@p1,c6f,1N}qGS|8E m8d{i}UԢMވ[}$N=QJ{Cϕ);͚3u[Y5n) fY~,?^<35+^OS'_N ꮩ LIl9ΛVN;ıT19!  #U`Hgj9AS˗nvN[ܟ_Q/P*dКE]~@@@FVqƋ    @N7ȳu:?ܹuM7w|Hwrƛ󤎾xf͜?Kccf29sRYwZ7NS0O~ooMޭ[ٓ#`;[[M{&pM39GC>{/3ϩd03[̸c(Gtߚyʐ W[7cnTul*`s;xL gFvfF3z#=]f>?̝w?z-2}3t^eu5GMoh1 f-b[Rx}dgTA4m7mW5&#WnpuHxeҧ, >;-3Z5Խ%ڷ ^A@@9FN)     #иw<+ӟPeҩDu]fyjֽC 6&rvPQk2=Uܖsm^}ѥ O;dϸϠfS+wb(]?jΪxQUSNvzN[/ڮ`v [^v/G]˝TO!?ey`O\ۡ_-W٫C ƻ?yL]ܭn;Wʳʌ"]ECTN+MV)=~@@@`D Qf    \{>M :d>XLGOnmBeIxH Hk HV|`6.}iZfMdyeZ:S}4D*VsM6YȬIu&6V5R`̏GƩh 7NaBgSAj=NV̌[0_z6n T׵K17ox~zn䨬KD陊eN_U4t` 4jeGSwK Ul2%&htj)᫯4AUQk 9ezhjO`nu͋ b[Vj4Ý=xfMkh_ñ7Pݕ =%j}?@@@A̘e>5@@@@@'<kOhtɛMG̢uf֡Yҙ͘ 8[{fcr5<%ubBӆ_'=WF[̌TSškfmjP3f ز8xȏ3WzhF/oHf')|O+׬70j(0CsJak+YqYP"pv=0iڰkd/)1mtvfSofΦeY^8Yڲ,m҉m-o;/{$'槿UۖN`1{O6֎ ߂5@@@"@0ka"    X=:yY=Q=j>^/K'eO{WfjY [2Sڡ{l=gN>=i8chYD*n֖я;Wj*3;̲GYf׬{y%*;ڮ98H;r+6V4K >%Viˮa$j4oW ,W&7;\{D3?f:[Մ GES ƬO@@@$@0k"    -@ѓy͟o",kҬ t=_́JeS5ӔJt*QQkC1x5'emmZ?r8@Vl`wsFMLuh;u47w;YEZy9vv&*̨5`֐+Ouݻ4+&3y|Yme:ub}y'J+*,34 X0Zl{̊!<^@]1 6)h8 k uϩ^AgGF̘^!]u ݁]@mF[䬡ߎbCv]psܵ/(\6 _oOs_AjE잺~GƯн~   #X@#x @@@@@$Sv=͞P%6t ^sBT\&wc ʭvY݁PfяvM4|76A0ۿoh-PrG]Oqua~~UIF@@Fp:    W_nh':S\,H"f 3V=G\58lG:7f` %<_V8d1H!MZHʞuیc4gKӭX̡}Su2c5XR2i,JmWJ8׋QХ]C%^ן>:sL'#G"kIf甫(EҦh7gQլ:M@@@+3F     %(Ϗ^d8k@^YUpypzS^lEWNs6 T*)'Pk&ErVBs$g쪨 7>~Ff{\}8P!+O̰)}h,/t3-4Got}mӷN[dqf̪[uy^*04o`<ܧȌYC{9xq >u4Pc[-tp[`sxLfwsHr.q\ @@@&YS3@@@@:}>)cxeg<^eLM/|34~lh&&{tCim|A6}t['7a q,~_}^gLءc raWM=gTJ{]>1C=ޖE1ck eg hcF{u=;VAn\h0Tgg̭ƚ{11@@@`d Yf     &7,R3+ДeVʫm46;^دy}sYѥ!٣}fMէe!ˡ   fq'     I&rl~;~9ktd~EORv"5[+5)9f+z+X*sǩOjf&+J+    5J΀@@@@@#ٸOEy˜ G`~d @@@>YgK     t~^)ia/m   )@0+5ʨ@@@@@@@@@ ̺\@@@@@@@@RS`Vj֕Q!        5 u 4        Ԭ+B@@@@@@@@k(@0si@@@@@@@@HMYYWF    #@q=Ϥ[>֘+0{hڊ*j֖%wO_ٳuʝ8+ =:qgr5~XMԚӴU%jۨyp%^<{Z=Z;-Iw˗jjE@@@̺@@@@@ Է?WBIGO*˾-{yi&-tEGߦՒ\'4[? 3T iڻ2S+vG\ۡvӧL:T4kh׋[S֢MR~+^půzq}fWK-"  /@0+k@@@@H:%.VNyjf%W}ZjyU۵_BJez ww1^Hl[I6tWmPii&-!lBJ8ĝȍ*uoZTW*roQ/rŽC#i)l+'uVl봚U;̀Gԛxy2{';f*OΞ2m˥~"W?Snvo{Tkّ+/_l]t+^=, R;?~ ɇC䞳˩c~$\xUʭ-^9sW%?7_ FOc">+r߹SF_{KrX;zlO|sA?1kV+ ]S3A#l WQߒ=_WlzgufŽ3C@QM._=\z3ع{ݛG1OyfO~yO5[OKFĽrs \w\ ݢYٹ|o9"G:?|3lOyMtߧ$%ZzǬ/k?G{'NK_kvǬ`,/z᜼A7x6_~<[>~v乧-䈮7`|sdLNw+twȉ/~ q z)=>9y콒~ Z^ϵ^b?-}X?{;[U~&Mmׯ]W  -A@@@@~IZN2V0rgjTXsA;TޓW+c&k]AJ#9>ǵQo=JG&{ct.JRYwA}Pv%.?_1k uZTOZY[joNU^ xg֩+x㉵S(1Č`-kJWSs_:*zjjYn.ueѺ+ùK/KdZKjnB.k9t-='6l8*F9cYp,aS|FjԲ^Fz?vߎZJ'M;iUիD盱]@@@ C@2>#@@@@@@&˪ZTʪYMUjCu]nTmHGfjfEZv)[ZɅ@ LT^SFp}KU .DVl+ hU.ժBSnk6N3!tF74PZ\$r]բ0Ok6c q:n*4`YUVFa$_RNWuZX74O~+oaŖ :}ju]x]v^ҿ>˫jzݶ^on7Q8h2JQB]:|q\ݞ~~*ιд"{ b<{}fvI~~W$jz:MU.gCvBB%l'fYakîY0/Mc¢ @@@=fI     v 6QC̏(GZ; ݩ\$M6F.4S(֯0 E;z )E54᫩ ~c{j75Wf.?.jeC,6\4UB~?01(_WLYcBE0ssIf̩s鯿XzNi7뷭=T7&z6Pެ>Kqhceqi|w;~G "Vۛ u1YWk@@@ K`V !    d,&с&%*/RLxbzsb# TF%Şo\Ya A yQ;<φQ+2jԡ'w_gIY d͍Y0O 3e69fϽ/(;n3Y"s5Ͻs/gyh gv6|dϒ{^q9j&f0q#KjM7ﮩBk5    6@@@@8` AWj8P}gjvP f [S3ݒ xGEvؚ#&5~ǛjssD2B.E^=6QRJ39 69ņEG96W[tSXmRTuBUfg${ f Nh.ׯƃhd~ۛZwtX'3 Qk\6e2,4כnfp[ͣޭK2[M-,y%ޗRl MӱxԼ#Zcusםb @@#@0k_!    ؀\z~yGIX 6I.zϴc?S)GqҁAꌢrI&{r1k^0̚B]SXd-GB(s\(䷑{Ҩft.7O\{\] {757߷ׂ_n. fЗ!3NLjZl6he.Կ31}}2ܴlk9򪋽L徶7ɠZv7Ϣ*Lu#$iU¿rUb<^!   Z@@@@@0\cKTodvQʫ`=nF+-AH4ldU jm3jz >kLU-U²!j Xኘ.Z n=QOU2 LǪݹJ`33)~]U{at~9诹6^A#'tфltX'c}1T?~냖 ?yX`. f)a\ 1Zӛ cmmј_vͺyL_ƒ2_M糴:Mang}3`.uxH5MS MMxw9+@@@`7Y9    ^r5cI.0 gP1o&D⊝n8 \oNnK>vъ-6tȥ'V2Nt_1jQ;9=Vӷm'*6gd^ Z.L׻^X.pMUN꜎:MΗi73YIO_]y 6\.R:,}Vbm;Y!v-QzhhLIZͥ]>wJ=*<4szt04ziuPj2F@@ #    .0XDŽ0R-Fz"C)j TT !'z߈vTJ9U!)zwJ~;'jUM-1U+͊wmQKOT^>aMZG1~dlv)4R!n}KbǫYkG/w6Sv;EO7V2;PPQ^,8N>J0@SV)lK)].ՆWn#>ӻʪ,>~0+>!tSuZ J s׉1˭Lu@1OS۲m.6G`H8AW>W5Fl߃xhN'wЮ1UѥQf1.l>@@@e\.?     ;D`Gd{2CGcGúv˵M:*w>˪5yC19s\-%9r_}DZW_ٔ'K#[6Dw-s9qtm6DyB>b~T>r؍s}K6'1]ds[+:$w;.{3BQΟ,[:?5,fi@@x zM6CE@@@@ sa4B*-Kw~Ʒuޞ=ʮfKq   @L`V7         fߐ@@@@@@@@@o@@@@@@@@@ ڿ!-         1Y1          CZ@@@@@@@@@bbA@@@@@@@@/@0k        f8x        _` i@@@@@@@@ ̊q@@@@@@@@ؿ                    Y7@@@@@@@@@ &@0+@@@@@@@@@`oH          @L |͸IENDB`opentimelineio-0.18.1/docs/conf.py0000664000175000017500000001507315110656141014644 0ustar meme# # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import re import sphinx_rtd_theme import opentimelineio # -- Project information --------------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'OpenTimelineIO' copyright = "Copyright Contributors to the OpenTimelineIO project" author = 'Contributors to the OpenTimelineIO project' try: RELEASE = opentimelineio.__version__ except AttributeError: RELEASE = 'unknown' # The short X.Y version. version = RELEASE.split('-')[0] # The full version, including alpha/beta/rc tags. release = RELEASE # -- General configuration ------------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'myst_parser', # This plugin is used to format our markdown correctly ] templates_path = ['_templates'] exclude_patterns = ['_build', '_templates', '.venv'] pygments_style = 'sphinx' # -- Options for HTML output ----------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinx_rtd_theme" htmlhelp_basename = f'{project.lower()}doc' # -- Options for LaTeX output ---------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output latex_documents = [ ('index', f'{project.lower()}.tex', f'{project} Documentation', author, 'manual'), ] # -- Options for manual page output ---------------------------------------------------- # sphinx-doc.org/en/master/usage/configuration.html#options-for-manual-page-output man_pages = [ ('index', project.lower(), f'{project} Documentation', [author], 1) ] # -- Options for Texinfo output -------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-texinfo-output texinfo_documents = [ ('index', project.lower(), f'{project} Documentation', author, project, 'One line description of project.', 'Miscellaneous'), ] # -- Options for intersphinx ----------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration intersphinx_mapping = { "python": ("https://docs.python.org/3", None), } # -- Options for Autodoc --------------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration # Both the class’ and the __init__ method’s docstring are concatenated and inserted. # Pybind11 generates class signatures on the __init__ method. autoclass_content = "both" autodoc_default_options = { 'undoc-members': True } # -- Options for linkcheck ------------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder linkcheck_exclude_documents = [ r'cxx/cxx' ] # For some reason this URL gives 403 Forbidden when running in github actions linkcheck_ignore = [r'https://opensource.org/licenses/MIT'] # -- Options for MySt-Parser ----------------------------------------------------------- # https://myst-parser.readthedocs.io/en/latest/sphinx/reference.html myst_heading_anchors = 5 # -- Custom ---------------------------------------------------------------------------- def process_signature( app, what: str, name: str, obj: object, options: dict[str, str], signature: str, return_annotation, ): """This does several things: * Removes "self" argument from a signature. Pybind11 adds self to method arguments, which is useless in a python reference documentation. * Handles overloaded methods/functions by using the docstrings generated by Pybind11. Pybind11 adds the signature of each overload in the first function's signature. So the idea is to generate a new signature for each one instead. """ signatures = [] isClass = what == "class" # This block won't be necessary once https://github.com/pybind/pybind11/pull/2621 # gets merged in Pybind11. if signature or isClass: docstrLines = obj.__doc__ and obj.__doc__.split("\n") or [] if not docstrLines or isClass: # A class can have part of its doc in its docstr or in the __init__ docstr. docstrLines += ( obj.__init__.__doc__ and obj.__init__.__doc__.split("\n") or [] ) # This could be solidified by using a regex on the reconstructed docstr? if len(docstrLines) > 1 and "Overloaded function." in docstrLines: # Overloaded function detected. Extract each signature and create a new # signature for each of them. for line in docstrLines: nameToMatch = name.split(".")[-1] if not isClass else "__init__" # Maybe get use sphinx.util.inspect.signature_from_str ? if match := re.search(fr"^\d+\.\s{nameToMatch}(\(.*)", line): signatures.append(match.group(1)) elif signature: signatures.append(signature) signature = "" # Remove self from signatures. for index, sig in enumerate(signatures): newsig = re.sub(r"self\: [a-zA-Z0-9._]+(,\s)?", "", sig) signatures[index] = newsig signature = "\n".join(signatures) return signature, return_annotation def process_docstring( app, what: str, name: str, obj: object, options: dict[str, str], lines: list[str], ): for index, line in enumerate(lines): # Remove "self" from docstrings of overloaded functions/methods. # For overloaded functions/methods/classes, pybind11 # creates docstrings that look like: # # Overloaded function. # 1. func_name(self: , param2: int) # 1. func_name(self: , param2: float) # # "self" is a distraction that can be removed to improve readability. # This should be removed once https://github.com/pybind/pybind11/pull/2621 is merged. if re.match(fr'\d+\. {name.split("."[0])}', line): line = re.sub(r"self\: [a-zA-Z0-9._]+(,\s)?", "", line) lines[index] = line def setup(app): app.connect("autodoc-process-signature", process_signature) app.connect("autodoc-process-docstring", process_docstring) opentimelineio-0.18.1/docs/requirements.txt0000664000175000017500000000012515110656141016621 0ustar memesphinx==7.3.7 readthedocs-sphinx-ext==2.1.9 # ?? sphinx-rtd-theme myst-parser==3.0.1 opentimelineio-0.18.1/docs/python_reference.rst0000664000175000017500000000013315110656141017425 0ustar memePython ====== .. autosummary:: :toctree: api/python :recursive: opentimelineio opentimelineio-0.18.1/docs/tutorials/0000775000175000017500000000000015110656141015365 5ustar memeopentimelineio-0.18.1/docs/tutorials/versioning-schemas.md0000664000175000017500000003277315110656141021527 0ustar meme# Versioning Schemas ## Overview This document describes OpenTimelineIO's systems for dealing with different schema versions when reading files, writing files, or during development of the library itself. It is intended for developers who are integrating OpenTimelineIO into their pipelines or applications, or working directly on OpenTimelineIO. TL;DR for users: OpenTimelineIO should be able to read files produced by older versions of the library and be able to write files that are compatible with older versions of the library from newer versions. ## Schema/Version Introduction Each SerializableObject (the base class of OpenTimelineIO) has `schema_name` and `schema_version` fields. The `schema_name` is a string naming the schema, for example, `Clip`, and the `schema_version` is an integer of the current version number, for example, `3`. SerializableObjects can be queried for these using the `.schema_name()` and `.schema_version()` methods. For a given release of the OpenTimelineIO library, in-memory objects the library creates will always be the same schema version. In other words, if `otio.schema.Clip()` instantiates an object with `schema_version` 2, there is no way to get an in-memory `Clip` object with version 1. OpenTimelineIO can still interoperate with older and newer versions of the library by way of the schema upgrading/downgrading system. As OpenTimelineIO deserializes json from a string or disk, it will upgrade the schemas to the version supported by the library before instantiating the concrete in-memory object. Similarly, when serializing OpenTimelineIO back to disk, the user can instruct OpenTimelineIO to downgrade the JSON to older versions of the schemas. In this way, a newer version of OpenTimelineIO can read files with older schemas, and a newer version of OpenTimelineIO can generate JSON with older schemas in it. ## Schema Upgrading Once a type is registered to OpenTimelineIO, developers may also register upgrade functions. In python, each upgrade function takes a dictionary and returns a dictionary. In C++, the AnyDictionary is manipulated in place. Each upgrade function is associated with a version number - this is the version number that it upgrades to. C++ Example (can be viewed/run in `examples/upgrade_downgrade_example.cpp`): ```cpp class SimpleClass : public otio::SerializableObject { public: struct Schema { static auto constexpr name = "SimpleClass"; static int constexpr version = 2; }; void set_new_field(int64_t val) { _new_field = val; } int64_t new_field() const { return _new_field; } protected: using Parent = SerializableObject; virtual ~SimpleClass() = default; virtual bool read_from(Reader& reader) { auto result = ( reader.read("new_field", &_new_field) && Parent::read_from(reader) ); return result; } virtual void write_to(Writer& writer) const { Parent::write_to(writer); writer.write("new_field", _new_field); } private: int64_t _new_field; }; // later, during execution: // register type and upgrade/downgrade functions otio::TypeRegistry::instance().register_type(); // 1->2 otio::TypeRegistry::instance().register_upgrade_function( SimpleClass::Schema::name, 2, [](otio::AnyDictionary* d) { (*d)["new_field"] = (*d)["my_field"]; d->erase("my_field"); } ); ``` Python Example: ```python @otio.core.register_type class SimpleClass(otio.core.SerializableObject): serializable_label = "SimpleClass.2" my_field = otio.core.serializable_field("new_field", int) @otio.core.upgrade_function_for(SimpleClass, 2) def upgrade_one_to_two(data): return {"new_field" : data["my_field"] } ``` When upgrading schemas, OpenTimelineIO will call each upgrade function in order in an attempt to get to the current version. For example, if a schema is registered to have version 3, and a file with version 1 is read, OpenTimelineIO will attempt to call the 1->2 function, then the 2->3 function before instantiating the concrete class. ## Schema Downgrading Similarly, once a type is registered, downgrade functions may be registered. Downgrade functions take a dictionary of the version specified and return a dictionary of the schema version one lower. For example, if a downgrade function is registered for version 5, that will downgrade from 5 to 4. C++ Example, building off the prior section SimpleClass example (can be viewed/run in `examples/upgrade_downgrade_example.cpp`): ```cpp // 2->1 otio::TypeRegistry::instance().register_downgrade_function( SimpleClass::Schema::name, 2, [](otio::AnyDictionary* d) { (*d)["my_field"] = (*d)["new_field"]; d->erase("new_field"); } ); ``` Python Example: ```python @otio.core.upgrade_function_for(SimpleClass, 2) def downgrade_two_to_one(data): return {"my_field" : data["new_field"] } ``` To specify what version of a schema to downgrade to, the serialization functions include an optional `schema_version_targets` argument which is a map of schema name to target schema version. During serialization, any schemas who are listed in the map and are of greater version than specified in the map will be converted to AnyDictionary and run through the necessary downgrade functions before being serialized. Example C++: ```cpp auto sc = otio::SerializableObject::Retainer(new SimpleClass()); sc->set_new_field(12); // this will only downgrade the SimpleClass, to version 1 otio::schema_version_map downgrade_manifest = { {"SimpleClass", 1} }; // write it out to disk, downgrading to version 1 sc->to_json_file("/var/tmp/simpleclass.otio", &err, &downgrade_manifest); ``` Example python: ```python sc = SimpleClass() otio.adapters.write_to_file( sc, "/path/to/output.otio", target_schema_versions={"SimpleClass":1} ) ``` ### Schema-Version Sets In addition to passing in dictionaries of desired target schema versions, OpenTimelineIO also provides some tools for having sets of schemas with an associated label. The core C++ library contains a compiled-in map of them, the `CORE_VERSION_MAP`. This is organized (as of v0.15.0) by library release versions label, ie "0.15.0", "0.14.0" and so on. In order to downgrade to version 0.15.0 for example: ```cpp auto downgrade_manifest = otio::CORE_VERSION_MAP["0.15.0"]; // write it out to disk, downgrading to version 1 sc->to_json_file("/var/tmp/simpleclass.otio", &err, &downgrade_manifest); ``` In python, an additional level of indirection is provided, "FAMILY", which is intended to allow developers to define their own sets of target versions for their plugin schemas. For example, a studio might have a family named "MYFAMILY" under which they organize labels for their internal releases of their own plugins. These can be defined in a plugin manifest, which is a `.plugin_manifest.json` file found on the environment variable {term}`OTIO_PLUGIN_MANIFEST_PATH`. For example: ```python { "OTIO_SCHEMA" : "PluginManifest.1", "version_manifests": { "MYFAMILY": { "June2022": { "SimpleClass": 2, ... }, "May2022": { "SimpleClass": 1, ... } } } } ``` To fetch the version maps and work with this, the python API provides some additional functions: ```python # example using a built in family downgrade_manifest = otio.versioning.fetch_map("OTIO_CORE", "0.15.0") otio.adapters.write_to_file( sc, "/path/to/file.otio", target_schema_versions=downgrade_manifest ) # using a custom family defined in a plugin manifest json file downgrade_manifest = otio.versioning.fetch_map("MYFAMILY", "June2022") otio.adapters.write_to_file( sc, "/path/to/file.otio", target_schema_versions=downgrade_manifest ) ``` To fetch the version sets defined by the core from python, use the `OTIO_CORE` family of version sets. See the [versioning module](../api/python/opentimelineio.versioning.rst) for more information on accessing these. ## Downgrading at Runtime If you are using multiple pieces of software built with mismatched versions of OTIO, you may need to configure the newer one(s) to write out OTIO in an older format without recompiling or modifying the software. You can accomplish this in two ways: - The {term}`OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL` environment variable can specify a family and version. - The `otioconvert` utility program can downgrade an OTIO file to an older version. ### OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL Environment Variable If your software uses OTIO's Python adapter system, then you can set the {term}`OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL` environment variable with a `FAMILY:VERSION` value. For example, in a *nix shell: `env OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL=OTIO_CORE:0.14.0 my_program` The `OTIO_CORE` family is pre-populated with the core OTIO schema versions for previous OTIO releases, for example `0.14.0`. If you have custom schema that needs to be downgraded as well, you will need to specify your own family and version mapping, as described above. ### Downgrading with otioconvert If your software uses OTIO's C++ API, then it does not look for the {term}`OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL` environment variable, but you can convert an OTIO file after it has been created with the `otioconvert` utility. You can either use a family like this: ``` env OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL=OTIO_CORE:0.14.0 otioconvert -i input.otio -o output.otio ``` or you can specify the version mapping for each schema you care about like this: ``` otioconvert -i input.otio -o output.otio -A target_schema_versions="{'Clip':1, 'Timeline':1, 'Marker':2}" ``` ## For Developers During the development of OpenTimelineIO schemas, whether they are in the core or in plugins, it is expected that schemas will change and evolve over time. Here are some processes for doing that. ### Changing a Field Given `SimpleClass`: ```python import opentimelineio as otio @otio.core.register_type class SimpleClass(otio.core.SerializableObject): serializable_label = "SimpleClass.1" my_field = otio.core.serializable_field("my_field", int) ``` And `my_field` needs to be renamed to `new_field`. To do this: - Make the change in the class - Bump the version number in the label - add upgrade and downgrade functions ```python @otio.core.register_type class SimpleClass(otio.core.SerializableObject): serializable_label = "SimpleClass.2" new_field = otio.core.serializable_field("new_field", int) @otio.core.upgrade_function_for(SimpleClass, 2) def upgrade_one_to_two(data): return {"new_field" : data["my_field"] } @otio.core.downgrade_function_from(SimpleClass, 2) def downgrade_two_to_one(data): return {"my_field": data["new_field"]} ``` Changing it again, now `new_field` becomes `even_newer_field`. ```python @otio.core.register_type class SimpleClass(otio.core.SerializableObject): serializable_label = "SimpleClass.2" even_newer_field = otio.core.serializable_field("even_newer_field", int) @otio.core.upgrade_function_for(SimpleClass, 2) def upgrade_one_to_two(data): return {"new_field" : data["my_field"] } # NOTE we now have a second upgrade function @otio.core.upgrade_function_for(SimpleClass, 3) def upgrade_two_to_three(data): return {"even_newer_field" : data["new_field"] } @otio.core.downgrade_function_from(SimpleClass, 2) def downgrade_two_to_one(data): return {"my_field": data["new_field"]} # ...and corresponding second downgrade function @otio.core.downgrade_function_from(SimpleClass, 3) def downgrade_two_to_one(data): return {"new_field": data["even_newer_field"]} ``` ### Adding or Removing a Field Starting from the same class: ```python @otio.core.register_type class SimpleClass(otio.core.SerializableObject): serializable_label = "SimpleClass.1" my_field = otio.core.serializable_field("my_field", int) ``` If a change to a schema is to add a field, for which the default value is the correct value for an old schema, then no upgrade or downgrade function is needed. The parser ignores values that aren't in the schema. Additionally, upgrade functions will be called in order, but they need not cover every version number. So if there is an upgrade function for version 2 and 4, to get to version 4, OTIO will automatically apply function 2 and then function 4 in order, skipping the missing 3. Downgrade functions must be called in order with no gaps. Example of adding a field (`other_field`): ```python @otio.core.register_type class SimpleClass(otio.core.SerializableObject): serializable_label = "SimpleClass.2" my_field = otio.core.serializable_field("my_field", int) other_field = otio.core.serializable_field("other_field", int) ``` Removing a field (`my_field`): ```python @otio.core.register_type class SimpleClass(otio.core.SerializableObject): serializable_label = "SimpleClass.3" other_field = otio.core.serializable_field("other_field", int) ``` Similarly, when deleting a field, if the field is now ignored and does not contribute to computation, no upgrade or downgrade function is needed. opentimelineio-0.18.1/docs/tutorials/contributing.md0000664000175000017500000000626415110656141020426 0ustar meme# Contributing We're excited to collaborate with the community and look forward to the many improvements you can make to OpenTimelineIO! ## Contributor License Agreement Before contributing code to OpenTimelineIO, we ask that you sign a Contributor License Agreement (CLA). When you create a pull request, the Linux Foundation's EasyCLA system will guide you through the process of signing the CLA. If you are unable to use the EasyCLA system, you can send a signed CLA to `opentimelineio-tsc@aswf.io` (please make sure to include your github username) and wait for confirmation that we've received it. Here are the two possible CLAs: * [OTIO_CLA_Corporate.pdf](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/raw/main/OTIO_CLA_Corporate.pdf): please sign this one for corporate use * [OTIO_CLA_Individual.pdf](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/raw/main/OTIO_CLA_Individual.pdf): please sign this one if you're an individual contributor ## Coding Conventions Please follow the coding convention and style in each file and in each library when adding new files. ## Platform Support Policy As recommended by the [VFX Platform](https://vfxplatform.com) (see "Support Guidance"), we support the intended calendar year of the release as well as the three prior years. ## Git Workflow Here is the workflow we recommend for working on OpenTimelineIO if you intend on contributing changes back: Post an issue on github to let folks know about the feature or bug that you found, and mention that you intend to work on it. That way, if someone else is working on a similar project, you can collaborate, or you can get early feedback which can sometimes save time. Use the github website to fork your own private repository. Clone your fork to your local machine, like this: ```bash git clone https://github.com/you/OpenTimelineIO.git ``` Add the primary OpenTimelineIO repo as upstream to make it easier to update your remote and local repos with the latest changes: ```bash cd OpenTimelineIO git remote add upstream https://github.com/AcademySoftwareFoundation/OpenTimelineIO.git ``` Now you fetch the latest changes from the OpenTimelineIO repo like this: ```bash git fetch upstream git merge upstream/main ``` All the development should happen against the `main` branch. We recommend you create a new branch for each feature or fix that you'd like to make and give it a descriptive name so that you can remember it later. You can checkout a new branch and create it simultaneously like this: ```bash git checkout -b mybugfix upstream/main ``` Now you can work in your branch locally. Once you are happy with your change, you can verify that the change didn't cause tests failures by running tests like this: ```bash make test make lint ``` If all the tests pass and you'd like to send your change in for consideration, push it to your remote repo: ```bash git push origin mybugfix ``` Now your remote branch will have your `mybugfix` branch, which you can now pull request (to OpenTimelineIO's `main` branch) using the github UI. Please make sure that your pull requests are clean. Use the rebase and squash git facilities as needed to ensure that the pull request is as clean as possible. opentimelineio-0.18.1/docs/tutorials/time-ranges.md0000664000175000017500000001622115110656141020124 0ustar meme# Time Ranges ## Overview A Timeline and all of the Tracks and Stacks it contains work together to place the Clips, Gaps and Transitions relative to each other in time. You can think of this as a 1-dimensional coordinate system. Simple cases of assembling Clips into a Track will lay the Clips end-to-end, but more complex cases involve nesting, cross-dissolves, trimming, and speed-up/slow-down effects which can lead to confusion. In an attempt to make this easy to work with OpenTimelineIO uses the following terminology and API for dealing with time ranges. Note: You probably also want to refer to [Timeline Structure](otio-timeline-structure.md). ## Clips There are several ranges you might want from a Clip. For each of these, it is important to note which time frame (the 1-dimensional coordinate system of time) the range is relative to. We call these the "Clip time frame" and the "parent time frame" (usually the Clip's parent Track). ### Ranges within the Clip and its media: #### `Clip.available_range()` The `available_range()` method on Clip returns a TimeRange that tells you how much media is available via the Clip's `media_reference`. If a Clip points to a movie file on disk, then this should tell you how long that movie is and what timecode it starts at. For example: "wedding.mov" starts at timecode 01:00:00:00 and is 30 minutes long. Note that `available_range()` may return `None` if the range is not known. #### `Clip.source_range` Setting the `source_range` property on a Clip will trim the Clip to only that range of media. The `source_range` is specified in the Clip time frame. Note that `source_range` may be `None` indicating that the Clip should use the full `available_range()` whatever that may be. If both `source_range` and `available_range()` are `None`, then the Clip is invalid. You need at least one of those. Usually this will be a shorter segment than the `available_range()` but this is not a hard constraint. Some use cases will intentionally ask for a Clip that is longer (or starts earlier) than the available media as a way to request that new media (a newly animated take, or newly recorded audio) be made to fill the requested `source_range`. #### `Clip.trimmed_range()` This will return the Clip's `source_range` if it is set, otherwise it will return the `available_range()`. This tells you how long the Clip is meant to be in its parent Track or Stack. The `trimmed_range()` is specified in the Clip time frame. #### `Clip.visible_range()` This will return the same thing as `trimmed_range()` but also takes any adjacent Transitions into account. For example, a Clip that is trimmed to end at frame 10, but is followed by a cross-dissolve with `out_offset` of 5 frames, will have a `visible_range()` that ends at frame 15. The `visible_range()` is specified in the Clip time frame. #### `Clip.duration()` This is the way to ask for the Clip's "natural" duration. In `oitoview` or most common non-linear editors, this is the duration of the Clip you will see in the timeline user interface. `Clip.duration()` is a convenience for `Clip.trimmed_range().duration()`. If you want a different duration, then you can ask for `Clip.available_range().duration()` or `Clip.visible_range().duration()` explicitly. This makes it clear in your code when you are asking for a different duration. ### Ranges of the Clip in its parent Track or Stack: #### `Clip.range_in_parent()` The answer to this depends on what type is the Clip's parent. In the typical case, the Clip is inside a Track, so the `Clip.range_in_parent()` will give you the range within that Track where this Clip is visible. Each clip within the Track will have a start time that is directly after the previous clip's end. So, given a Track with clipA and clipB in it, this is always true: - The `range_in_parent()` is specified in the parent time frame. - `clipA.range_in_parent().end_time_exclusive() == clipB.range_in_parent().start_time` If the parent is a Stack, then `range_in_parent()` is less interesting. The start time will always have `.value == 0` and the duration is the Clip's duration. This means that the start of each clip in a Stack is aligned. If you want to shift them around, then use a Stack of Tracks (like the top-level Timeline has by default) and then you can use Gaps to shift the contents of each Track around. #### `Clip.trimmed_range_in_parent()` This is the same as `Clip.range_in_parent()` but trimmed to the *parent* `source_range`. In most cases the parent has a `source_range` of `None`, so there is no trimming, but in cases where the parent is trimmed, you may want to ask where a Clip is relative to the trimmed parent. In cases where the Clip is completely trimmed by the parent, the `Clip.trimmed_range_in_parent()` will be `None`. The `trimmed_range_in_parent()` is specified in the parent time frame. ## Tracks ### `Track.available_range()` Total duration of the track, totalling all items inside it. For example, if there is a clip and a gap in the track, and each is 5 seconds long, the `available_range()` would return 10 seconds. ### `Track.range_of_child_at_index(index)` Returns range of nth item (specified by `index`) in the track. Same as item's `duration`, including transition time. ### `Track.trimmed_range_of_child_at_index(index)` Same as `range_of_child_at_index()`, but trimmed relative to the track's source range. ### `Track.range_of_all_children()` Returns a list of every item, mapped to their trimmed range in the track. ## Markers Markers can be attached to any Item (Clips, Tracks, Stacks, Gaps, etc.) Each Marker has a `marked_range` which specifies the position and duration of the Marker relative to the object it is attached to. The `marked_range` of a Marker on a Clip is in the Clip's time frame (same as the Clip's `source_range`, `trimmed_range()`, etc.) The `marked_range` of a Marker on a Track is in the Track's time frame (same as the Track's `source_range`, `trimmed_range()`, etc.) The `marked_range.duration.value` may be 0 if the Marker is meant to be a instantaneous moment in time, or some other duration if it spans a length of time. ## Transitions ### `Transition.range_in_parent()` Range of transition for whatever holds the transition. Calls its parent's `range_in_child()`. ### `Transition.trimmed_range_in_parent()` The same as `range_in_parent()`, but trimmed relative to the parent. Calls its parent's `trimmed_range_in_child()`. ## Gaps ### `Gap.source_range` Time range of the gap, in between other items like Clips. ## Stacks ### `Stack.source_range` Defined range for the set of tracks. ### `Stack.available_range()` Returns a range representing its tracks' longest duration (based on the underlying clip `duration()`s). ### `Stack.range_of_child_at_index(int index)` Returns the total duration of the track at position `index`. ### `Stack.trimmed_range_of_child_at_index(int index)` Same as `range_of_child_at_index()`, but trimmed relative to the duration of the `source_range` of the stack. ### `Stack.range_of_all_children()` Returns set of Tracks mapped to their individual time ranges. ## Timelines ### `Timeline.range_of_child(child)` Get trimmed time range of direct as well as indirect children (like clips and gaps). opentimelineio-0.18.1/docs/tutorials/write-a-schemadef.md0000664000175000017500000000770115110656141021201 0ustar meme# Writing an OTIO SchemaDef Plugin OpenTimelineIO SchemaDef plugins are plugins that define new schemas within the otio type registry system. You might want to do this to add new schemas that are specific to your own internal studio workflow and shouldn't be part of the generic OpenTimelineIO package. To write a new SchemaDef plugin, you create a Python source file that defines and registers one or more new classes subclassed from ``otio.core.SerializeableObject``. Multiple schema classes can be defined and registered in one plugin, or you can use a separate plugin for each of them. Here's an example of defining a very simple class called ``MyThing``: ```python import opentimelineio as otio @otio.core.register_type class MyThing(otio.core.SerializableObject): """A schema for my thing.""" _serializable_label = "MyThing.1" _name = "MyThing" def __init__( self, arg1=None, argN=None ): otio.core.SerializableObject.__init__(self) self.arg1 = arg1 self.argN = argN arg1 = otio.core.serializable_field( "arg1", doc = ( "arg1's doc string") ) argN = otio.core.serializable_field( "argN", doc = ( "argN's doc string") ) def __str__(self): return "MyThing({}, {})".format( repr(self.arg1), repr(self.argN) ) def __repr__(self): return "otio.schema.MyThing(arg1={}, argN={})".format( repr(self.arg1), repr(self.argN) ) ``` In the example, the ``MyThing`` class has two parameters ``arg1`` and ``argN``, but your schema class could have any number of parameters as needed to contain the data fields you want to have in your class. One or more class definitions like this one can be included in a plugin source file, which must then be added to the plugin manifest as shown below. ## Registering Your SchemaDef Plugin To create a new SchemaDef plugin, you need to create a Python source file as shown in the example above. Let's call it ``mything.py``. Then you must add it to a plugin manifest: ```json { "OTIO_SCHEMA" : "PluginManifest.1", "schemadefs" : [ { "OTIO_SCHEMA" : "SchemaDef.1", "name" : "mything", "filepath" : "mything.py" } ] } ``` The same plugin manifest may also include adapters and media linkers, if desired. Then you need to add this manifest to your {term}`OTIO_PLUGIN_MANIFEST_PATH` environment variable. ## Using the New Schema in Your Code Now that we've defined a new otio schema, how can we create an instance of the schema class in our code (for instance, in an adapter or media linker)? SchemaDef plugins are loaded in a deferred way. The load is triggered either by reading a file that contains the schema or by manually asking the plugin for its module object. For example, if you have a `my_thing` schemadef module: ```python import opentimelineio as otio my_thing = otio.schema.schemadef.module_from_name('my_thing') ``` Once the plugin has been loaded, SchemaDef plugin modules are magically inserted into a namespace called ``otio.schemadef``, so you can create a class instance just like this: ```python import opentimelineio as otio mine = otio.schemadef.my_thing.MyThing(arg1, argN) ``` An alternative approach is to use the ``instance_from_schema`` mechanism, which requires that you create and provide a dict of the parameters: ```python mything = otio.core.instance_from_schema("MyThing", 1, { "arg1": arg1, "argN": argN }) ``` This ``instance_from_schema`` approach has the added benefit of calling the schema upgrade function to upgrade the parameters in the case where the requested schema version is earlier than the current version defined by the schemadef plugin. This seems rather unlikely to occur in practice if you keep your code up-to-date, so the first technique of creating the class instance directly from ``otio.schemadef`` is usually preferred. opentimelineio-0.18.1/docs/tutorials/write-an-adapter.md0000664000175000017500000002561715110656141021066 0ustar meme# Writing an OTIO Adapter OpenTimelineIO Adapters are plugins that allow OTIO to read and/or write other timeline formats. Users of OTIO can read and write files like this: ```python #/usr/bin/env python import opentimelineio as otio mytimeline = otio.adapters.read_from_file("something.edl") otio.adapters.write_to_file(mytimeline, "something.otio") ``` The otio.adapters module will look at the file extension (in this case ".edl" or ".otio") and pick the right adapter to convert to or from the appropriate format. Note that the OTIO JSON format is treated like an adapter as well. The ".otio" format is the only format that is lossless. It can store and retrieve all of the objects, metadata and features available in OpenTimelineIO. Other formats are lossy - they will only store and retrieve features that are supported by that format (and by the adapter implementation). Some adapters may choose to put extra information, not supported by OTIO, into metadata on any OTIO object. ## Sharing an Adapter You’ve Written With the Community If you've written an adapter that might be useful to others, please contact the [OpenTimelineIO team](https://github.com/AcademySoftwareFoundation/OpenTimelineIO) so we can add it to the list of [Tools and Projects Using OpenTimelineIO](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/wiki/Tools-and-Projects-Using-OpenTimelineIO). If the adapter is of broad enough interest to adopt as an OTIO community supported adapter, we can discuss transitioning it to the [OpenTimelineIO GitHub Organization](https://github.com/OpenTimelineIO/). Keep in mind, code should be [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) or [MIT](https://opensource.org/licenses/MIT) licensed if it is intended to transition to the OpenTimelineIO project. ### Packaging and Sharing Custom Adapters Adapters may also be organized into their own independent Python project for subsequent [packaging](https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives), [distribution](https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives) and [installation](https://packaging.python.org/tutorials/packaging-projects/#installing-your-newly-uploaded-package) by [`pip`](https://packaging.python.org/key_projects/#pip). The easiest way is to [otio-plugin-template repo](https://github.com/OpenTimelineIO/otio-plugin-template) and click "Use this template". This will get you started with plugin boilerplate and allow you to develop the adapter in your own GitHub account. If you'd like to work from scratch, we recommend you organize your project like so: ``` . ├── setup.py └── opentimelineio_mystudio    ├── __init__.py ├── plugin_manifest.json    ├── adapters    │   ├── __init__.py    │   ├── my_adapter_x.py    │   └── my_adapter_y.py    └── operations       ├── __init__.py       └── my_linker.py ``` With a `setup.py` containing this minimum entry set: ```python from setuptools import setup setup( name='OpenTimelineMyStudioAdapters', entry_points={ 'opentimelineio.plugins': 'opentimelineio_mystudio = opentimelineio_mystudio' }, package_data={ 'opentimelineio_mystudio': [ 'plugin_manifest.json', ], }, version='0.0.1', packages=[ 'opentimelineio_mystudio', 'opentimelineio_mystudio.adapters', 'opentimelineio_mystudio.operations', ], ) ``` And a `plugin_manifest.json` like: ```json { "OTIO_SCHEMA" : "PluginManifest.1", "adapters" : [ { "OTIO_SCHEMA" : "Adapter.1", "name" : "adapter_x", "filepath" : "adapters/my_adapter_x.py", "suffixes" : ["xxx"] }, { "OTIO_SCHEMA" : "Adapter.1", "name" : "adapter_y", "filepath" : "adapters/my_adapter_y.py", "suffixes" : ["yyy", "why"] } ], "media_linkers" : [ { "OTIO_SCHEMA" : "MediaLinker.1", "name" : "my_studios_media_linker", "filepath" : "operations/my_linker.py" } ] } ``` ### Custom Adapters Alternately, if you are creating a site specific adapter that you do _not_ intend to share with the community, you can create your `myadapter.py` file anywhere. In this case, you must create a `mysite.plugin_manifest.json` (with an entry like the below example that points at `myadapter.py`) and then put the path to your `mysite.plugin_manifest.json` on your {term}`OTIO_PLUGIN_MANIFEST_PATH` environment variable. For example, to register `myadapter.py` that supports files with a `.myext` file extension: ```json { "OTIO_SCHEMA" : "Adapter.1", "name" : "myadapter", "filepath" : "myadapter.py", "suffixes" : ["myext"] } ``` ## Required Functions Each adapter must implement at least one of these functions: ```python def read_from_file(filepath): # ... return timeline def read_from_string(input_str): # ... return timeline def write_to_string(input_otio): # ... return text def write_to_file(input_otio, filepath): # ... return ``` If your format is text-based, then we recommend that you implement `read_from_string` and `write_to_string`. The adapter module will automatically wrap these and allow users to call `read_from_file` and `write_to_file`. ## Constructing a Timeline To construct a Timeline in the `read_from_string` or `read_from_file` functions, you can use the API like this: ```python timeline = otio.schema.Timeline() timeline.name = "Example Timeline" track = otio.schema.Track() track.name = "V1" timeline.tracks.append(track) clip = otio.schema.Clip() clip.name = "Wedding Video" track.append(clip) ``` ### Metadata If your timeline, tracks, clips or other objects have format-specific, application-specific or studio-specific metadata, then you can add metadata to any of the OTIO schema objects like this: ```python timeline.metadata["mystudio"] = { "showID": "zz" } clip.metadata["mystudio"] = { "shotID": "zz1234", "takeNumber": 17, "department": "animation", "artist": "hanna" } ``` Note that all metadata should be nested inside a sub-dictionary (in this example "mystudio") so that metadata from other applications, pipeline steps, etc. can be kept separate. OTIO carries this metadata along blindly, so you can put whatever you want in there (within reason). Very large data should probably not go in there. ### Media References Clip media (if known) should be linked like this: ```python clip.media_reference = otio.schema.ExternalReference( target_url="file://example/movie.mov" ) ``` Some formats don't support direct links to media, but focus on metadata instead. It is fine to leave the media_reference empty ('None') if your adapter doesn't know a real file path or URL for the media. ### Source Range vs Available Range To specify the range of media used in the Clip, you must set the Clip's source_range like this: ```python clip.source_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(150, 24), # frame 150 @ 24fps duration=otio.opentime.RationalTime(200, 24) # 200 frames @ 24fps ) ``` Note that the source_range of the clip is not necessarily the same as the available_range of the media reference. You may have a clip that uses only a portion of a longer piece of media, or you might have some media that is too short for the desired clip length. Both of these are fine in OTIO. Also, clips can be relinked to different media, in which case the source_range of the clip stays the same, but the media_reference (and its available_range) will change after the relink. For example, you might relink from an old render to a newer render which has been extended to cover the source_range references by the clip. If you know the range of media available at that Media Reference's URL, then you can specify it like this: ```python clip.media_reference = otio.schema.ExternalReference( target_url="file://example/movie.mov", available_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(100, 24), # frame 100 @ 24fps duration=otio.opentime.RationalTime(500, 24) # 500 frames @ 24fps ) ) ``` It is fine to leave the Media Reference's available_range empty if you don't know it, but you should always specify a Clip's source_range. ## Traversing a Timeline When exporting a Timeline in the `write_to_string` or `write_to_file` functions, you will need to traverse the Timeline data structure. Some formats only support a single track, so a simple adapter might work like this: ```python def write_to_string(input_otio): """Turn a single track timeline into a very simple CSV.""" result = "Clip,Start,Duration\n" if len(input_otio.tracks) != 1: raise Exception("This adapter does not support multiple tracks.") for item in input_otio.each_clip(): start = otio.opentime.to_seconds(item.source_range.start_time) duration = otio.opentime.to_seconds(item.source_range.duration) result += ",".join([item.name, start, duration]) + "\n" return result ``` More complex timelines will contain multiple tracks and nested stacks. OTIO supports nesting via the abstract Composition class, with two concrete subclasses, Track and Stack. In general a Composition has children, each of which is an Item. Since Composition is also a subclass of Item, they can be nested arbitrarily. In typical usage, you are likely to find that a Timeline has a Stack (the property called 'tracks'), and each item within that Stack is a Track. Each item within a Track will usually be a Clip, Transition or Gap. If you don't support Transitions, you can just skip them and the overall timing of the Track should still work. If the format your adapter supports allows arbitrary nesting, then you should traverse the composition in a general way, like this: ```python def export_otio_item(item): result = MyThing(item) if isinstance(item, otio.core.Composition): result.children = map(export_otio_item, item.children) return result ``` If the format your adapter supports has strict expectations about the structure, then you should validate that the input has the expected structure and then traverse it based on those expectations, like this: ```python def export_timeline(timeline): result = MyTimeline(timeline.name) for track in timeline.tracks: if not isinstance(track, otio.schema.Track): raise Exception("This adapter requires each top-level item to be a track, not a "+typeof(track)) t = result.AddTrack(track.name) for clip in track.each_clip(): c = result.AddClip(clip.name) return result ``` ## Examples OTIO includes "core" adapters for `.otio`, `.otiod` and `otioz` files found in in `opentimelineio/adapters`. In addition to these you'll find many more adapters in the various repositories under the [OpenTimelineIO Organization](https://github.com/OpenTimelineIO) opentimelineio-0.18.1/docs/tutorials/otio-serialized-schema-only-fields.md0000664000175000017500000000714615110656141024503 0ustar meme# Serialized Data (Fields Only) This document is a list of all the OpenTimelineIO classes that serialize to and from JSON, omitting plugins classes and docstrings. This document is automatically generated by running: `src/py-opentimelineio/opentimelineio/console/autogen_serialized_datamodel.py` or by running: `make doc-model` It is part of the unit tests suite and should be updated whenever the schema changes. If it needs to be updated and this file regenerated, run: `make doc-model-update` # Classes ## Module: opentimelineio.adapters ### Adapter.1 parameters: - *filepath* - *name* - *suffixes* ## Module: opentimelineio.core ### Color.1 parameters: - *a* - *b* - *g* - *name* - *r* ### Composable.1 parameters: - *metadata* - *name* ### Composition.1 parameters: - *color* - *effects* - *enabled* - *markers* - *metadata* - *name* - *source_range* ### Item.1 parameters: - *color* - *effects* - *enabled* - *markers* - *metadata* - *name* - *source_range* ### MediaReference.1 parameters: - *available_image_bounds* - *available_range* - *metadata* - *name* ### SerializableObjectWithMetadata.1 parameters: - *metadata* - *name* ## Module: opentimelineio.hooks ### HookScript.1 parameters: - *filepath* - *name* ## Module: opentimelineio.media_linker ### MediaLinker.1 parameters: - *filepath* - *name* ## Module: opentimelineio.opentime ### RationalTime.1 parameters: - *rate* - *value* ### TimeRange.1 parameters: - *duration* - *start_time* ### TimeTransform.1 parameters: - *offset* - *rate* - *scale* ## Module: opentimelineio.plugins ### PluginManifest.1 parameters: - *adapters* - *hook_scripts* - *hooks* - *media_linkers* - *schemadefs* - *version_manifests* ### SerializableObject.1 parameters: - *filepath* - *name* ## Module: opentimelineio.schema ### Clip.2 parameters: - *active_media_reference_key* - *color* - *effects* - *enabled* - *markers* - *media_references* - *metadata* - *name* - *source_range* ### Effect.1 parameters: - *effect_name* - *enabled* - *metadata* - *name* ### ExternalReference.1 parameters: - *available_image_bounds* - *available_range* - *metadata* - *name* - *target_url* ### FreezeFrame.1 parameters: - *effect_name* - *enabled* - *metadata* - *name* - *time_scalar* ### Gap.1 parameters: - *color* - *effects* - *enabled* - *markers* - *metadata* - *name* - *source_range* ### GeneratorReference.1 parameters: - *available_image_bounds* - *available_range* - *generator_kind* - *metadata* - *name* - *parameters* ### ImageSequenceReference.1 parameters: - *available_image_bounds* - *available_range* - *frame_step* - *frame_zero_padding* - *metadata* - *missing_frame_policy* - *name* - *name_prefix* - *name_suffix* - *rate* - *start_frame* - *target_url_base* ### LinearTimeWarp.1 parameters: - *effect_name* - *enabled* - *metadata* - *name* - *time_scalar* ### Marker.2 parameters: - *color* - *comment* - *marked_range* - *metadata* - *name* ### MissingReference.1 parameters: - *available_image_bounds* - *available_range* - *metadata* - *name* ### SerializableCollection.1 parameters: - *metadata* - *name* ### Stack.1 parameters: - *color* - *effects* - *enabled* - *markers* - *metadata* - *name* - *source_range* ### TimeEffect.1 parameters: - *effect_name* - *enabled* - *metadata* - *name* ### Timeline.1 parameters: - *global_start_time* - *metadata* - *name* - *tracks* ### Track.1 parameters: - *color* - *effects* - *enabled* - *kind* - *markers* - *metadata* - *name* - *source_range* ### Transition.1 parameters: - *in_offset* - *metadata* - *name* - *out_offset* - *transition_type* ### SchemaDef.1 parameters: - *filepath* - *name* opentimelineio-0.18.1/docs/tutorials/adapters.md0000664000175000017500000000471415110656141017520 0ustar meme# Adapters While OpenTimelineIO favors the `.otio` JSON format, Python OpenTimelineIO supports many file formats via adapter plugins. ## Built-In Adapters The OpenTimelineIO native file format adapters that are present in the `opentimelineio` python package are: - [otio_json](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/blob/main/src/py-opentimelineio/opentimelineio/adapters/otio_json.py) - OpenTimelineIO's native file format. - [otiod](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/blob/main/src/py-opentimelineio/opentimelineio/adapters/otiod.py) - a directory bundle of a `.otio` file along with referenced media. - [otioz](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/blob/main/src/py-opentimelineio/opentimelineio/adapters/otioz.py) - a zip file bundle of a `.otio` file along with referenced media. ## Batteries-Included Adapters To also install a curated list of additional useful adapters, use the [OpenTimelineIO-Plugins ](https://pypi.org/project/OpenTimelineIO-Plugins/) python package. In addition to the OpenTimelineIO native adapters, you'll get additional useful adapters including: - [AAF](https://github.com/OpenTimelineIO/otio-aaf-adapter) - [ale](https://github.com/OpenTimelineIO/otio-ale-adapter) - [burnins](https://github.com/OpenTimelineIO/otio-burnins-adapter) - [cmx_3600](https://github.com/OpenTimelineIO/otio-cmx3600-adapter) - [fcp_xml](https://github.com/OpenTimelineIO/otio-fcp-adapter) - [fcpx_xml](https://github.com/OpenTimelineIO/otio-fcpx-xml-adapter) - [hls_playlist](https://github.com/OpenTimelineIO/otio-hls-playlist-adapter) - [maya_sequencer](https://github.com/OpenTimelineIO/otio-maya-sequencer-adapter) - [svg](https://github.com/OpenTimelineIO/otio-svg-adapter) - [xges](https://github.com/OpenTimelineIO/otio-xges-adapter) These adapters are supported by the broader OpenTimelineIO community. While the OTIO core team consults and sometimes contribute to their development, they may be maintained and supported at varying levels. ## Additional Adapters Below are some other adapters that may be useful to some users: - [kdenlive](https://invent.kde.org/multimedia/kdenlive-opentimelineio) ## Custom Adapters Adapters are implemented as plugins for OpenTimelineIO and can either be registered via an [environment variable](./otio-env-variables) or by packaging in a Python module with a particular entrypoint defined. For more detail, see the [Writing an OTIO Adapter](./write-an-adapter) tutorial. opentimelineio-0.18.1/docs/tutorials/write-a-hookscript.md0000664000175000017500000001661215110656141021450 0ustar meme# Writing a Hook Script OpenTimelineIO Hook Scripts are plugins that run at predefined points during the execution of various OTIO functions, for example after an adapter has read a file into memory but before the media linker has run. To write a new hook script, you create a python source file that defines a function named ``hook_function`` with signature: ``hook_function :: otio.schema.Timeline, Dict => otio.schema.Timeline`` The first argument is the timeline to process, and the second one is a dictionary of arguments that can be passed to it by the adapter or media linker. Only one hook function can be defined per python file. For example: ```python def hook_function(tl, argument_map=None): for cl in tl.each_clip(): cl.metadata['example_hook_function_was_here'] = True return tl ``` This will insert some extra metadata into each clip. This plugin can then be registered with the system by configuring a plugin manifest. ## Registering Your Hook Script To create a new OTIO hook script, you need to create a file myhooks.py. Then add a manifest that points at that python file: ```json { "OTIO_SCHEMA" : "PluginManifest.1", "hook_scripts" : [ { "OTIO_SCHEMA" : "HookScript.1", "name" : "example hook", "filepath" : "myhooks.py" } ], "hooks" : { "pre_adapter_write" : ["example hook"], "post_adapter_read" : [], "post_adapter_write" : [], "post_media_linker" : [] } } ``` The ``hook_scripts`` section will register the plugin with the system, and the ``hooks`` section will attach the scripts to hooks. Then you need to add this manifest to your {term}`OTIO_PLUGIN_MANIFEST_PATH` environment variable. You may also define media linkers and adapters via the same manifest. ## Running a Hook Script If you would like to call a hook script from a plugin, the hooks need not be one of the ones that OTIO pre-defines. You can have a plugin adapter or media linker, for example, that defines its own hooks and calls your own custom studio specific hook scripts. To run a hook script from your custom code, you can call: ```python otio.hooks.run("some_hook", some_timeline, optional_argument_dict) ``` This will call the ``some_hook`` hook script and pass in ``some_timeline`` and ``optional_argument_dict``. ## Order of Hook Scripts To query which hook scripts are attached to a given hook, you can call: ```python import opentimelineio as otio hook_list = otio.hooks.scripts_attached_to("some_hook") ``` Note that ``hook_list`` will be in order of execution. You can rearrange this list, or edit it to change which scripts will run (or not run) and in which order. To Edit the order, change the order in the list: ```python hook_list[0], hook_list[2] = hook_list[2], hook_list[0] print hook_list # ['c','b','a'] ``` Now c will run, then b, then a. To delete a function in the list: ```python del hook_list[1] ``` ## Example Hooks ### Replacing part of a path for drive mapping An example use-case would be to create a pre-write adapter hook that checks the argument map for a style being identified as nucoda and then performs a path replacement on the reference url: ```python def hook_function(in_timeline,argument_map=None): adapter_args = argument_map.get('adapter_arguments') if adapter_args and adapter_args.get('style') == 'nucoda': for in_clip in in_timeline.each_clip(): ''' Change the Path to use windows drive letters ( Nucoda is not otherwise forward slash sensitive ) ''' if in_clip.media_reference: in_clip.media_reference.target_url = in_clip.media_reference.target_url.replace(r'/linux/media/path','S:') ``` ### Add an incremental copy of otio file to backup folder Example of a post adapter write hook that creates a timestamped copy of newly written file in a hidden "incremental" folder: ```python import os import time import shutil def hook_function(in_timeline, argument_map=None): # Adapter will add "_filepath" to argument_map filepath = argument_map.get('_filepath') backup_name = "{filename}.{time}".format( filename=os.path.basename(filepath), time=time.time() ) incrpath = os.path.join( os.path.dirname(filepath), '.incremental', backup_name ) shutil.copyfile(filepath, incrpath) return in_timeline ``` Please note that if a "post adapter write hook" changes `in_timeline` in any way, the api will not automatically update the already serialized file. The changes will only exist in the in-memory object, because the hook runs _after_ the file is serialized to disk. ## Implementing Adapter-specific hooks While OTIO ships with a set of pre-defined hooks (e.g. `pre_adapter_write`), you can also define your own hooks in your adapter. These can be useful to give the user more fine-grained control over the execution of your adapter and make it work for their specific workflow. A good example is media embedding within Avids AAF files: Depending on the workflow, media references might have to be transcoded to be compatible with the AAF format. To achieve this, the AAF adapter could define a hook which users can leverage to transcode the files before embedding is attempted. To define a custom hook in your adapter, you need to implement the `adapter_hook_names` function in your adapter module. You can define as many hooks as you like, but try to use the native hooks where possible to keep the API consistent. ```python # my_aaf_adapter.py def read_from_file(self, filepath, **kwargs): ... def write_to_file(self, timeline, filepath, **kwargs): ... def adapter_hook_names() -> List[str]: """Returns names of custom hooks implemented by this adapter.""" return [ "my_custom_adapter_hook" ] ``` The new hooks also need to be added to the adapter plugin manifest. ```json { "OTIO_SCHEMA" : "PluginManifest.1", "adapters" : [ { "OTIO_SCHEMA" : "Adapter.1", "name" : "My AAF Adapter", "execution_scope" : "in process", "filepath" : "adapters/my_aaf_adapter.py", "suffixes" : ["aaf"] } ], "hook_scripts" : [ { "OTIO_SCHEMA" : "HookScript.1", "name" : "script_attached_to_custom_adapter_hook", "filepath" : "my_custom_adapter_hook_script.py" } ], "hooks" : { "pre_adapter_write" : [], "post_adapter_read" : [], "post_adapter_write" : [], "post_media_linker" : [], "my_custom_adapter_hook" : ["script_attached_to_custom_adapter_hook"] } } ``` A custom hook script might look like this: ```python # my_custom_adapter_hook_script.py def hook_function(timeline, custom_argument, argument_map=None): # Do something with the timeline print( f"Running custom adapter hook with custom argument value '{custom_argument}'" f"and argument map: {argument_map}" ) return timeline ``` Attached hook scripts can then be run anywhere using the `otio.hooks.run` function: ```python # my_aaf_adapter.py def write_to_file(self, timeline, filepath, **kwargs): # Do something ... # Run custom hook script with it's custom arguments and pass hook_argument_map along otio.hooks.run( "my_custom_adapter_hook", timeline, custom_argument="some_value", argument_map=kwargs.get("hook_argument_map", {}) ) ... # Do something more ```opentimelineio-0.18.1/docs/tutorials/otio-plugins.md0000664000175000017500000001232315110656141020341 0ustar meme# Plugin Documentation This documents all the plugins that ship with in the open source OpenTimelineIO distribution. This document is automatically generated by running the `autogen_plugin_documentation` command, or by running `make plugin-model`. It is part of the unit tests suite and should be updated whenever the schema changes. If it needs to be updated, run: `make doc-plugins-update` and this file should be regenerated. # Manifests The manifests describe plugins that are visible to OpenTimelineIO. The core manifest is listed first, then any user-defined local plugins. - `opentimelineio/adapters/builtin_adapters.plugin_manifest.json` # Core Plugins Manifest path: `opentimelineio/adapters/builtin_adapters.plugin_manifest.json` ## Adapter Plugins Adapter plugins convert to and from OpenTimelineIO. [Adapters documentation page for more information](./adapters). [Tutorial on how to write an adapter](write-an-adapter). ### otio_json ``` Adapter for reading and writing native .otio json files. ``` *source*: `opentimelineio/adapters/otio_json.py` *Supported Features (with arguments)*: - read_from_file: ``` De-serializes an OpenTimelineIO object from a file Args: filepath (str): The path to an otio file to read from Returns: OpenTimeline: An OpenTimeline object ``` - filepath - read_from_string: ``` De-serializes an OpenTimelineIO object from a json string Args: input_str (str): A string containing json serialized otio contents Returns: OpenTimeline: An OpenTimeline object ``` - input_str - write_to_file: ``` Serializes an OpenTimelineIO object into a file Args: input_otio (OpenTimeline): An OpenTimeline object filepath (str): The name of an otio file to write to indent (int): number of spaces for each json indentation level. Use -1 for no indentation or newlines. If target_schema_versions is None and the environment variable "OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL" is set, will read a map out of that for downgrade target. The variable should be of the form FAMILY:LABEL, for example "MYSTUDIO:JUNE2022". Returns: bool: Write success Raises: ValueError: on write error otio.exceptions.InvalidEnvironmentVariableError: if there is a problem with the default environment variable "OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL". ``` - input_otio - filepath - target_schema_versions - indent - write_to_string: ``` Serializes an OpenTimelineIO object into a string Args: input_otio (OpenTimeline): An OpenTimeline object indent (int): number of spaces for each json indentation level. Use -1 for no indentation or newlines. If target_schema_versions is None and the environment variable "OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL" is set, will read a map out of that for downgrade target. The variable should be of the form FAMILY:LABEL, for example "MYSTUDIO:JUNE2022". Returns: str: A json serialized string representation Raises: otio.exceptions.InvalidEnvironmentVariableError: if there is a problem with the default environment variable "OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL". ``` - input_otio - target_schema_versions - indent ### otiod ``` OTIOD adapter - bundles otio files linked to local media in a directory Takes as input an OTIO file that has media references which are all ExternalReferences with target_urls to files with unique basenames that are accessible through the file system and bundles those files and the otio file into a single directory named with a suffix of .otiod. ``` *source*: `opentimelineio/adapters/otiod.py` *Supported Features (with arguments)*: - read_from_file: - filepath - absolute_media_reference_paths - write_to_file: - input_otio - filepath - media_policy - dryrun ### otioz ``` OTIOZ adapter - bundles otio files linked to local media Takes as input an OTIO file that has media references which are all ExternalReferences with target_urls to files with unique basenames that are accessible through the file system and bundles those files and the otio file into a single zip file with the suffix .otioz. Can error out if files aren't locally referenced or provide missing references Can also extract the content.otio file from an otioz bundle for processing. Note that OTIOZ files _always_ use the unix style path separator ('/'). This ensures that regardless of which platform a bundle was created on, it can be read on unix and windows platforms. ``` *source*: `opentimelineio/adapters/otioz.py` *Supported Features (with arguments)*: - read_from_file: - filepath - extract_to_directory - write_to_file: - input_otio - filepath - media_policy - dryrun ## Media Linkers Media Linkers run after the adapter has read in the file and convert the media references into valid references where appropriate. [Tutorial on how to write a Media Linker](write-a-media-linker). ## SchemaDefs SchemaDef plugins define new external schema. [Tutorial on how to write a schemadef](write-a-schemadef). ## HookScripts HookScripts are extra plugins that run on _hooks_. [Tutorial on how to write a hookscript](write-a-hookscript). ## Hooks Hooks are the points at which hookscripts will run. opentimelineio-0.18.1/docs/tutorials/feature-matrix.rst0000664000175000017500000000576515110656141021071 0ustar memeFeature Matrix =============== Adapters may or may not support all of the features of OpenTimelineIO or the format they convert to/from. Here is a list of features and which adapters do/don't support those features. +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Feature | OTIO | EDL |FCP7 XML| FCP X | AAF | RV | ALE |GStreamer | +=========================+======+=======+========+========+=======+========+=======+==========+ |Single Track of Clips | ✔ | ✔ | ✔ | ✔ | ✔ | W-O | ✔ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Multiple Video Tracks | ✔ | ✖ | ✔ | ✔ | ✔ | W-O | ✔ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Audio Tracks & Clips | ✔ | ✔ | ✔ | ✔ | ✔ | W-O | ✔ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Gap/Filler | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✖ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Markers | ✔ | ✔ | ✔ | ✔ | ✔ | N/A | ✖ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Nesting | ✔ | ✖ | ✔ | ✔ | ✔ | W-O | ✔ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Transitions | ✔ | ✔ | ✖ | ✖ | ✔ | W-O | ✖ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Audio/Video Effects | ✖ | ✖ | ✖ | ✖ | ✖ | N/A | ✖ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Linear Speed Effects | ✔ | ✔ | ✖ | ✖ | R-O | ✖ | ✖ | ✖ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Fancy Speed Effects | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Color Decision List | ✔ | ✔ | ✖ | ✖ | ✖ | ✖ | N/A | ✖ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ |Image Sequence Reference | ✔ | ✔ | ✖ | ✖ | ✖ | W-O | ✖ | ✔ | +-------------------------+------+-------+--------+--------+-------+--------+-------+----------+ N/A: Not Applicable W-O: Write Only R-O: Read Only opentimelineio-0.18.1/docs/tutorials/otio-timeline-structure.md0000664000175000017500000002221715110656141022527 0ustar meme# Timeline Structure An OpenTimelineIO `Timeline` object can contain many tracks, nested stacks, clips, gaps, and transitions. This document is meant to clarify how these objects nest within each other, and how they work together to represent an audio/video timeline. ## Rendering Rendering of the image tracks in a timeline is done in painter order. The layers in a stack are iterated from the bottom (the first entry in the stack) towards the top (the final entry in the stack). Images in a stack overlay lower images using an alpha composite operation respecting any alpha in the source materials. All compositing is assumed to occur over a background of zero values in color components, and 100% values for alpha components. Within a track, clips may overlap via a transition. In that case, the contribution of track is the linear blend of the elements joined by the transition. If there are effects on a clip, OpenTimelineIO does not say anything about the impact of the effect and deviation from the base behavior is application specific. Rendering of the audio tracks is additive. It is strongly advised, but not required, that the summed audio is summed as floating point, and that it is processed through a compression filter in order to prevent clipping and distortion. ## Simple Cut List Let’s start with a simple cut list of a few clips. This is stored as a single `Timeline` with a single `Track` which contains several `Clip` children, spliced end-to-end. *Figure 1 - Simple Cut List* ![Figure 1 - Simple Cut List](../_static/simple_cut_list.png) Since a `Timeline` can hold multiple tracks, it always has a top-level `Stack` object to hold its `Track` children. In this case, that `Stack` has just one `Track`, named “Track-001”. Within "Track-001", there are four `Clip` objects, named "Clip-001", "Clip-002", "Clip-003", and "Clip-004". Each `Clip` has a corresponding media reference, "Media-001", "Media-002", etc. At the bottom level, we see that each media reference has a target_url and an available_range. The target_url tells us where to find the media (e.g. a file path or network URL, etc.) The available_range specifies the range of media that is available in the file that it points to. An available_range is a `TimeRange` object which specifies a start_time and duration. The start_time and duration are each `RationalTime` objects, which store a value and rate. Thus we can use `RationalTime(7,24)` to mean frame 7 at 24 frames per second. In the diagram we write this as just 7 for brevity. In this case most of our media references have an available_range that starts at 0 for some number of frames. One of the media references starts at 100. Assuming the media is 24 frames per second, this means that the media file contains media that starts at 4 seconds and 4 frames (timecode 00:00:04:04). In many cases you might not know the available_range because the media is missing, or points to a file path or URL which might be expensive to query. If that’s the case, then the available_range of a media_reference will be `None`. Above the media references, we see that each `Clip` has a source_range, which specifies a trimmed segment of media. In cases where we only want a portion of the available media, the source_range will start at a higher start_time, and/or have a shorter duration. The colored segments of "Media-001", "Media-002" and "Media-003" show the portion that each clip’s source_range references. In the case of "Clip-004", the source_range is `None`, so it exposes its entire media reference. In the OTIO API, you can query the trimmed_range() of a clip to get the range of media used regardless of whether it has a source_range, available_range or both - but it needs at least one of the two. Also note that a clip’s source_range could refer to a segment outside the available_range of its media reference. That is fine, and comes up in practice often (e.g. I only rendered the first half of my shot). OTIO itself does no snapping or verification of this, but downstream applications may handle this in a variety of ways. The single `Track` in this example contains all four clips in order. You can ask the `Track` or `Stack` for its trimmed_range() or duration() and it will sum up the trimmed lengths of its children. In later examples, we will see cases where a `Track` or `Stack` is trimmed by setting a source_range, but in this example they are not trimmed. ## Transitions A `Transition` is a visual effect, like a cross dissolve or wipe, that blends two adjacent items on the same track. The most common case is a fade or cross-dissolve between two clips, but OTIO supports transitions between any two `Composable` items (`Clip`s, `Gap`s, or nested `Track`s or `Stack`s). *Figure 2 - Transitions* ![Figure 2 - Transitions](../_static/transitions.png) In Figure 2, there is a `Transition` between "Clip-002" and "Clip-003". The in_offset and out_offset of the `Transition` specify how much media from the adjacent clips is used by the transition. Notice that the `Transition` itself does not make "Track-001" any shorter or longer. If a playback tool is not able to render a transition, it may simply ignore transitions and the overall length of the timeline will not be affected. In Figure 2, the `Transition`'s in_offset of 2 frames means that frames 1 and 2 of "Media-003" are used in the cross dissolve. The out_offset of 3 frames means that frames 8, 9, 10 of "Media-002" are used. Notice that "Media-002"'s available_range is 2 frames too short to satisfy the desired length of the cross-dissolve. OTIO does not prevent you from doing this, as it may be important for some use cases. OTIO also does not specify what a playback tool might display in this case. A `Transition`'s in_offset and out_offset are not allowed to extend beyond the duration of the adjacent clips. If a clip has transitions at both ends, the two transitions are not allowed to overlap. Also, you cannot place two transitions next to each other in a track; there must be a composable item between them. A fade to or from black will often be represented as a transition to or from a `Gap`, which can be 0 duration. If multiple tracks are present note that a `Gap` is meant to be transparent, so you may need to consider using a `Clip` with a `GeneratorReference` if you require solid black or any other solid color. ## Multiple Tracks A more typical timeline will include multiple video tracks. In Figure 3, the top-level `Stack` now contains "Track-001", "Track-002", and "Track-003" which contain some `Clip` and `Gap` children. Figure 3 also shows a flattened copy of the timeline to illustrate how multitrack composition works. *Figure 3 - Multiple Tracks* ![Figure 3 - Multiple Tracks](../_static/multiple_tracks.png) The `Gap` in "Track-001" is 4 frames long, and the track below, "Track-002", has frames 102-105 of "Clip-003" aligned with the `Gap` above, so those frames show through in the resulting flattened `Track`. Note that the `Gap` at the front of "Track-002" is used just to offset "Clip-003". This is a common way to shift clips around on a track, but you may also use the `Track`’s source_range to do this, as illustrated in "Track-003". "Clip-005" is completely obscured by "Clip-003" above it, so "Clip-005" does not appear in the flattened timeline at all. You might also notice that "Track-001" is longer than the other two. If you wanted "Track-002" to be the same length, then you would need to append a `Gap` at the end. If you wanted "Track-003" to be the same length, then you could extend the duration of its source_range to the desired length. In both cases, the trimmed_range() will be the same. ## Nested Compositions The children of a `Track` can be any `Composable` object, which includes `Clip`s, `Gap`s, `Track`s, `Stack`s, and `Transition`s. In Figure 4 we see an example of a `Stack` nested within a `Track`. *Figure 4 - Nested Compositions* ![Figure 4 - Nested Compositions](../_static/nested_compositions.png) In this example, the top-level `Stack` contains only one `Track`. "Track-001" contains four children, "Clip-001", "Nested Stack", "Gap", and "Clip-004". By nesting a `Composition` (either `Track` or `Stack`) we can refer to a `Composition` as though it was just another `Clip` in the outer `Composition`. If a source_range is specified, then only a trimmed segment of the inner `Composition` is included. In this case that is frames 2 through 7 of "Nested Stack". If no source_range is specified, then the full available_range of the nested composition is computed and included in the outer composition. "Nested Stack" contains two tracks, with some clips, gaps, and a track-level source_range on the lower track. This illustrates how the content of "Nested Stack" is composed upwards into "Track-001" so that a trimmed portion of "Clip-005" and "Clip-003" appear in the flattened composition. Notice how the `Gap` in "Track-001" cannot see anything inside the nested composition ("Clip-003", etc.) because those are not peers to "Track-001", they are nested within "Nested Stack" and do not spill over into adjacent `Gap`s. In other words, "Nested Stack" behaves just like a `Clip` that happens to have complex contents rather than a simple media reference. opentimelineio-0.18.1/docs/tutorials/developing-a-new-schema.md0000664000175000017500000001146415110656141022314 0ustar meme# Schema Proposal and Development Workflow ## Introduction This document describes a process for proposing and developing a new schema for the [OpenTimelineIO project](https://opentimeline.io). The process includes several steps: * Proposing at a TSC meeting and gathering interested parties for feedback * Outlining example JSON * Implementing and iterating on a branch * Building support into an adapter as a demonstration * Incrementing other schemas that are impacted (Eg. Changes to `Clip` to implement `Media Multi Reference` ## Examples A number of schemas have been proposed and introduced during OpenTimelineIO's development. These include: * [ImageSequenceReference](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/pull/602) * [SpatialCoordinates](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/pull/1219) * [Multi media-reference](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/pull/1241) ## Core schema or Plugin? OpenTimelineIO has a number of plugin mechanisms, including the [Schemadef](write-a-schemadef). Plugin schemadefs are great for things that aren't expected to be useful to the broader community, or are specific to a particular studio, workflow, or practice. Example of this might be a reference to a proprietary database or a proprietary effect. They can also be a good place to prototype a particular schema before proposing it to the community for adoption. ## Proposal A proposal can be as fleshed out as a proposed implementation, or as vague as an idea. Presenting the proposal at a Technical Steering Committee for discussion is preferred so that interested parties can form a working group if necessary. The goal of a TSC presentation would be to find view points / impacts that might not have been considered and advertise the development to the community at large. Including an example JSON excerpt which has the fields you think might be needed can help. References that are particularly helpful are examples from existing applications/formats, information about how (or if) the schema participates in temporal transformations, and other relevant citations. ## Implementing and Iterating on a branch Development of schemas typically takes longer and includes more feedback and review than normal development. To facilitate this, generally the project will open a branch on the repository so that pull requests can be merged into the prototype without disturbing the main branch. For example, the [ImageSequenceReference](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/pull/602) branch demonstrates that workflow. A complete implementation should have a: * C++ core implementation in src/opentimelineio * python binding in src/py-opentimelineio * unit tests ### Unit Tests Unit Tests should include a C++ test for the C++ component, a python test for the python binding, and a baseline test. #### C++ test The C++ test should directly test the C++ interface. For examples of that, see `tests/*.cpp`. #### Python tests The Python test should test the python binding, including any extra ergonomic conveniences unique to the python implementation (iterators, etc). We use the `unittest` python library. For examples of this, see: `tests/test_*.py`. #### Baseline tests Baseline tests are written in python and are intended to test the serializer. They include: * a file named `tests/baselines/empty_.json`, which is the result of calling the constructor and then immediately serializing the object: ```python ys = YourSchema() otio.adapters.write_to_file(ys, "empty_your_schema.json", adapter="otio_json") ``` * a test in `tests/test_json_backend.py` of the form: ```python class TestJsonFormat(unittest.TestCase, otio_test_utils.OTIOAssertions): ... def test_your_schema(self): ys = YourSchema() self.check_against_baseline(ys, "empty_your_schema") ... ``` ## Demo Adapter Providing an adapter that supports the schema can show how the schema is translated into another format. For example, the [ImageSequenceReference](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/pull/722) used the RV adapter to demonstrate how it could be used by an adapter. ## Incrementing Other Schemas Depending on where the schema fits into the overall structure, other schemas might need to be incremented or changed. For example, the Media multi-reference caused the clip schema to increment. Considering and implementing this is part of the implementation. Providing up and downgrade functions ensure backwards and forwards compatibility. ## Conclusion OpenTimelineIO is designed to evolve, and through its schema versioning system hopes to adapt to changes in the world of timelines and time math. We hope that working with and on OpenTimelineIO can be a positive, enriching experience for the community. Thanks for being a part of it! opentimelineio-0.18.1/docs/tutorials/otio-env-variables.md0000664000175000017500000000370715110656141021424 0ustar meme# Environment Variables This document describes the environment variables that can be used to configure various aspects of OTIO. ## Plugin Configuration These variables must be set _before_ the OpenTimelineIO python library is imported. They only impact the python library. The C++ library has no environment variables. ```{glossary} OTIO_PLUGIN_MANIFEST_PATH A colon (`:`) on POSIX system (or a semicolon (`;`) on Windows) separated string with paths to `.manifest.json` files that contain OTIO plugin manifests. See the [tutorial on how to write an adapter plugin](write-an-adapter.md) for additional details. OTIO_DEFAULT_MEDIA_LINKER The name of the default media linker to use after reading a file, if `""` then no media linker is automatically invoked. OTIO_DISABLE_ENTRYPOINTS_PLUGINS By default, OTIO will use the `importlib.metadata` entry_points mechanism to discover plugins that have been installed into the current python environment. For users who wish to disable this behavior, this variable can be set to 1. OTIO_DEFAULT_TARGET_VERSION_FAMILY_LABEL If no downgrade arguments are passed to `write_to_file`/`write_to_string`, use the downgrade manifest specified by the family/label combination in the variable. Variable is of the form `FAMILY:LABEL`. Only one tuple of `FAMILY:LABEL` may be specified. ``` ## Unit tests These variables only impact unit tests. ```{glossary} OTIO_DISABLE_SHELLOUT_TESTS When running the unit tests, skip the console tests that run the otiocat program and check output through the shell. This is desirable in environments where running the commandline tests is not meaningful or problematic. Does not disable the tests that run through python calling mechanisms. OTIO_DISABLE_SERIALIZED_SCHEMA_TEST Skip the unit tests that generate documentation and compare the current state of the schema against the stored one. Useful if the documentation is not available from the test directory. ``` opentimelineio-0.18.1/docs/tutorials/otio-file-format-specification.md0000664000175000017500000003033315110656141023704 0ustar meme# File Format Specification ## Version This DRAFT describes the OpenTimelineIO JSON File Format as of OTIO Beta 13. ## Note It is strongly recommended that everyone use the OpenTimelineIO library to read and write OTIO files instead of implementing a separate parser or writer. ## Naming OpenTimelineIO files should have a `.otio` path extension. Please do not use `.json` to name OTIO files. ## Contents OpenTimelineIO files are serialized as JSON (http://www.json.org). ### Number Types Supported number types: - integers: `int64_t` (signed 64 bit integer) - floating point numbers: `double` (IEEE754 64 bit signed floating point number) In addition to the basic JSON spec, OTIO allows the following values for doubles: - `NaN` (not a number) - `Inf`, `Infinity` (positive infinity) - `-Inf, -Infinity (negative infinity) ## Structure An OTIO file is a tree structure of nested OTIO objects. Each OTIO object is stored as a JSON dictionary with member fields, each of which may contain simple data types or nested OTIO objects. OTIO does not support instancing, there cannot be references the same object multiple times in the tree structure. If the same clip or media appears multiple times in a timeline, it will appear as identical copies of the Clip or MediaReference object. The top level object in an OTIO file can be any OTIO data type, but is typically a Timeline. This means that most use cases will assume that the top level object is a Timeline, but in specific workflows, otio files can be read or written that contain just a Clip, Track, RationalTime, or any other OTIO data type. Due to the nature of JSON, arrays of objects can also be read/written, but it is better to use the OTIO SerializableCollection data type in this case so that metadata can be attached to the container itself. Code that reads an OTIO file should guard against unexpected top level types and fail gracefully. Note also, that this is the reason that there is no top level file format version in OTIO. Each data type has a version instead to allow for more granular versioning. Each OTIO object has an `"OTIO_SCHEMA"` key/value pair that identifies the OTIO data type and version of that type. For example `"OTIO_SCHEMA": "Timeline.1"` or `"OTIO_SCHEMA": "Clip.5"`. This allows future versions of OTIO to change the serialization details of each data type independently and introduce new data types over time. (TODO: Link to discussion on schema versioning.) Member fields of each data type are encoded as key/value pairs in the containing object's dictionary. The value of each key can be a JSON string, number, list, or dictionary. If the value is a dictionary, then it will often be an OTIO data type. In some cases (specifically metadata) it can be a regular JSON dictionary. OTIO JSON files are typically formatted with indentation to make them easier to read. This makes the files slightly larger, but dramatically improves human readability which makes debugging much easier. Furthermore, the OTIO library will write the keys of each object in a predictable order to help with change tracking, comparisons, etc. Since human readablility and ease of use are explicit goals of the OpenTimelineIO project, it is recommended that OTIO JSON not be minified unless absolutely necessary. If a minimum file size is desired, the recommendation is to use gzip rather than minifying. ## Nesting A Timeline has one child, called "tracks" which is a Stack. Each of that Stack's children is a Track. From there on down each child can be any of these types: Clip, Filler, Stack, Track. In a simple case with one track of 3 clips: ``` Timeline "my timeline" Stack "tracks" Track "video track" Clip "intro" Clip "main" Clip "credits" ``` In order to make the tree structure easy to traverse, OTIO uses the name "children" for the list of child objects in each parent (except for Timeline's "tracks"). ## Metadata Timeline, Stack, Track, Clip, MediaReferece, and most other OTIO objects all have a `metadata` property. This metadata property holds a dictionary of key/value pairs which may be deeply nested, and may hold any variety of JSON-compatible data types (numbers, booleans, strings, arrays, dictionaries) as well as any other OTIO objects. This is intended to be a place to put information that does not fit into the schema defined properties. The core of OTIO doesn't do anything with this metadata, it only carries it along so that adapters, scripts, applications, or other workflows can use that metadata however needed. For example, several of the adapters shipped with OTIO use metadata to store information that doesn't (yet) fit into the core OTIO schema. Due to the fact that many different workflows can and will use metadata, it is important to group metadata inside namespaces so that independent workflows can coexist without encountering name collisions. In the example below, there is metadata on the Timeline and on several Clips for both a hypothetical `my_playback_tool` and `my_production_tracking_system` that could coexist with anything else added under a different namespace. Metadata can also be useful when prototyping new OTIO schemas. An existing object can be extended with metadata which can later be migrated into a new schema version, or a custom schema defined in a [SchemaDef plugin](write-a-schemadef). ## Example: ```json { "OTIO_SCHEMA": "Timeline.1", "metadata": { "my_playback_tool": { "metadata_overlay": "full_details", "loop": false }, "my_production_tracking_system": { "purpose": "dailies", "presentation_date": "2020-01-01", "owner": "rose" } }, "name": "transition_test", "tracks": { "OTIO_SCHEMA": "Stack.1", "children": [ { "OTIO_SCHEMA": "Track.1", "children": [ { "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "t0", "transition_type": "SMPTE_Dissolve", "parameters": {}, "in_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 }, "out_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": { "my_playback_tool": { "tags": ["for_review", "nightly_render"], }, "my_production_tracking_system": { "status": "IP", "due_date": "2020-02-01", "assigned_to": "rose" } }, "name": "A", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "t1", "transition_type": "SMPTE_Dissolve", "parameters": {}, "in_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 }, "out_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": { "my_playback_tool": { "tags": ["for_review", "nightly_render"], }, "my_production_tracking_system": { "status": "IP", "due_date": "2020-02-01", "assigned_to": "rose" } }, "name": "B", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Clip.1", "effects": [], "markers": [], "media_reference": null, "metadata": { "my_playback_tool": { "tags": [], }, "my_production_tracking_system": { "status": "final", "due_date": "2020-01-01", "assigned_to": null } }, "name": "C", "source_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 50 }, "start_time": { "OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0.0 } } }, { "OTIO_SCHEMA": "Transition.1", "metadata": {}, "name": "t3", "transition_type": "SMPTE_Dissolve", "parameters": {}, "in_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 }, "out_offset": { "OTIO_SCHEMA" : "RationalTime.1", "rate" : 24, "value" : 10 } } ], "effects": [], "kind": "Video", "markers": [], "metadata": {}, "name": "Track1", "source_range": null } ], "effects": [], "markers": [], "metadata": {}, "name": "tracks", "source_range": null } } ``` ## Schema Specification To see an autogenerated documentation of the serialized types and their fields, see this: [Autogenerated Serialized File Format](otio-serialized-schema). opentimelineio-0.18.1/docs/tutorials/otio-filebundles.md0000664000175000017500000001332515110656141021157 0ustar meme# File Bundles ## Overview This document describes OpenTimelineIO's file bundle formats, `otiod` and `otioz`, as well as how to use the internal adapters that read and write them. The OTIOZ/D File Bundle formats package OpenTimelineIO data and associated media into a single file. This can be useful for sending, archiving and interchange of a single unit that collects cut information and media together. ## OTIOZ/D File Bundle Format Details There are two encodings for OTIO file bundles, OTIOZ and OTIOD. OTIOD is an encoding in the file system that uses a directory hierarchy of files. OTIOZ is the identical structure packed into a single .zip file, currently using the python `zipfile` library. Both contain a content.otio entry at the top level which contains the cut information for the bundle. ### Structure File bundles have a consistent structure: OTIOD: ``` something.otiod (directory) ├── content.otio (file) └── media (directory) ├── media1 (file)    ├── media2 (file)    └── media3 (file) ``` OTIOZ (adds the version.txt file and is encoded in a zipfile): ``` something.otioz (zipfile) ├── content.otio (compressed file) ├── version.txt (compressed file) └── media (directory) ├── media1 (uncompressed file)    ├── media2 (uncompressed file)    ├── media3 (uncompressed file)    └── ... (uncompressed files) ``` ### content.otio file This is an OpenTimelineIO file whose media references are either `MissingReference`s, or `ExternalReference`s with target_urls that are relative paths pointing into the `media` directory. ### version.txt file This file encodes the otioz version of the file, with no other text, in the form: ``` 1.0.0 ``` ### "media" Directory The `media` directory contains all the media files that the `ExternalReference`s `target_url`s in the `content.otio` point at, in a flat structure. Each media file must have a unique basename, but can be encoded in whichever codec/container the user wishes (otio is unable to decode or encode the media files). ## Adapter Usage ## Read Adapter Behavior When a bundle is read from disk using the OpenTimelineIO Python API (using the adapters.read_from_* functions), only the `content.otio` file is read and parsed. For example, to get some stats of the timeline (not the media) of an otioz file in `otiostat`, you can run: `otiostat something.otioz` Because this will _only_ read the `content.otio` from the bundle, it is usually a fast operation to run. None of the media is decoded or unzipped during this process. ### extract_to_directory Optional Argument extract_to_directory: if a value other than `None` is passed in, will extract the contents of the bundle into the directory at the path passed into the `extract_to_directory` argument. For the OTIOZ adapter, this will unzip the associated media. ### absolute_media_reference_paths Optional Argument The OTIOD adapter additionally has an argument `absolute_media_reference_paths` which will convert all the media references in the bundle to be absolute paths if `True` is passed. Default is `False`. ### Read Adapter Example Extract the contents of the bundle and convert to an rv playlist: `otioconvert -i /var/tmp/some_file.otioz -a extract_to_directory=/var/tmp/example_directory -o /var/tmp/example_directory/some_file.rv` ## Write Adapter ### Source Timeline Constraints For creating otio bundles using the provided python adapter, an OTIO file is used as input. There are some constraints on the source timeline. #### Unique Basenames Because file bundles have a flat namespace for media, and media will be copied into the bundle, the `ExternalReference` media references in the source OTIO must have a target_url fields pointing at media files with unique basenames. For example, if there are media references that point at: `/project_a/academy_leader.mov` and: `/project_b/academy_leader.mov` Because the basename of both files is `academy_leader.mov`, this will be an error. The adapters have different policies for how to handle media references. See below for more information. #### Expected Source Timeline External Reference URL Format The file bundle adapters expect the `target_url` field of any `media_reference`s in the source timeline to be in one of two forms (as produced by python's [urlparse](https://docs.python.org/3/library/urllib.parse.html) library): - absolute path: "file:///path/to/some/file" (encodes "/path/to/some/file") - relative path: "path/to/some/file" (the path is relative to the current working directory of the command running the adapter on the source timeline). ### MediaReferencePolicy Option When building a file bundle using the OTIOZ/OTIOD adapters, you can set the 'media reference policy', which is described by an enum in the file_bundle_utils module. The policies can be: - (default) `ErrorIfNotFile`: will raise an exception if a media reference is found that is of type `ExternalReference` but that does not point at a `target_url`. - `MissingIfNotFile`: will replace any media references that meet the above condition with a `MissingReference`, preserving the original media reference in the metadata of the new `MissingReference`. - `AllMissing`: will replace all media references with `MissingReference`, preserving the original media reference in metadata on the new object. When running in `AllMissing` mode, no media will be put into the bundle. To use this argument with `otioconvert` from the commandline, you can use the `-A` flag with the argument name `media_policy`: ``` otioconvert -i -o path/to/output_file.otioz -A media_policy="AllMissing" ``` ### Write Adapter Example Convert an otio into a zip bundle: `otioconvert -i some_file.otio -o /var/tmp/some_file.otioz` opentimelineio-0.18.1/docs/tutorials/spatial-coordinates.md0000664000175000017500000000767215110656141021670 0ustar meme# OTIO Spatial Coordinate System This document describes a proposed coordinate system for OpenTimelineIO. It focuses mainly on the Bounds object, which is a rectangular area represented through a 2D box within that coordinate system. ## Coordinate System The proposed spatial coordinate system is unit-less. It allows decoupling clip layouts from pixel density. It has a single origin (X=0.0, Y=0.0) and is used as a unique canvas across the whole Timeline. Y-Axis-Up convention is used. We propose in the OTIO spatial coordinate system that we use planes that are unique in the continuous domain in order to make it analogous to the existing temporal implementation. Currently in a Composition of Items a RationalTime's value (usually a frame) index is seen as belonging to the Item that appears later in time. For example, if we have two Items ranging from value 1 to value 2 and from value 2 to value 3, value 2 will belong to the second Item. Or in other words, we are using exclusive temporal bounds such that the temporal spans are [1,2) and [2,3). In order to preserve the same logic in the spatial domain we require a plane that is unique in the continuous domain. To use a similar example given a 2 dimensional bound from (0, 1), and another from (1, 2), we require that a sample at the value 1 falls strictly into one bound or the other, in particular, it should fall into the higher value bound (1,2) and not into (0, 1). ![Coordinate System](../_static/spatial_coords_system.svg) ## Bounds A Bounds object is a 2D box that defines a spatial area in the unit-less coordinate system. Here is an example of Bounds defining a first-quadrant-snapped rectangle with a width of 16 and a height of 9. Note that, since Bounds are serializable object, they have a metadata member. ``` "available_image_bounds": { "OTIO_SCHEMA": "Box2d.1", "min": { "OTIO_SCHEMA":"V2d.1", "x": 0.0, "y": 0.0 }, "max": { "OTIO_SCHEMA":"V2d.1", "x": 16.0, "y": 9.0 } } ``` ![Example 1](../_static/spatial_coords_example1.svg) Here is another example of Bounds defining an origin-centered rectangle with a width of 16 and a height of 9. ``` "available_image_bounds": { "OTIO_SCHEMA": "Box2d.1", "min": { "OTIO_SCHEMA":"V2d.1", "x": -8.0, "y": -4.5 }, "max": { "OTIO_SCHEMA":"V2d.1", "x": 8.0, "y": 4.5 } } ``` ![Example 2](../_static/spatial_coords_example2.svg) When multiple clips are being rendered, either through a transition or through the presence of a multi-tracks timeline, all clips share the same coordinate system. For instance, if we add an origin-centered square clip on top of the previous one, here is what we get. ![Example 3](../_static/spatial_coords_example3.svg) Since each clip has its own bounds properties, clips can be arranged into complex layouts. This can be used in several contexts, for instance: - Side-by-side comparison - Picture-In-Picture - Complex spatial layouts for notes (collage) Example of a Picture-In-Picture layout added on top of the previous 2-clips layout: ![Example 4](../_static/spatial_coords_example4.svg) ## Bounds and Clips Currently, we use the Bounds object at the ***Clip.media_reference.bounds*** level. This allows support for different bounds when changing the media-representation of a given Clip. For instance, a Clip could have 2 media-representations (a set of High-Res 16:9 OpenEXR files and a Low-Res 4:3 MP4 file). Those 2 media-representations might not cover the same spatial area, therefore it makes sense for them to have their individual Bounds region. ## Non-Bounds representations The coordinate system can also be used to describe non-rectangular coordinates. For instance, effects that have spatial-based parameters that need to be expressed in a resolution-independent way could use the same system. Examples of potential usage for this coordinate system: - Blur amount - Annotation position - Wipe bar position (angular mask) opentimelineio-0.18.1/docs/tutorials/architecture.md0000664000175000017500000001555015110656141020377 0ustar meme# Architecture Overview ---------- OpenTimelineIO is an open source library for the interchange of editorial information. This document describes the structure of the python library. To import the library into python: `import opentimelineio as otio` Canonical Structure -------------------- Although you can compose your OTIO files differently if you wish, the canonical OTIO structure is as follows: - root: `otio.schema.Timeline` This file contains information about the root of a timeline, including a `global_start_time` and a top level container, `tracks` - `timeline.tracks`: This member is a `otio.schema.Stack` which contains `otio.schema.Track` objects - `timeline.tracks[i]`: The `otio.schema.Track` contained by a `timeline.tracks` object contains the clips, transitions and subcontainers that compose the rest of the editorial data Modules -------- The most interesting pieces of OTIO to a developer integrating OTIO into another application or workflow are: - `otio.schema`: The classes that describe the in-memory OTIO representation - `otio.opentime`: Classes and utility functions for representing time, time ranges and time transforms - `otio.adapters`: Modules that can read/write to or from an on-disk format and the in-memory OTIO representation Additionally, for developers integrating OTIO into a studio pipeline: - `otio.media_linker`: Plugin system for writing studio or workflow specific media linkers that run after adapters read files The in-memory OTIO representation data model is rooted at an `otio.schema.Timeline` which has a member `tracks` which is a `otio.schema.Stack` of `otio.schema.Track`, which contain a list of items such as: - `otio.schema.Clip` - `otio.schema.Gap` - `otio.schema.Stack` - `otio.schema.Track` - `otio.schema.Transition` The `otio.schema.Clip` objects can reference media through a `otio.schema.ExternalReference` or indicate that they are missing a reference to real media with a `otio.schema.MissingReference`. All objects have a metadata dictionary for blind data. Schema composition objects (`otio.schema.Stack` and `otio.schema.Track`) implement the python mutable sequence API. A simple script that prints out each shot might look like: ```python import opentimelineio as otio # read the timeline into memory tl = otio.adapters.read_from_file("my_file.otio") for each_seq in tl.tracks: for each_item in each_seq: if isinstance(each_item, otio.schema.Clip): print each_item.media_reference ``` Or, in the case of a nested composition, like this: ```python import opentimelineio as otio # read the timeline into memory tl = otio.adapters.read_from_file("my_file.otio") for clip in tl.each_clip(): print clip.media_reference ``` ## Time on otio.schema.Clip A clip may set its timing information (which is used to compute its `duration()` or its `trimmed_range()`) by configuring either its: - `media_reference.available_range` This is the range of the available media that can be cut in. So for example, frames 10-100 have been rendered and prepared for editorial. - `source_range` The range of media that is cut into the sequence, in the space of the available range (if it is set). In other words, it further truncates the available_range. A clip must have at least one set or else its duration is not computable: ```python cl.duration() # raises: opentimelineio._otio.CannotComputeAvailableRangeError: Cannot compute available range: No available_range set on media reference on clip: Clip("", ExternalReference("file:///example.mov"), None, {}) ``` You may query the `available_range` and `trimmed_range` via accessors on the `Clip()` itself, for example: ```python cl.trimmed_range() cl.available_range() # == cl.media_reference.available_range ``` Generally, if you want to know the range of a clip, we recommend using the `trimmed_range()` method, since this takes both the `media_reference.available_range` and the `source_range` into consideration. ## Time On Clips in Containers Additionally, if you want to know the time of a clip in the context of a container, you can use the local: `trimmed_range_in_parent()` method, or a parent's `trimmed_range_of_child()`. These will additionally take into consideration the `source_range` of the parent container, if it is set. They return a range in the space of the specified parent container. ## otio.opentime Opentime encodes timing related information. ### RationalTime A point in time at `rt.value*(1/rt.rate)` seconds. Can be rescaled into another RationalTime's rate. ### TimeRange A range in time. Encodes the start time and the duration, meaning that end_time_inclusive (last portion of a sample in the time range) and end_time_exclusive can be computed. ## otio.adapters OpenTimelineIO includes several adapters for reading and writing from other file formats. The `otio.adapters` module has convenience functions that will auto-detect which adapter to use, or you can specify the one you want. Adapters can be added to the system (outside of the distribution) via JSON files that can be placed on the {term}`OTIO_PLUGIN_MANIFEST_PATH` environment variable to be made available to OTIO. Most common usage only cares about: - `timeline = otio.adapters.read_from_file(filepath)` - `timeline = otio.adapters.read_from_string(rawtext, adapter_name)` - `otio.adapters.write_to_file(timeline, filepath)` - `rawtext = otio.adapters.write_to_string(timeline, adapter_name)` The native format serialization (`.otio` files) is handled via the "otio_json" adapter, `otio.adapters.otio_json`. In most cases you don't need to worry about adapter names, just use `otio.adapters.read_from_file()` and `otio.adapters.write_to_file` and it will figure out which one to use based on the filename extension. For more information, see [How To Write An OpenTimelineIO Adapter](write-an-adapter). ## otio.media_linkers Media linkers run on the otio file after an adapter calls `.read_from_file()` or `.read_from_string()`. They are intended to replace media references that exist after the adapter runs (which depending on the adapter are likely to be `MissingReference`) with ones that point to valid files in the local system. Since media linkers are plugins, they can use proprietary knowledge or context and do not need to be part of OTIO itself. You may also specify a media linker to be run after the adapter, either via the `media_linker_name` argument to `.read_from_file()` or `.read_from_string()` or via the {term}`OTIO_DEFAULT_MEDIA_LINKER` environment variable. You can also turn the media linker off completely by setting the `media_linker_name` argument to `otio.media_linker.MediaLinkingPolicy.DoNotLinkMedia`. For more information about writing media linkers, see [How To Write An OpenTimelineIO Media Linker](write-a-media-linker). Example Scripts ---------------- Example scripts are located in the [examples subdirectory](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/tree/main/examples). opentimelineio-0.18.1/docs/tutorials/write-a-media-linker.md0000664000175000017500000000530115110656141021615 0ustar meme# Writing an OTIO Media Linker OpenTimelineIO Media Linkers are plugins that allow OTIO to replace MissingReferences with valid, site specific MediaReferences after an adapter reads a file. The current MediaLinker can be specified as an argument to `otio.adapters.read_from_file` or via an environment variable. If one is specified, then it will run after the adapter reads the contents of the file before it is returned back. ```python #/usr/bin/env python import opentimelineio as otio mytimeline = otio.adapters.read_from_file("something.edl", media_linker_name="awesome_studios_media_linker") ``` After the EDL adapter reads something.edl, the media linker "awesome_studios_media_linker" will run and link the media in the file (if it can). ## Registering Your Media Linker To create a new OTIO Adapter, you need to create a file mymedialinker.py. Then add a manifest that points at that python file: ```json { "OTIO_SCHEMA" : "PluginManifest.1", "media_linkers" : [ { "OTIO_SCHEMA" : "MediaLinker.1", "name" : "awesome_studios_media_linker", "filepath" : "mymedialinker.py" } ], "adapters" : [ ] } ``` Then you need to add this manifest to your {term}`OTIO_PLUGIN_MANIFEST_PATH` environment variable. Finally, to specify this linker as the default media linker, set {term}`OTIO_DEFAULT_MEDIA_LINKER` to the name of the media linker: ```bash export OTIO_DEFAULT_MEDIA_LINKER="awesome_studios_media_linker" ``` To package and share your media linker, follow [these instructions](write-an-adapter.md#packaging-and-sharing-custom-adapters). ## Writing a Media Linker To write a media linker, write a python file with a "link_media_reference" function in it that takes two arguments: "in_clip" (the clip to try and add a media reference to) and "media_linker_argument_map" (arguments passed from the calling function to the media linker. For example: ```python def link_media_reference(in_clip, media_linker_argument_map): d.update(media_linker_argument_map) # you'll probably want to set it to something other than a missing reference in_clip.media_reference = otio.schema.MissingReference( name=in_clip.name + "_tweaked", metadata=d ) ``` ## For Testing The otioconvert.py script has a --media-linker argument you can use to test out your media linker (once its on the path). ```bash env OTIO_PLUGIN_MANIFEST_PATH=mymanifest.otio.json:$OTIO_PLUGIN_MANIFEST_PATH bin/otioconvert.py -i somefile.edl --media-linker="awesome_studios_media_linker" -o /var/tmp/test.otio ``` opentimelineio-0.18.1/docs/tutorials/otiotool.md0000664000175000017500000003120615110656141017561 0ustar meme# otiotool Tutorial `otiotool` is a command-line utility in OpenTimelineIO for inspecting, manipulating, and transforming OTIO timeline files. This tutorial covers its main features and usage patterns, with practical examples. ## Installation `otiotool` is included with several other command line utilities as part of the OpenTimelineIO Python module. You can install it via typical Python utilities like `pip`, etc. See [Quickstart](./quickstart) for details. > [!TIP] > If you have [uv installed](https://docs.astral.sh/uv/), then you can use `otiotool` with this handy shortcut without having to deal with any installation: ```bash uvx --from opentimelineio otiotool ``` ## Basic Usage `otiotool` reads one or more OTIO timeline files, optionally makes changes to the timelines, and outputs a text report and/or a new OTIO file with the result. To run `otiotool` for reporting, use options like `--list-clips` or `--list-tracks`. The report output is printed to the console: ```bash otiotool --input [more inputs...] [options] ``` Report output can be redirected from standard output to a file like any terminal program: ```bash otiotool --input [more inputs...] [options] > report.txt ``` To run `otiotool` to create a new OTIO file, use: ```bash otiotool --input [more inputs...] [options] --output ``` Many of `otiotool`'s command line options have a long and a short form. For example: `--input` is also `-i`, and `--output` is `-o`. Multiple options of `otiotool` can be combined into a single invocation. For example, you might read a file, trim it, remove some tracks, verify missing media into a report and output a new file all in one command like this: ```bash otiotool -i multitrack.otio -trim 20 30 --video-only --verify-media -o output.otio > report.txt ``` For a complete listing of all options use `otiotool -h`. ## Phases Unlike some other command line tools, the order in which most options appear on the command line does not matter. For example these two commands do the same thing: ```bash otiotool -i input.otio --flatten -o output.otio otiotool --flatten -o output.otio -i input.otio ``` The only time that command line argument ordering matters is when multiple input files are specified and operations like `--stack` and `--concat` combine them together. Instead, the features of this tool work in phases, as follows: 1. Input Input files provided by the `--input ` argument(s) are read into memory. Files may be OTIO format, or any format supported by adapter plugins. 2. Filtering Options such as `--video-only`, `--audio-only`, `--only-tracks-with-name`, `--only-tracks-with-index`, `--only-clips-with-name`, `--only-clips-with-name-regex`, `--remove-transitions`, and `--trim` will remove content. Only the tracks, clips, etc. that pass all of the filtering options provided are passed to the next phase. 3. Combine If specified, the `--stack` or `--concat` operations are performed (in that order) to combine all of the input timeline(s) into one. 4. Flatten If `--flatten` is specified, multiple tracks are flattened into one. 5. Relink The `--relink-by-name` option, will scan the specified folder(s) looking for files which match the name of each clip in the input timeline(s). If matching files are found, clips will be relinked to those files (using file:// URLs). Clip names are matched to filenames ignoring file extension. If specified, the `--copy-media-to-folder` option, will copy or download all linked media, and relink the OTIO to reference the local copies. 6. Remove/Redact The `--remove-metadata-key` option allows you to remove a specific piece of metadata from all objects. If specified, the `--redact` option, will remove ALL metadata and rename all objects in the OTIO with generic names (e.g. "Track 1", "Clip 17", etc.) 7. Inspect Options such as `--stats`, `--list-clips`, `--list-tracks`, `--list-media`, `--verify-media`, `--list-markers`, `--verify-ranges`, and `--inspect` will examine the OTIO and print information to standard output. 8. Output Finally, if the `--output ` option is specified, the resulting OTIO will be written to the specified file. The extension of the output filename is used to determine the format of the output (e.g. OTIO or any format supported by the adapter plugins.) If you need to output an older schema version, see the `--downgrade` option. ## Listing Timeline Contents ### List Tracks Prints all tracks in the timeline: ```bash otiotool -i multitrack.otio --list-tracks ``` Output: ``` TIMELINE: OTIO TEST - multitrack.Exported.01 TRACK: Sequence (Video) TRACK: Sequence 2 (Video) TRACK: Sequence 3 (Video) ``` ### List Clips, Markers, etc. Prints all clips and markers in the timeline: ```bash otiotool -i screening_example.otio --list-clips --list-markers ``` Output: ``` TIMELINE: Example_Screening.01 CLIP: ZZ100_501 (LAY3) CLIP: ZZ100_502A (LAY3) CLIP: ZZ100_503A (LAY1) CLIP: ZZ100_504C (LAY1) MARKER: global: 00:59:49:13 local: 01:00:01:14 duration: 0.0 color: RED name: ANIM FIX NEEDED MARKER: global: 00:59:50:13 local: 01:00:02:14 duration: 0.0 color: PINK ... ``` ## Filtering Tracks and Clips ### Video or Audio Only List only video or audio clips: ```bash otiotool -i premiere_example.otio --video-only --list-clips otiotool -i premiere_example.otio --audio-only --list-clips ``` ### Filter by Track Name or Index ```bash otiotool -i multitrack.otio --only-tracks-with-name "Sequence 3" --list-clips otiotool -i multitrack.otio --only-tracks-with-index 3 --list-clips ``` Indexes for `--only-tracks-with-index` begin at 1 for the first track, and that you often want to use it in combination with `--video-only` or `--audio-only`. ### Filter Clips by Name or Regex ```bash otiotool -i premiere_example.otio --list-clips --only-clips-with-name "sc01_sh010_anim.mov" otiotool -i premiere_example.otio --list-clips --only-clips-with-name-regex "sh\d+_anim" ``` The `--only-clips-with-name-regex` option uses the [Python Regular Expression syntax](https://docs.python.org/3/library/re.html). ## Media Information ### List Media References ```bash otiotool -i multitrack.otio --list-tracks --list-clips --list-media ``` ### Verify Media Existence Checks if media files exist. Only local file paths are checked by `otiotool`, not URLs or other non-file path media references. ```bash otiotool -i premiere_example.otio --verify-media ``` ## Statistics and Inspection ### Print Timeline Stats ```bash otiotool -i multitrack.otio --stats ``` Output: ``` Name: OTIO TEST - multitrack.Exported.01 Start: 00:00:00:00 End: 00:02:16:18 Duration: 00:02:16:18 ``` ### Inspect Items Show details for a specific item: ```bash otiotool -i multitrack.otio --inspect "KOLL" ``` Output: ``` TIMELINE: OTIO TEST - multitrack.Exported.01 ITEM: KOLL-HD.mp4 () source_range: TimeRange(RationalTime(0, 24), RationalTime(640, 24)) trimmed_range: TimeRange(RationalTime(0, 24), RationalTime(640, 24)) visible_range: TimeRange(RationalTime(0, 24), RationalTime(640, 24)) range_in_parent: TimeRange(RationalTime(1198, 24), RationalTime(640, 24)) trimmed range in timeline: TimeRange(RationalTime(1198, 24), RationalTime(640, 24)) visible range in timeline: TimeRange(RationalTime(1198, 24), RationalTime(640, 24)) range in Sequence 3 (): TimeRange(RationalTime(1198, 24), RationalTime(640, 24)) range in NestedScope (): TimeRange(RationalTime(1198, 24), RationalTime(640, 24)) ``` ## Input/Output ### Input File(s) Multiple input files can be specified via `--input` like this: ```bash otiotool -i one.otio two.otio three.otio --concat -o result.otio ``` > [!NOTE] > When `otiotool` is given multiple inputs, the order of those inputs will affect the outcome of `--concat`, `--stack`, and any text reports printed to the console. ### Output File Modifications to the timeline(s) can be written out to a new file with the `--output ` option. > [!NOTE] > The input files are never modified unless the output path specifies the same file, in which case that file will be overwritten (not recommended). ### Multiple Timelines If the result is a single timeline, then the output file will contain that timeline as expected. However, if there were multiple input files and those timelines were not combined with `--concat` or `--stack` then the output will be a single file containing a SerializableCollection with multiple timelines. This is a supported OTIO feature, but many tools and workflows expect only a single timeline in an OTIO file. ### Standard In/Out You can chain `otiotool` with other tools on the command line. If you specify the `--output` file as a single `-` then the resulting OTIO will be printed as text to stdout instead of a file. ```bash otiotool -i multitrack.otio --video-only -o - | grep MissingReference ``` You can also use `-` as an input from stdin. ```bash curl https://example.com/some/path/premiere_example.otio | otiotool -i - --verify-media --stats ``` ### Format Conversion The format of the input and output file is inferred from the filename extension. It can be `.otio` for an OTIO file, or any other file format supported by an available [OTIO adapter plugin](./adapters). Thus `otiotool` can operate much like `otioconvert` however some more advanced conversion options are only available in `otioconvert`. If you need both, you can write to an intermediate OTIO file and convert to/from the other format in a separate step. ```bash otiotool -i multitrack.otio --flatten video --video-only -o single-track.otio ``` Combined with conversion to EDL (via [this adapter plugin](https://github.com/OpenTimelineIO/otio-cmx3600-adapter)): ```bash uvx --from opentimelineio --with otio-cmx3600-adapter otiotool -i multitrack.otio --flatten video --video-only -o single-track.edl ``` ## Timeline Manipulation ### Trim Timeline Trim to a time range: ```bash otiotool -i multitrack.otio --trim 20.5 40 -o output.otio otiotool -i multitrack.otio --trim 00:01:00:00 00:02:00:00 -o output.otio ``` The start and end times for `--trim` can be either a floating point number of seconds or timecode `HH:MM:SS:FF` in the frame rate inferred from the timeline itself. ### Flatten Tracks Combine multiple tracks into one with `--flatten ` where `TYPE` is either `video`, `audio`, or `all`: ```bash otiotool -i multitrack.otio --flatten video -o output.otio --list-tracks ``` ### Stack or Concatenate Timelines > [!NOTE] > With `--stack` and `--concat` the order of the input files affects the outcome. When concatenated, the inputs are assembled in the order listed, so the first input is earliest on the output timeline. Concat Example: ```bash otiotool -i opening.otio end_credits.otio --concat -o output.otio ``` ``` When stacked, video tracks layer bottom-to-top, so the video tracks of the second input are layered above the first input. This follows conventional video/audio ordering where video tracks are layered numerically increasing upward (V2 is above V1). Audio tracks are layered in the opposite order, since traditionally audio tracks are layered numerically increasing downward (A2 is below A1). Stack Example: ```bash otiotool -i a.otio b.otio --stack -o output.otio ``` ### Redact Timeline Replace names of clips, tracks, etc. with generic labels: ```bash otiotool -i multitrack.otio --redact -o output.otio --list-clips ``` Output: ``` TIMELINE: Timeline #1 CLIP: Clip #1 CLIP: Clip #2 CLIP: Clip #3 CLIP: Clip #4 CLIP: Clip #5 ``` This feature is meant for cases where you want to share an OTIO without leaking sensitive information that might appear in a clip name, metadata, etc. For example when filing a bug report. Please look at the file contents after running this to ensure everything you care about was handled. ### Remove Transitions Remove all transitions: ```bash otiotool -i transition.otio --remove-transitions -o output.otio ``` ## OTIO Schema Versions When `otiotool` reads an older OTIO format, it will automatically upgrade the file to the newest schema supported by `otiotool`. When working with an application or workflow that requires an older OTIO file format, you can use `otiotool` to downgrade an OTIO to a specific schema version which is compatible. See [Versioning Schemas](./versioning-schemas) to understand this in detail. ```bash otiotool --list-versions ``` Output: ``` Available versions for --downgrade FAMILY:VERSION OTIO_CORE:0.14.0 OTIO_CORE:0.15.0 OTIO_CORE:0.16.0 OTIO_CORE:0.17.0 ``` ```bash otiotool -i multitrack.otio --downgrade OTIO_CORE:0.14.0 -o old-format.otio ``` opentimelineio-0.18.1/docs/tutorials/quickstart.md0000664000175000017500000001177715110656141020116 0ustar meme# Quickstart **Note** This guide assumes that you are working inside a [virtualenv](https://virtualenv.pypa.io/en/latest/). ## Install OTIO - `python -m pip install opentimelineio` ## Setup Any Additional Adapters You May Want A default OTIO installation includes only the "Core" adapters, which include the native OTIO JSON format (`.otio`), OpenTimelineIO directory bundles (`.otiod`), and OpenTimelineIO ZIP bundles (`.otiod`). A curated list of adapters for popular file formats like EDL, AAF, ALE, and FCP XML can be installed using the [OpenTimelineIO Plugins package in PyPI](https://pypi.org/project/OpenTimelineIO-Plugins/). These plugins can also be individually installed from their PyPI packages. For more information, see the [Adapters](./adapters) section. ## Timeline Viewers OpenTimelineIO provides applications to view timelines in a graphical interface. ### Raven [Raven](https://github.com/OpenTimelineIO/raven) is the preferred application and replaces OTIOView as the main application for viewing timelines. ### OTIOView [OTIOView](https://github.com/OpenTimelineIO/otioview) has been moved to its own repository so those who rely on it still have access to it. # Developer Quickstart Get the source and submodules: + `git clone git@github.com:AcademySoftwareFoundation/OpenTimelineIO.git` Before reading further, it is good to note that there is two parts to the C++ code: the OTIO C++ library that you can use in your C++ projects, and the C++ Python bindings that makes the C++ library available in Python. ## To build OTIO for C++ development: ### Linux/Mac ```bash mkdir build cd build cmake .. { options } make install ``` ### Windows - in an "x64 Native Tools Command Prompt" for Visual Studio ```bash mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX={path/to/install/location} { options } cmake --build . --target install --config Release ``` The `CMAKE_INSTALL_PREFIX` variable must be set to a path with no spaces in it, because CMake's default install location is in `C:\Program Files`, which won't work with OpenTimelineIO due to spaces in the path. ## To build OTIO for Python development: + `python -m pip install -e .` ## To build OTIO for both C++ and Python development: The Python package is a mix of pure python and C++ code. Therefore, it is recommended to use the python tooling (`python -m pip`) to develop both the C++ binding and the pure python code. We use `setuptools` as our python build backend, which means `pip` will call the `setup.py` in the root of the directory to build both the pure python and the C++ bindings. `setuptools` will take care of all the complexity of building a C++ Python extension for you. The first time `setup.py` is run, cmake scripts will be created, and the headers and libraries will be installed where you specify. If the C++ or Python sources are subsequently modified, running this command again will build and update everything appropriately. **Note** Any CMake arguments can be passed through `pip` by using the `CMAKE_ARGS` environment variable when building from source. *nix Example: ```bash env CMAKE_ARGS="-DCMAKE_VAR=VALUE1 -DCMAKE_VAR_2=VALUE2" python -m pip install . ``` `python -m pip install .` adds some overhead that might be annoying or unwanted when developing the python bindings. For that reason (and only that reason), if you want a faster iteration process, you can use `setuptools` commands. For example you can use `python setup.py build_ext` to only run the compilation step. Be aware that calling `setup.py` directly is highly discouraged and should only be used when no of the other options are viable. See https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html. To compile your own C++ file referencing the OTIO headers from your C++ build using gcc or clang, add the following -I flags: + `c++ -c source.cpp -I/home/someone/cxx-otio-root/include -I/home/someone/cxx-otio-root/include/opentimelineio/deps` To link your own program against your OTIO build using gcc or clang, add the following -L/-l flags: + `c++ ... -L/home/someone/cxx-otio-root/lib -lopentimelineio` To use opentime without opentimelineio, link with -lopentime instead, and compile with: + `c++ -c source.cpp -I/home/someone/cxx-otio-root/include` # Debugging Quickstart ## Linux / GDB / LLDB To compile in debug mode, set the `OTIO_CXX_DEBUG_BUILD` environment variable to any value and then `python -m pip install`. You can then attach GDB to python and run your program: + `gdb --args python script_you_want_to_debug.py` Or LLDB: + `lldb -- python script_you_want_to_debug.py` One handy tip is that you can trigger a breakpoint in gdb by inserting a SIGINT: ```c++ #include // ... std::raise(SIGINT); ``` GDB will automatically break when it hits the SIGINT line. # How to Generate the C++ Documentation: ## Mac / Linux The doxygen docs can be generated with the following commands: ``` cd doxygen ; doxygen config/dox_config ; cd .. ``` Another option is to trigger the make target: ``` make doc-cpp ``` opentimelineio-0.18.1/docs/tutorials/otio-serialized-schema.md0000664000175000017500000004007615110656141022257 0ustar meme# Serialized Data Documentation This documents all the OpenTimelineIO classes that serialize to and from JSON, omitting SchemaDef plugins. This document is automatically generated by running: `src/py-opentimelineio/opentimelineio/console/autogen_serialized_datamodel.py` or by running: `make doc-model` It is part of the unit tests suite and should be updated whenever the schema changes. If it needs to be updated and this file regenerated, run: `make doc-model-update` # Class Documentation ## Module: opentimelineio.adapters ### Adapter.1 *full module path*: `opentimelineio.adapters.Adapter` *documentation*: ``` Adapters convert between OTIO and other formats. Note that this class is not subclassed by adapters. Rather, an adapter is a python module that implements at least one of the following functions: .. code-block:: python write_to_string(input_otio) write_to_file(input_otio, filepath) (optionally inferred) read_from_string(input_str) read_from_file(filepath) (optionally inferred) ...as well as a small json file that advertises the features of the adapter to OTIO. This class serves as the wrapper around these modules internal to OTIO. You should not need to extend this class to create new adapters for OTIO. For more information: https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an- adapter.html. # noqa ``` parameters: - *filepath*: Absolute path or relative path to adapter module from location of json. - *name*: Adapter name. - *suffixes*: File suffixes associated with this adapter. ## Module: opentimelineio.core ### Color.1 *full module path*: `opentimelineio.core.Color` *documentation*: ``` :class:`Color` is a definition of red, green, blue, and alpha double floating point values, allowing conversion between different formats. To be considered interoperable, the sRGB transfer function encoded values, ranging between zero and one, are expected to be accurate to within 1/255 of the intended value. Round-trip conversions may not be guaranteed outside that. This Color class is meant for use in user interface elements, like marker or clip coloring, NOT for image pixel content. ``` parameters: - *a*: - *b*: - *g*: - *name*: - *r*: ### Composable.1 *full module path*: `opentimelineio.core.Composable` *documentation*: ``` An object that can be composed within a :class:`~Composition` (such as :class:`~Track` or :class:`.Stack`). ``` parameters: - *metadata*: - *name*: ### Composition.1 *full module path*: `opentimelineio.core.Composition` *documentation*: ``` Base class for an :class:`~Item` that contains :class:`~Composable`\s. Should be subclassed (for example by :class:`.Track` and :class:`.Stack`), not used directly. ``` parameters: - *color*: - *effects*: - *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden. - *markers*: - *metadata*: - *name*: - *source_range*: ### Item.1 *full module path*: `opentimelineio.core.Item` *documentation*: ``` None ``` parameters: - *color*: - *effects*: - *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden. - *markers*: - *metadata*: - *name*: - *source_range*: ### MediaReference.1 *full module path*: `opentimelineio.core.MediaReference` *documentation*: ``` None ``` parameters: - *available_image_bounds*: - *available_range*: - *metadata*: - *name*: ### SerializableObjectWithMetadata.1 *full module path*: `opentimelineio.core.SerializableObjectWithMetadata` *documentation*: ``` None ``` parameters: - *metadata*: - *name*: ## Module: opentimelineio.hooks ### HookScript.1 *full module path*: `opentimelineio.hooks.HookScript` *documentation*: ``` None ``` parameters: - *filepath*: Absolute path or relative path to adapter module from location of json. - *name*: Adapter name. ## Module: opentimelineio.media_linker ### MediaLinker.1 *full module path*: `opentimelineio.media_linker.MediaLinker` *documentation*: ``` None ``` parameters: - *filepath*: Absolute path or relative path to adapter module from location of json. - *name*: Adapter name. ## Module: opentimelineio.opentime ### RationalTime.1 *full module path*: `opentimelineio.opentime.RationalTime` *documentation*: ``` The RationalTime class represents a measure of time of :math:`rt.value/rt.rate` seconds. It can be rescaled into another :class:`~RationalTime`'s rate. ``` parameters: - *rate*: - *value*: ### TimeRange.1 *full module path*: `opentimelineio.opentime.TimeRange` *documentation*: ``` The TimeRange class represents a range in time. It encodes the start time and the duration, meaning that :meth:`end_time_inclusive` (last portion of a sample in the time range) and :meth:`end_time_exclusive` can be computed. ``` parameters: - *duration*: - *start_time*: ### TimeTransform.1 *full module path*: `opentimelineio.opentime.TimeTransform` *documentation*: ``` 1D transform for :class:`~RationalTime`. Has offset and scale. ``` parameters: - *offset*: - *rate*: - *scale*: ## Module: opentimelineio.plugins ### PluginManifest.1 *full module path*: `opentimelineio.plugins.Manifest` *documentation*: ``` Defines an OTIO plugin Manifest. This is considered an internal OTIO implementation detail. A manifest tracks a collection of plugins and enables finding them by name or other features (in the case of adapters, what file suffixes they advertise support for). For more information, consult the documentation. ``` parameters: - *adapters*: Adapters this manifest describes. - *hook_scripts*: Scripts that can be attached to hooks. - *hooks*: Hooks that hooks scripts can be attached to. - *media_linkers*: Media Linkers this manifest describes. - *schemadefs*: Schemadefs this manifest describes. - *version_manifests*: Sets of versions to downgrade schemas to. ### SerializableObject.1 *full module path*: `opentimelineio.plugins.PythonPlugin` *documentation*: ``` A class of plugin that is encoded in a python module, exposed via a manifest. ``` parameters: - *filepath*: Absolute path or relative path to adapter module from location of json. - *name*: Adapter name. ## Module: opentimelineio.schema ### Clip.2 *full module path*: `opentimelineio.schema.Clip` *documentation*: ``` A :class:`~Clip` is a segment of editable media (usually audio or video). Contains a :class:`.MediaReference` and a trim on that media reference. ``` parameters: - *active_media_reference_key*: - *color*: - *effects*: - *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden. - *markers*: - *media_references*: - *metadata*: - *name*: - *source_range*: ### Effect.1 *full module path*: `opentimelineio.schema.Effect` *documentation*: ``` None ``` parameters: - *effect_name*: - *enabled*: If true, the Effect is applied. If false, the Effect is omitted. - *metadata*: - *name*: ### ExternalReference.1 *full module path*: `opentimelineio.schema.ExternalReference` *documentation*: ``` None ``` parameters: - *available_image_bounds*: - *available_range*: - *metadata*: - *name*: - *target_url*: ### FreezeFrame.1 *full module path*: `opentimelineio.schema.FreezeFrame` *documentation*: ``` Hold the first frame of the clip for the duration of the clip. ``` parameters: - *effect_name*: - *enabled*: If true, the Effect is applied. If false, the Effect is omitted. - *metadata*: - *name*: - *time_scalar*: Linear time scalar applied to clip. 2.0 means the clip occupies half the time in the parent item, i.e. plays at double speed, 0.5 means the clip occupies twice the time in the parent item, i.e. plays at half speed. Note that adjusting the time_scalar of a :class:`~LinearTimeWarp` does not affect the duration of the item this effect is attached to. Instead it affects the speed of the media displayed within that item. ### Gap.1 *full module path*: `opentimelineio.schema.Gap` *documentation*: ``` None ``` parameters: - *color*: - *effects*: - *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden. - *markers*: - *metadata*: - *name*: - *source_range*: ### GeneratorReference.1 *full module path*: `opentimelineio.schema.GeneratorReference` *documentation*: ``` None ``` parameters: - *available_image_bounds*: - *available_range*: - *generator_kind*: - *metadata*: - *name*: - *parameters*: ### ImageSequenceReference.1 *full module path*: `opentimelineio.schema.ImageSequenceReference` *documentation*: ``` An ImageSequenceReference refers to a numbered series of single-frame image files. Each file can be referred to by a URL generated by the :class:`~ImageSequenceReference`. Image sequences can have URLs with discontinuous frame numbers, for instance if you've only rendered every other frame in a sequence, your frame numbers may be 1, 3, 5, etc. This is configured using the ``frame_step`` attribute. In this case, the 0th image in the sequence is frame 1 and the 1st image in the sequence is frame 3. Because of this there are two numbering concepts in the image sequence, the image number and the frame number. Frame numbers are the integer numbers used in the frame file name. Image numbers are the 0-index based numbers of the frames available in the reference. Frame numbers can be discontinuous, image numbers will always be zero to the total count of frames minus 1. An example for 24fps media with a sample provided each frame numbered 1-1000 with a path ``/show/sequence/shot/sample_image_sequence.%04d.exr`` might be .. code-block:: json { "available_range": { "start_time": { "value": 0, "rate": 24 }, "duration": { "value": 1000, "rate": 24 } }, "start_frame": 1, "frame_step": 1, "rate": 24, "target_url_base": "file:///show/sequence/shot/", "name_prefix": "sample_image_sequence.", "name_suffix": ".exr" "frame_zero_padding": 4, } The same duration sequence but with only every 2nd frame available in the sequence would be .. code-block:: json { "available_range": { "start_time": { "value": 0, "rate": 24 }, "duration": { "value": 1000, "rate": 24 } }, "start_frame": 1, "frame_step": 2, "rate": 24, "target_url_base": "file:///show/sequence/shot/", "name_prefix": "sample_image_sequence.", "name_suffix": ".exr" "frame_zero_padding": 4, } A list of all the frame URLs in the sequence can be generated, regardless of frame step, with the following list comprehension .. code-block:: python [ref.target_url_for_image_number(i) for i in range(ref.number_of_images_in_sequence())] Negative ``start_frame`` is also handled. The above example with a ``start_frame`` of ``-1`` would yield the first three target urls as: - ``file:///show/sequence/shot/sample_image_sequence.-0001.exr`` - ``file:///show/sequence/shot/sample_image_sequence.0000.exr`` - ``file:///show/sequence/shot/sample_image_sequence.0001.exr`` ``` parameters: - *available_image_bounds*: - *available_range*: - *frame_step*: Step between frame numbers in file names. - *frame_zero_padding*: Number of digits to pad zeros out to in frame numbers. - *metadata*: - *missing_frame_policy*: Directive for how frames in sequence not found during playback or rendering should be handled. - *name*: - *name_prefix*: Everything in the file name leading up to the frame number. - *name_suffix*: Everything after the frame number in the file name. - *rate*: Frame rate if every frame in the sequence were played back. - *start_frame*: The first frame number used in file names. - *target_url_base*: Everything leading up to the file name in the ``target_url``. ### LinearTimeWarp.1 *full module path*: `opentimelineio.schema.LinearTimeWarp` *documentation*: ``` A time warp that applies a linear speed up or slow down across the entire clip. ``` parameters: - *effect_name*: - *enabled*: If true, the Effect is applied. If false, the Effect is omitted. - *metadata*: - *name*: - *time_scalar*: Linear time scalar applied to clip. 2.0 means the clip occupies half the time in the parent item, i.e. plays at double speed, 0.5 means the clip occupies twice the time in the parent item, i.e. plays at half speed. Note that adjusting the time_scalar of a :class:`~LinearTimeWarp` does not affect the duration of the item this effect is attached to. Instead it affects the speed of the media displayed within that item. ### Marker.2 *full module path*: `opentimelineio.schema.Marker` *documentation*: ``` A marker indicates a marked range of time on an item in a timeline, usually with a name, color or other metadata. The marked range may have a zero duration. The marked range is in the owning item's time coordinate system. ``` parameters: - *color*: Color string for this marker (for example: 'RED'), based on the :class:`~Color` enum. - *comment*: Optional comment for this marker. - *marked_range*: Range this marker applies to, relative to the :class:`.Item` this marker is attached to (e.g. the :class:`.Clip` or :class:`.Track` that owns this marker). - *metadata*: - *name*: ### MissingReference.1 *full module path*: `opentimelineio.schema.MissingReference` *documentation*: ``` Represents media for which a concrete reference is missing. Note that a :class:`~MissingReference` may have useful metadata, even if the location of the media is not known. ``` parameters: - *available_image_bounds*: - *available_range*: - *metadata*: - *name*: ### SerializableCollection.1 *full module path*: `opentimelineio.schema.SerializableCollection` *documentation*: ``` A container which can hold an ordered list of any serializable objects. Note that this is not a :class:`.Composition` nor is it :class:`.Composable`. This container approximates the concept of a bin - a collection of :class:`.SerializableObject`\s that do not have any compositional meaning, but can serialize to/from OTIO correctly, with metadata and a named collection. A :class:`~SerializableCollection` is useful for serializing multiple timelines, clips, or media references to a single file. ``` parameters: - *metadata*: - *name*: ### Stack.1 *full module path*: `opentimelineio.schema.Stack` *documentation*: ``` None ``` parameters: - *color*: - *effects*: - *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden. - *markers*: - *metadata*: - *name*: - *source_range*: ### TimeEffect.1 *full module path*: `opentimelineio.schema.TimeEffect` *documentation*: ``` Base class for all effects that alter the timing of an item. ``` parameters: - *effect_name*: - *enabled*: If true, the Effect is applied. If false, the Effect is omitted. - *metadata*: - *name*: ### Timeline.1 *full module path*: `opentimelineio.schema.Timeline` *documentation*: ``` None ``` parameters: - *global_start_time*: - *metadata*: - *name*: - *tracks*: ### Track.1 *full module path*: `opentimelineio.schema.Track` *documentation*: ``` None ``` parameters: - *color*: - *effects*: - *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden. - *kind*: - *markers*: - *metadata*: - *name*: - *source_range*: ### Transition.1 *full module path*: `opentimelineio.schema.Transition` *documentation*: ``` Represents a transition between the two adjacent items in a :class:`.Track`. For example, a cross dissolve or wipe. ``` parameters: - *in_offset*: Amount of the previous clip this transition overlaps, exclusive. - *metadata*: - *name*: - *out_offset*: Amount of the next clip this transition overlaps, exclusive. - *transition_type*: Kind of transition, as defined by the :class:`Type` enum. ### SchemaDef.1 *full module path*: `opentimelineio.schema.SchemaDef` *documentation*: ``` None ``` parameters: - *filepath*: Absolute path or relative path to adapter module from location of json. - *name*: Adapter name. opentimelineio-0.18.1/examples/0000775000175000017500000000000015110656141014225 5ustar memeopentimelineio-0.18.1/examples/io_perf_test.cpp0000664000175000017500000001634115110656141017420 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include #include "opentimelineio/clip.h" #include "opentimelineio/typeRegistry.h" #include "opentimelineio/serialization.h" #include "opentimelineio/deserialization.h" #include "opentimelineio/timeline.h" #include "util.h" namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; using chrono_time_point = std::chrono::steady_clock::time_point; const struct { bool FIXED_TMP = true; bool PRINT_CPP_VERSION_FAMILY = false; bool TO_JSON_STRING = true; bool TO_JSON_STRING_NO_DOWNGRADE = true; bool TO_JSON_FILE = true; bool TO_JSON_FILE_NO_DOWNGRADE = true; bool CLONE_TEST = true; bool SINGLE_CLIP_DOWNGRADE_TEST = true; } RUN_STRUCT ; // typedef std::chrono::duration fsec; // auto t0 = Time::now(); // auto t1 = Time::now(); // fsec fs = t1 - t0; /// utility function for printing std::chrono elapsed time double print_elapsed_time( const std::string& message, const chrono_time_point& begin, const chrono_time_point& end ) { const std::chrono::duration dur = end - begin; std::cout << message << ": " << dur.count() << " [s]" << std::endl; return dur.count(); } void print_version_map() { std::cerr << "current version map: " << std::endl; for (const auto& kv_lbl: otio::CORE_VERSION_MAP) { std::cerr << " " << kv_lbl.first << std::endl; for (auto kv_schema_version : kv_lbl.second) { std::cerr << " \"" << kv_schema_version.first << "\": "; std::cerr << kv_schema_version.second << std::endl; } } } int main( int argc, char *argv[] ) { if (RUN_STRUCT.PRINT_CPP_VERSION_FAMILY) { print_version_map(); } if (argc < 2) { std::cerr << "usage: otio_io_perf_test path/to/timeline.otio "; std::cerr << "[--keep-tmp]" << std::endl; return 1; } bool keep_tmp = false; if (argc > 2) { const std::string arg = argv[2]; if (arg == "--keep-tmp") { keep_tmp = true; } } const std::string tmp_dir_path = ( RUN_STRUCT.FIXED_TMP ? "/var/tmp/ioperftest" : examples::create_temp_dir() ); otio::ErrorStatus err; assert(!otio::is_error(err)); otio::schema_version_map downgrade_manifest = { {"FakeSchema", 3}, {"Clip", 1}, {"OtherThing", 12000} }; if (RUN_STRUCT.CLONE_TEST) { otio::SerializableObject::Retainer cl = new otio::Clip("test"); cl->metadata()["example thing"] = "banana"; const auto intermediate = cl->clone(&err); assert(intermediate != nullptr); const auto cl_clone = dynamic_cast(intermediate); assert(cl_clone != nullptr); assert(!otio::is_error(err)); assert(cl->name() == cl_clone->name()); } if (RUN_STRUCT.SINGLE_CLIP_DOWNGRADE_TEST) { otio::SerializableObject::Retainer cl = new otio::Clip("test"); cl->metadata()["example thing"] = "banana"; chrono_time_point begin = std::chrono::steady_clock::now(); cl->to_json_file( examples::normalize_path(tmp_dir_path + "/clip.otio"), &err, &downgrade_manifest ); chrono_time_point end = std::chrono::steady_clock::now(); assert(!otio::is_error(err)); print_elapsed_time("downgrade clip", begin, end); } std::any tl; std::string fname = std::string(argv[1]); // read file chrono_time_point begin = std::chrono::steady_clock::now(); otio::SerializableObject::Retainer timeline( dynamic_cast( otio::Timeline::from_json_file( examples::normalize_path(argv[1]), &err ) ) ); chrono_time_point end = std::chrono::steady_clock::now(); assert(!otio::is_error(err)); if (!timeline) { examples::print_error(err); return 1; } print_elapsed_time("deserialize_json_from_file", begin, end); double str_dg, str_nodg; if (RUN_STRUCT.TO_JSON_STRING) { begin = std::chrono::steady_clock::now(); const std::string result = timeline.value->to_json_string( &err, &downgrade_manifest ); end = std::chrono::steady_clock::now(); assert(!otio::is_error(err)); if (otio::is_error(err)) { examples::print_error(err); return 1; } str_dg = print_elapsed_time("serialize_json_to_string", begin, end); } if (RUN_STRUCT.TO_JSON_STRING_NO_DOWNGRADE) { begin = std::chrono::steady_clock::now(); const std::string result = timeline.value->to_json_string(&err, {}); end = std::chrono::steady_clock::now(); assert(!otio::is_error(err)); if (otio::is_error(err)) { examples::print_error(err); return 1; } str_nodg = print_elapsed_time( "serialize_json_to_string [no downgrade]", begin, end ); } if (RUN_STRUCT.TO_JSON_STRING && RUN_STRUCT.TO_JSON_STRING_NO_DOWNGRADE) { std::cout << " JSON to string no_dg/dg: " << str_dg / str_nodg; std::cout << std::endl; } double file_dg, file_nodg; if (RUN_STRUCT.TO_JSON_FILE) { begin = std::chrono::steady_clock::now(); timeline.value->to_json_file( examples::normalize_path(tmp_dir_path + "/io_perf_test.otio"), &err, &downgrade_manifest ); end = std::chrono::steady_clock::now(); assert(!otio::is_error(err)); file_dg = print_elapsed_time("serialize_json_to_file", begin, end); } if (RUN_STRUCT.TO_JSON_FILE_NO_DOWNGRADE) { begin = std::chrono::steady_clock::now(); timeline.value->to_json_file( examples::normalize_path( tmp_dir_path + "/io_perf_test.nodowngrade.otio" ), &err, {} ); end = std::chrono::steady_clock::now(); assert(!otio::is_error(err)); file_nodg = print_elapsed_time( "serialize_json_to_file [no downgrade]", begin, end ); } if (RUN_STRUCT.TO_JSON_FILE && RUN_STRUCT.TO_JSON_FILE_NO_DOWNGRADE) { std::cout << " JSON to file no_dg/dg: " << file_dg / file_nodg; std::cout << std::endl; } if (keep_tmp || RUN_STRUCT.FIXED_TMP) { std::cout << "Temp directory preserved. All files written to: "; std::cout << tmp_dir_path << std::endl; } else { // clean up const auto tmp_files = examples::glob(tmp_dir_path, "*"); for (const auto& fp : tmp_files) { remove(fp.c_str()); } remove(tmp_dir_path.c_str()); std::cout << "cleaned up tmp dir, pass --keep-tmp to preserve"; std::cout << " output." << std::endl; } return 0; } opentimelineio-0.18.1/examples/upgrade_downgrade_example.cpp0000664000175000017500000000544315110656141022133 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/serializableObject.h" #include "opentimelineio/typeRegistry.h" #include // demonstrates a minimal custom SerializableObject written in C++ with upgrade // and downgrade functions namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; // define the custom class class SimpleClass : public otio::SerializableObject { public: struct Schema { static auto constexpr name = "SimpleClass"; static int constexpr version = 2; }; void set_new_field(int64_t val) { _new_field = val; } int64_t new_field() const { return _new_field; } protected: using Parent = SerializableObject; virtual ~SimpleClass() = default; // methods for serialization virtual bool read_from(Reader& reader) override { auto result = ( reader.read("new_field", &_new_field) && Parent::read_from(reader) ); return result; } // ...and deserialization virtual void write_to(Writer& writer) const override { Parent::write_to(writer); writer.write("new_field", _new_field); } private: int64_t _new_field; }; int main( int argc, char *argv[] ) { // register type and upgrade/downgrade functions otio::TypeRegistry::instance().register_type(); // 1->2 otio::TypeRegistry::instance().register_upgrade_function( SimpleClass::Schema::name, 2, [](otio::AnyDictionary* d) { (*d)["new_field"] = (*d)["my_field"]; d->erase("my_field"); } ); // 2->1 otio::TypeRegistry::instance().register_downgrade_function( SimpleClass::Schema::name, 2, [](otio::AnyDictionary* d) { (*d)["my_field"] = (*d)["new_field"]; d->erase("new_field"); } ); otio::ErrorStatus err; auto sc = otio::SerializableObject::Retainer(new SimpleClass()); sc->set_new_field(12); // write it out to disk, without changing it sc->to_json_file("/var/tmp/simpleclass.otio", &err); otio::schema_version_map downgrade_manifest = { {"SimpleClass", 1} }; // write it out to disk, downgrading to version 1 sc->to_json_file("/var/tmp/simpleclass.otio", &err, &downgrade_manifest); // read it back, upgrading automatically back up to version 2 of the schema otio::SerializableObject::Retainer sc2( dynamic_cast( SimpleClass::from_json_file("/var/tmp/simpleclass.otio", &err) ) ); assert(sc2->new_field() == sc->new_field()); std::cout << "Upgrade/Downgrade demo complete." << std::endl; return 0; } opentimelineio-0.18.1/examples/summarize_timing.py0000775000175000017500000000771115110656141020173 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Example OTIO script that reads a timeline and then prints a summary of the video clips found, including re-timing effects on each one. """ import sys import opentimelineio as otio def _summarize_effects(item): if hasattr(item, "effects"): for effect in item.effects: if isinstance(effect, otio.schema.FreezeFrame): print( "Effect: Freeze Frame" ) elif isinstance(effect, otio.schema.LinearTimeWarp): print( "Effect: Linear Time Warp ({}%)".format( effect.time_scalar * 100.0 ) ) else: print( "Effect: {} ({})".format( effect.name, str(type(effect)) ) ) def _summarize_range(label, time_range): if time_range is None: print(f"\t{label}: None") else: print( "\t{}: {} - {} (Duration: {})".format( label, otio.opentime.to_timecode( time_range.start_time ), otio.opentime.to_timecode( time_range.end_time_exclusive() ), otio.opentime.to_timecode(time_range.duration) ) ) def _summarize_timeline(timeline): # Here we iterate over each video track, and then just the top-level # items in each track. If you want to traverse the whole nested structure # then you can use: for item in timeline.find_children() # or just clips via: for clip in timeline.find_clips() # See also: https://opentimelineio.readthedocs.io/en/latest/tutorials/otio-timeline-structure.html # noqa for track in timeline.video_tracks(): print( "Track: {}\n\tKind: {}\n\tDuration: {}".format( track.name, track.kind, track.duration() ) ) _summarize_effects(track) for item in track: if isinstance(item, otio.schema.Clip): clip = item print(f"Clip: {clip.name}") # See the documentation to understand the difference # between each of these ranges: # https://opentimelineio.readthedocs.io/en/latest/tutorials/time-ranges.html _summarize_range(" Trimmed Range", clip.trimmed_range()) _summarize_range(" Visible Range", clip.visible_range()) _summarize_range("Available Range", clip.available_range()) elif isinstance(item, otio.schema.Gap): continue elif isinstance(item, otio.schema.Transition): transition = item print( "Transition: {}\n\tDuration: {}".format( transition.transition_type, otio.opentime.to_timecode(transition.duration()) ) ) elif isinstance(item, otio.core.Composition): composition = item print( "Nested Composition: {}\n\tDuration: {}".format( composition.name, otio.opentime.to_timecode(composition.duration()) ) ) else: print( "Other: {} ({})\n\tDuration: {}".format( item.name, str(type(item)), otio.opentime.to_timecode(item.duration()) ) ) _summarize_effects(item) def main(): for filename in sys.argv[1:]: timeline = otio.adapters.read_from_file(filename) _summarize_timeline(timeline) if __name__ == '__main__': main() opentimelineio-0.18.1/examples/python_adapters_embed.cpp0000664000175000017500000001431215110656141021272 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project // Example OTIO C++ code for reading and writing files supported by the OTIO // Python adapters. // // This example uses an embedded Python interpreter to convert between // input/output files and JSON that can be used from C++ code. // // To run this example make sure the environment variable PYTHONPATH is set // correctly. #include "util.h" #include #include #include #include #if defined(_WINDOWS) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN #include #include #endif // _WINDOWS namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; class PythonAdapters { public: PythonAdapters(); ~PythonAdapters(); otio::SerializableObject::Retainer read_from_file( std::string const&, otio::ErrorStatus*); bool write_to_file( otio::SerializableObject::Retainer const&, std::string const&, otio::ErrorStatus*); }; PythonAdapters::PythonAdapters() { Py_Initialize(); } PythonAdapters::~PythonAdapters() { Py_Finalize(); } class PyObjectRef { public: PyObjectRef(PyObject* o) : o(o) { if (!o) { throw std::runtime_error("Python error"); } } ~PyObjectRef() { Py_XDECREF(o); } PyObject* o = nullptr; operator PyObject* () const { return o; } }; otio::SerializableObject::Retainer PythonAdapters::read_from_file( std::string const& file_name, otio::ErrorStatus* error_status) { otio::SerializableObject::Retainer timeline; try { // Import the OTIO Python module. auto p_module = PyObjectRef(PyImport_ImportModule("opentimelineio.adapters")); // Read the timeline into Python. auto p_read_from_file = PyObjectRef(PyObject_GetAttrString(p_module, "read_from_file")); auto p_read_from_file_args = PyObjectRef(PyTuple_New(1)); const std::string file_name_normalized = examples::normalize_path(file_name); auto p_read_from_file_arg = PyUnicode_FromStringAndSize(file_name_normalized.c_str(), file_name_normalized.size()); if (!p_read_from_file_arg) { throw std::runtime_error("cannot create arg"); } PyTuple_SetItem(p_read_from_file_args, 0, p_read_from_file_arg); auto p_timeline = PyObjectRef(PyObject_CallObject(p_read_from_file, p_read_from_file_args)); // Convert the Python timeline into a string and use that to create a C++ timeline. auto p_to_json_string = PyObjectRef(PyObject_GetAttrString(p_timeline, "to_json_string")); auto p_json_string = PyObjectRef(PyObject_CallObject(p_to_json_string, NULL)); timeline = otio::SerializableObject::Retainer( dynamic_cast(otio::Timeline::from_json_string( PyUnicode_AsUTF8AndSize(p_json_string, NULL), error_status))); } catch (const std::exception& e) { error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED; error_status->details = e.what(); } if (PyErr_Occurred()) { PyErr_Print(); } return timeline; } bool PythonAdapters::write_to_file( otio::SerializableObject::Retainer const& timeline, std::string const& file_name, otio::ErrorStatus* error_status) { bool r = false; try { // Import the OTIO Python module. auto p_module = PyObjectRef(PyImport_ImportModule("opentimelineio.adapters")); // Convert the C++ timeline to a string and pass that to Python. const auto string = timeline.value->to_json_string(error_status); if (otio::is_error(error_status)) { throw std::runtime_error("cannot convert to string"); } auto p_read_from_string = PyObjectRef(PyObject_GetAttrString(p_module, "read_from_string")); auto p_read_from_string_args = PyObjectRef(PyTuple_New(1)); auto p_read_from_string_arg = PyUnicode_FromStringAndSize(string.c_str(), string.size()); if (!p_read_from_string_arg) { throw std::runtime_error("cannot create arg"); } PyTuple_SetItem(p_read_from_string_args, 0, p_read_from_string_arg); auto p_timeline = PyObjectRef(PyObject_CallObject(p_read_from_string, p_read_from_string_args)); // Write the Python timeline. auto p_write_to_file = PyObjectRef(PyObject_GetAttrString(p_module, "write_to_file")); auto p_write_to_file_args = PyObjectRef(PyTuple_New(2)); const std::string file_name_normalized = examples::normalize_path(file_name); auto p_write_to_file_arg = PyUnicode_FromStringAndSize(file_name_normalized.c_str(), file_name_normalized.size()); if (!p_write_to_file_arg) { throw std::runtime_error("cannot create arg"); } PyTuple_SetItem(p_write_to_file_args, 0, p_timeline); PyTuple_SetItem(p_write_to_file_args, 1, p_write_to_file_arg); PyObjectRef(PyObject_CallObject(p_write_to_file, p_write_to_file_args)); r = true; } catch (const std::exception& e) { error_status->outcome = otio::ErrorStatus::Outcome::FILE_WRITE_FAILED; error_status->details = e.what(); } if (PyErr_Occurred()) { PyErr_Print(); } return r; } int main(int argc, char** argv) { if (argc != 3) { std::cout << "Usage: python_adapters_embed (inputpath) (outputpath)" << std::endl; return 1; } PythonAdapters adapters; otio::ErrorStatus error_status; auto timeline = adapters.read_from_file(argv[1], &error_status); if (!timeline) { examples::print_error(error_status); return 1; } std::cout << "Video tracks: " << timeline.value->video_tracks().size() << std::endl; std::cout << "Audio tracks: " << timeline.value->audio_tracks().size() << std::endl; if (!adapters.write_to_file(timeline, argv[2], &error_status)) { examples::print_error(error_status); return 1; } return 0; } opentimelineio-0.18.1/examples/python_adapters_child_process.cpp0000664000175000017500000001351215110656141023040 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project // Example OTIO C++ code for reading and writing files supported by the OTIO // Python adapters. // // This example uses the "otioconvert" utility in a child process to convert // between input/output files and JSON that can be used from C++ code. // // To run this example make sure that the "otioconvert" utility is in your // search path and the environment variable PYTHONPATH is set correctly. #include "util.h" #include #include #include #if defined(_WINDOWS) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN #include #include #include #include #include #else // _WINDOWS #include #endif // _WINDOWS namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; class PythonAdapters { public: static otio::SerializableObject::Retainer read_from_file( std::string const&, otio::ErrorStatus*); static bool write_to_file( otio::SerializableObject::Retainer const&, std::string const&, otio::ErrorStatus*); private: static bool _run_process(std::string const& cmd_line, otio::ErrorStatus*); }; otio::SerializableObject::Retainer PythonAdapters::read_from_file( std::string const& file_name, otio::ErrorStatus* error_status) { // Convert the input file to a temporary JSON file. const std::string temp_file_name = examples::create_temp_dir() + "/temp.otio"; std::stringstream ss; ss << "otioconvert" << " -i " << examples::normalize_path(file_name) << " -o " << temp_file_name; _run_process(ss.str(), error_status); // Read the temporary JSON file. return dynamic_cast(otio::Timeline::from_json_file(temp_file_name, error_status)); } bool PythonAdapters::write_to_file( otio::SerializableObject::Retainer const& timeline, std::string const& file_name, otio::ErrorStatus* error_status) { // Write the temporary JSON file. const std::string temp_file_name = examples::create_temp_dir() + "/temp.otio"; if (!timeline.value->to_json_file(temp_file_name, error_status)) { return false; } // Convert the temporary JSON file to the output file. std::stringstream ss; ss << "otioconvert" << " -i " << temp_file_name << " -o " << examples::normalize_path(file_name); _run_process(ss.str(), error_status); return true; } #if defined(_WINDOWS) class WCharBuffer { public: WCharBuffer(const WCHAR* data, size_t size) { p = new WCHAR[(size + 1) * sizeof(WCHAR)]; memcpy(p, data, size * sizeof(WCHAR)); p[size] = 0; } ~WCharBuffer() { delete[] p; } WCHAR* p = nullptr; }; bool PythonAdapters::_run_process(const std::string& cmd_line, otio::ErrorStatus* error_status) { // Convert the command-line to UTF16. std::wstring_convert, wchar_t> utf16; std::wstring w_cmd_line = utf16.from_bytes("/c " + cmd_line); WCharBuffer w_cmd_line_buf(w_cmd_line.c_str(), w_cmd_line.size()); // Create the process and wait for it to complete. STARTUPINFOW si; ZeroMemory(&si, sizeof(si)); PROCESS_INFORMATION pi; si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); if (0 == CreateProcessW( // TODO: MSDN documentation says to use "cmd.exe" for the "lpApplicationName" // argument, but that gives the error: "The system cannot find the file specified." // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw //L"cmd.exe", L"C:\\windows\\system32\\cmd.exe", w_cmd_line_buf.p, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { const DWORD error = GetLastError(); TCHAR error_buf[4096]; FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), error_buf, 4096, NULL); error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED; error_status->details = "cannot create process: " + std::string(error_buf, lstrlen(error_buf)); return false; } WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return true; } #else // _WINDOWS bool PythonAdapters::_run_process(const std::string& cmd_line, otio::ErrorStatus* error_status) { FILE* f = popen(cmd_line.c_str(), "r"); if (!f) { error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED; error_status->details = "cannot create process"; return false; } if (-1 == pclose(f)) { error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED; error_status->details = "cannot execute process"; return false; } return true; } #endif // _WINDOWS int main(int argc, char** argv) { if (argc != 3) { std::cout << "Usage: python_adapters_child_process (inputpath) (outputpath)" << std::endl; return 1; } otio::ErrorStatus error_status; auto timeline = PythonAdapters::read_from_file(argv[1], &error_status); if (!timeline) { examples::print_error(error_status); return 1; } std::cout << "Video tracks: " << timeline.value->video_tracks().size() << std::endl; std::cout << "Audio tracks: " << timeline.value->audio_tracks().size() << std::endl; if (!PythonAdapters::write_to_file(timeline, argv[2], &error_status)) { examples::print_error(error_status); return 1; } return 0; } opentimelineio-0.18.1/examples/flatten_video_tracks.cpp0000664000175000017500000000545415110656141021133 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "util.h" #include #include #include #include namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; int main(int argc, char** argv) { if (argc != 3) { std::cout << "Usage: flatten_video_tracks (inputpath) (outputpath)" << std::endl; return 1; } // Read the file otio::ErrorStatus error_status; otio::SerializableObject::Retainer timeline(dynamic_cast(otio::Timeline::from_json_file(argv[1], &error_status))); if (!timeline) { examples::print_error(error_status); return 1; } auto video_tracks = timeline.value->video_tracks(); auto audio_tracks = timeline.value->audio_tracks(); std::cout << "Read " << video_tracks.size() << " video tracks and " << audio_tracks.size() << " audio tracks." << std::endl; // Take just the video tracks - and flatten them into one. // This will trim away any overlapping segments, collapsing everything // into a single track. std::cout << "Flattening " << video_tracks.size() << " video tracks into one..." << std::endl; auto onetrack = otio::flatten_stack(video_tracks, &error_status); if (!onetrack || is_error(error_status)) { examples::print_error(error_status); return 1; } // Now make a new empty Timeline and put that one Track into it std::string name; std::stringstream ss(name); ss << timeline.value->name() << " Flattened"; auto newtimeline = otio::SerializableObject::Retainer(new otio::Timeline(ss.str())); auto stack = otio::SerializableObject::Retainer(new otio::Stack()); newtimeline.value->set_tracks(stack); if (!stack.value->append_child(onetrack, &error_status)) { examples::print_error(error_status); return 1; } // keep the audio track(s) as-is for (const auto& audio_track : audio_tracks) { auto clone = dynamic_cast(audio_track->clone(&error_status)); if (!clone) { examples::print_error(error_status); return 1; } if (!stack.value->append_child(clone, &error_status)) { examples::print_error(error_status); return 1; } } // ...and save it to disk. std::cout << "Saving " << newtimeline.value->video_tracks().size() << " video tracks and " << newtimeline.value->audio_tracks().size() << " audio tracks." << std::endl; if (!newtimeline.value->to_json_file(argv[2], &error_status)) { examples::print_error(error_status); return 1; } return 0; } opentimelineio-0.18.1/examples/flatten_video_tracks.py0000775000175000017500000000227015110656141020775 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import opentimelineio as otio import sys import copy inputpath, outputpath = sys.argv[1:] # Read the file timeline = otio.adapters.read_from_file(inputpath) video_tracks = timeline.video_tracks() audio_tracks = timeline.audio_tracks() print("Read {} video tracks and {} audio tracks.".format( len(video_tracks), len(audio_tracks) )) # Take just the video tracks - and flatten them into one. # This will trim away any overlapping segments, collapsing everything # into a single track. print(f"Flattening {len(video_tracks)} video tracks into one...") onetrack = otio.algorithms.flatten_stack(video_tracks) # Now make a new empty Timeline and put that one Track into it newtimeline = otio.schema.Timeline(name=f"{timeline.name} Flattened") newtimeline.tracks[:] = [onetrack] # keep the audio track(s) as-is newtimeline.tracks.extend(copy.deepcopy(audio_tracks)) # ...and save it to disk. print("Saving {} video tracks and {} audio tracks.".format( len(newtimeline.video_tracks()), len(newtimeline.audio_tracks()) )) otio.adapters.write_to_file(newtimeline, outputpath) opentimelineio-0.18.1/examples/util.h0000664000175000017500000000123515110656141015354 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include #include namespace examples { // Normalize path (change '\' path delimiters to '/'). std::string normalize_path(std::string const&); // Get the absolute path. std::string absolute(std::string const&); // Create a temporary directory. std::string create_temp_dir(); // Get a list of files from a directory. std::vector glob(std::string const& path, std::string const& pattern); // Print an error to std::cerr. void print_error(opentimelineio::OPENTIMELINEIO_VERSION::ErrorStatus const&); } opentimelineio-0.18.1/examples/shot_detect.py0000775000175000017500000001705215110656141017114 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Example OTIO script that generates an OTIO from a single quicktime by using ffprobe to detect shot breaks. """ import subprocess import re import os import argparse import opentimelineio as otio class ShotDetectError(Exception): pass class FFProbeFailedError(ShotDetectError): pass """ @NOTE EXAMPLE LINE from ffprobe: media_type=video|stream_index=0|key_frame=1|pkt_pts=385875|pkt_pts_time=128.753754|pkt_dts=385875|pkt_dts_time=128.753754|best_effort_timestamp=385875|best_effort_timestamp_time=128.753754|pkt_duration=125|pkt_duration_time=0.041708|pkt_pos=160853826|pkt_size=4608000|width=1920|height=800|pix_fmt=rgb24|sample_aspect_ratio=1:1|pict_type=I|coded_picture_number=0|display_picture_number=0|interlaced_frame=0|top_field_first=0|repeat_pict=0|tag:lavfi.scene_score=0.421988 """ def parse_args(): """ parse arguments out of sys.argv """ parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( 'filepath', type=str, nargs='+', help='Files to look for splits in.' ) parser.add_argument( '-r', '--reference-clip', action='store_true', default=False, help="instead of detecting shots, write a timeline with a single clip " "referencing the file." ) parser.add_argument( '-d', '--dryrun', action="store_true", default=False, help="Print ffprobe command instead of running it." ) return parser.parse_args() _MEDIA_RANGE_MAP = {} def _media_start_end_of(media_path, fps): if media_path in _MEDIA_RANGE_MAP: return _MEDIA_RANGE_MAP[media_path] cmd = [ "ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", media_path ] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() if proc.returncode != 0: raise FFProbeFailedError( "FFProbe Failed with error: {}, output: {}".format( err, out ) ) available_range = otio.opentime.TimeRange( otio.opentime.RationalTime(0, 1).rescaled_to(fps), otio.opentime.RationalTime(float(out), 1).rescaled_to(fps) ) _MEDIA_RANGE_MAP[media_path] = available_range return available_range def _timeline_with_single_clip(name, full_path, dryrun=False): timeline = otio.schema.Timeline() timeline.name = name track = otio.schema.Track() track.name = name timeline.tracks.append(track) fps = _ffprobe_fps(name, full_path, dryrun) available_range = _media_start_end_of(full_path, fps) media_reference = otio.schema.ExternalReference( target_url="file://" + full_path, available_range=available_range ) if dryrun: return clip = otio.schema.Clip(name=name) clip.media_reference = media_reference track.append(clip) return timeline def _timeline_with_breaks(name, full_path, dryrun=False): timeline = otio.schema.Timeline() timeline.name = name track = otio.schema.Track() track.name = name timeline.tracks.append(track) fps = _ffprobe_fps(name, full_path, dryrun) out, _ = _ffprobe_output(name, full_path, dryrun) if dryrun: return ("", "") # saving time in base 1.0 to demonstrate that you can playhead = otio.opentime.RationalTime(0, 1.0) shot_index = 1 for line in out.split("\n"): info = dict(re.findall(r'(\w+)=(\d+\.\d*)', line)) end_time_in_seconds = info.get('pkt_pts_time') if end_time_in_seconds is None: continue start_time = playhead.rescaled_to(fps) # Note: if you wanted to snap to a particular frame rate, you would do # it here. end_time_exclusive = otio.opentime.RationalTime( float(end_time_in_seconds), 1.0 ).rescaled_to(fps) clip = otio.schema.Clip() clip.name = f"shot {shot_index}" clip.source_range = otio.opentime.range_from_start_end_time( start_time, end_time_exclusive ) available_range = _media_start_end_of(full_path, fps) clip.media_reference = otio.schema.ExternalReference( target_url="file://" + full_path, available_range=available_range ) track.append(clip) playhead = end_time_exclusive shot_index += 1 return timeline def _verify_ffprobe(): cmd = [ "ffprobe", "-h" ] try: proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError: raise FFProbeFailedError( "Unable to run ffprobe command line tool. It might not be in your " "path, or you might need to get it from https://www.ffmpeg.org" ) out, err = proc.communicate() if proc.returncode != 0: raise FFProbeFailedError( "FFProbe Failed with error: {}, output: {}".format( err, out ) ) def _ffprobe_fps(name, full_path, dryrun=False): """Parse the framerate from the file using ffprobe.""" _, err = _ffprobe_output( name, full_path, dryrun, arguments=[f"{full_path}"], message="framerate" ) if dryrun: return 1.0 err_str = err.decode("utf-8") for line in err_str.split('\n'): if not ("Stream" in line and "Video" in line): continue bits = line.split(",") for bit in bits: if "fps" not in bit: continue return float([b for b in bit.split(" ") if b][0]) # fallback FPS is just 1.0 -- seconds return 1.0 def _ffprobe_output( name, full_path, dryrun=False, arguments=None, message="shot breaks" ): """ Run ffprobe and return resulting output """ arguments = arguments or [ "-show_frames", "-of", "compact=p=0", "-f", "lavfi", f"movie={full_path},select=gt(scene\\,.1)" ] if message: print(f"Scanning {name} for {message}...") cmd = ["ffprobe"] + arguments if dryrun: print(" ".join(cmd)) return ("", "") proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() if proc.returncode != 0: raise FFProbeFailedError( "FFProbe Failed with error: {}, output: {}".format( err, out ) ) return out, err def main(): args = parse_args() # make sure that ffprobe exists before continuing _verify_ffprobe() for full_path in args.filepath: name = os.path.basename(full_path) if args.reference_clip: new_tl = _timeline_with_single_clip(name, full_path, args.dryrun) else: new_tl = _timeline_with_breaks(name, full_path, args.dryrun) if args.dryrun: continue # @TODO: this should be an argument otio_filename = os.path.splitext(name)[0] + ".otio" otio.adapters.write_to_file(new_tl, otio_filename) print( "SAVED: {} with {} clips.".format( otio_filename, len(new_tl.tracks[0]) ) ) if __name__ == '__main__': main() opentimelineio-0.18.1/examples/util.cpp0000664000175000017500000001105615110656141015711 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "util.h" #include #include #include #include #include #if defined(_WINDOWS) #if !defined(WIN32_LEAN_AND_MEAN) #define WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN #include #include #if defined(min) #undef min #endif // min #else // _WINDOWS #include #include #include #include #include #include #endif // _WINDOWS namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; namespace examples { #if defined(_WINDOWS) std::string normalize_path(std::string const& in) { std::string out; for (auto i : in) { out.push_back('\\' == i ? '/' : i); } return out; } std::string absolute(std::string const& in) { wchar_t buf[MAX_PATH]; std::wstring_convert, wchar_t> utf16; if (!::_wfullpath(buf, utf16.from_bytes(in).c_str(), MAX_PATH)) { buf[0] = 0; } return normalize_path(utf16.to_bytes(buf)); } std::string create_temp_dir() { std::string out; // Get the temporary directory. char path[MAX_PATH]; DWORD r = GetTempPath(MAX_PATH, path); if (r) { out = std::string(path); // Replace path separators. for (size_t i = 0; i < out.size(); ++i) { if ('\\' == out[i]) { out[i] = '/'; } } // Create a unique name from a GUID. GUID guid; CoCreateGuid(&guid); const uint8_t* guidP = reinterpret_cast(&guid); for (int i = 0; i < 16; ++i) { char buf[3] = ""; sprintf_s(buf, 3, "%02x", guidP[i]); out += buf; } // Create a unique directory. CreateDirectory(out.c_str(), NULL); } return out; } std::vector glob(std::string const& path, std::string const& pattern) { std::vector out; // Prepare the path. std::wstring_convert, wchar_t> utf16; std::wstring wpath = utf16.from_bytes(path + '/' + pattern); WCHAR wbuf[MAX_PATH]; size_t size = std::min(wpath.size(), static_cast(MAX_PATH - 1)); memcpy(wbuf, wpath.c_str(), size * sizeof(WCHAR)); wbuf[size++] = 0; // List the directory contents. std::string const absolutePath = absolute(path); WIN32_FIND_DATAW ffd; HANDLE hFind = FindFirstFileW(wbuf, &ffd); if (hFind != INVALID_HANDLE_VALUE) { do { out.push_back(absolutePath + '/' + utf16.to_bytes(ffd.cFileName)); } while (FindNextFileW(hFind, &ffd) != 0); FindClose(hFind); } return out; } #else // _WINDOWS std::string normalize_path(std::string const& in) { return in; } std::string absolute(std::string const& in) { char buf[PATH_MAX]; if (NULL == realpath(in.c_str(), buf)) { buf[0] = 0; } return buf; } std::string create_temp_dir() { // Find the temporary directory. std::string path; char* env = nullptr; if ((env = getenv("TEMP"))) path = env; else if ((env = getenv("TMP"))) path = env; else if ((env = getenv("TMPDIR"))) path = env; else { for (const auto& i : { "/tmp", "/var/tmp", "/usr/tmp" }) { struct stat buffer; if (0 == stat(i, &buffer)) { path = i; break; } } } // Create a unique directory. path = path + "/XXXXXX"; const size_t size = path.size(); std::vector buf(size + 1); memcpy(buf.data(), path.c_str(), size); buf[size] = 0; return mkdtemp(buf.data()); } std::vector glob(std::string const& path, std::string const& pattern) { std::vector out; std::string const absolutePath = absolute(path); DIR* dir = opendir(path.c_str()); if (dir) { struct dirent* e = nullptr; while ((e = readdir(dir)) != nullptr) { if (0 == fnmatch(pattern.c_str(), e->d_name, 0)) { out.push_back(absolutePath + '/' + e->d_name); } } closedir(dir); } return out; } #endif // _WINDOWS void print_error(otio::ErrorStatus const& error_status) { std::cout << "ERROR: " << otio::ErrorStatus::outcome_to_string(error_status.outcome) << ": " << error_status.details << std::endl; } } opentimelineio-0.18.1/examples/sample_plugin/0000775000175000017500000000000015110656141017064 5ustar memeopentimelineio-0.18.1/examples/sample_plugin/setup.py0000664000175000017500000000277015110656141020604 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project from setuptools import setup """ This is an example of how to an OpenTimelineIO plugin installable as a separate python package. For the most part, the module can be built like any other python module, the one exception is that it must expose an ``opentimelineio.plugins`` entry point to allow OTIO to discover it. The entry point defined below can be read as: 1. Declaring this module provides for the entry point group ``opentimelineio.plugins``. 2. Declaring that for the ``opentimelineio.plugins`` group, this module provides a plugin named ``track_counter`` and the module that should be loaded for that plugin is the ``otio_counter`` module. For more information about python plugins, see the python packaging guide: https://packaging.python.org/guides/creating-and-discovering-plugins/#using-package-metadata """ setup( name='otio_counter', entry_points={ 'opentimelineio.plugins': 'track_counter = otio_counter' }, packages=['otio_counter'], package_data={ 'otio_counter': [ 'plugin_manifest.json', ], }, keywords='plugin OpenTimelineIO sample', platforms='any', version='1.0.0', description='Adapter writes number of tracks to file.', license='Modified Apache 2.0 License', author='Contributors to the OpenTimelineIO project', author_email='otio-discussion@lists.aswf.io', url='http://opentimeline.io', ) opentimelineio-0.18.1/examples/sample_plugin/otio_counter/0000775000175000017500000000000015110656141021575 5ustar memeopentimelineio-0.18.1/examples/sample_plugin/otio_counter/__init__.py0000664000175000017500000000367615110656141023722 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import importlib.resources from opentimelineio.plugins import manifest """ In the ``setup.py`` the top-level module was provided as the entry point. When OTIO loads a plugin, it will look for ``plugin_manifest.json`` in the package and register the plugins declared. If you wish to programmatically generate the manifest, you may return a :class:`Manifest` instance from your entry point's ``plugin_manifest`` function. For example purposes, this simply deserializes it from the json file included in the package. Below is an example of what the manifest json might look like (also included as a file in this package). .. codeblock:: JSON { "OTIO_SCHEMA" : "PluginManifest.1", "adapters": [ { "OTIO_SCHEMA": "Adapter.1", "name": "track_counter", "filepath": "adapter.py", "suffixes": ["count"] } ] } While this example shows an adapter, you may also define Media Linker plugins as below. .. codeblock:: JSON { "OTIO_SCHEMA" : "MediaLinker.1", "name" : "linker_example", "filepath" : "linker.py" } """ def plugin_manifest(): """ If, for some reason, the Manifest needs to be generated at runtime, it can be done here. This function takes precedence over ``plugin_manifest.json`` automatic discovery. Note the example implemenation's behavior is identical to the default when this function isn't defined. In most cases ``plugin_manifest.json`` should be sufficient and the ``__init__.py`` file can be left empty. """ # XXX: note, this doesn't get called. For an example of this working, # see the mockplugin unit test. filepath = importlib.resources.files(__package__) / "plugin_manifest.json" return manifest.manifest_from_string( filepath.read_text() ) opentimelineio-0.18.1/examples/sample_plugin/otio_counter/plugin_manifest.json0000664000175000017500000000035515110656141025657 0ustar meme{ "OTIO_SCHEMA" : "PluginManifest.1", "adapters": [ { "OTIO_SCHEMA": "Adapter.1", "name": "track_counter", "filepath": "adapter.py", "suffixes": ["count"] } ] } opentimelineio-0.18.1/examples/sample_plugin/otio_counter/adapter.py0000664000175000017500000000117715110656141023575 0ustar meme# SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project import opentimelineio as otio """ This is the implementation of the contrived example adapter that simply writes the number of tracks in the timeline to a file, or will read an integer from a file and create a timeline with that number of tracks. This would be where your plugin implementation would be. """ def write_to_string(input_otio): return f'{len(input_otio.tracks)}' def read_from_string(input_str): t = otio.schema.Timeline() for i in range(int(input_str)): t.tracks.append(otio.schema.Track()) return t opentimelineio-0.18.1/examples/build_simple_timeline.py0000775000175000017500000000562715110656141021152 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project __doc__ = """ Generate a simple timeline from scratch and write it to the specified path. """ import argparse import opentimelineio as otio FILE_LIST = [ # first file starts at 0 and goes 100 frames ( "first.mov", otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(100, 24) ) ), # second file starts 1 hour in and goes 300 frames (at 24) ( "second.mov", otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(86400, 24), duration=otio.opentime.RationalTime(300, 24) ) ), # third file starts at 0 and goes 400 frames @ 24) ( "thrd.mov", otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(0, 24), duration=otio.opentime.RationalTime(400, 24) ) ) ] def parse_args(): """ parse arguments out of sys.argv """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser.add_argument( 'filepath', type=str, help=( 'Path to write example file to. Example: /var/tmp/example.otio or ' 'c:\\example.xml' ) ) return parser.parse_args() def main(): args = parse_args() # build the structure tl = otio.schema.Timeline(name="Example timeline") tr = otio.schema.Track(name="example track") tl.tracks.append(tr) # build the clips for i, (fname, available_range_from_list) in enumerate(FILE_LIST): ref = otio.schema.ExternalReference( target_url=fname, # available range is the content available for editing available_range=available_range_from_list ) # attach the reference to the clip cl = otio.schema.Clip( name=f"Clip{i + 1}", media_reference=ref, # the source range represents the range of the media that is being # 'cut into' the clip. This is an artificial example, so its just # a bit shorter than the available range. source_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( available_range_from_list.start_time.value + 10, available_range_from_list.start_time.rate ), duration=otio.opentime.RationalTime( available_range_from_list.duration.value - 20, available_range_from_list.duration.rate ), ) ) # put the clip into the track tr.append(cl) # write the file to disk otio.adapters.write_to_file(tl, args.filepath) if __name__ == '__main__': main() opentimelineio-0.18.1/examples/CMakeLists.txt0000664000175000017500000000133015110656141016762 0ustar memefind_package(PythonLibs REQUIRED) include_directories(${PROJECT_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR}/../src ${PYTHON_INCLUDE_DIRS}) list(APPEND examples conform) list(APPEND examples flatten_video_tracks) list(APPEND examples summarize_timing) list(APPEND examples io_perf_test) list(APPEND examples upgrade_downgrade_example) if(OTIO_PYTHON_INSTALL) list(APPEND examples python_adapters_child_process) list(APPEND examples python_adapters_embed) endif() foreach(example ${examples}) add_executable(${example} ${example}.cpp util.h util.cpp) target_link_libraries(${example} OTIO::opentimelineio ${PYTHON_LIBRARIES}) set_target_properties(${example} PROPERTIES FOLDER examples) endforeach() opentimelineio-0.18.1/examples/conform.cpp0000664000175000017500000001071315110656141016376 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project // Example OTIO script that reads a timeline and then relinks clips // to movie files found in a given folder, based on matching clip names to filenames. // // Demo: // // % ls -1R // editorial_cut.otio // media/ // shot1.mov // shot17.mov // shot99.mov // // % conform editorial_cut.otio media conformed.otio // Relinked 3 clips to new media. // Saved conformed.otio with 100 clips. // // % diff editorial_cut.otio conformed.otio // ... #include "util.h" #include #include #include #include namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; // Look for media with this name in this folder. std::string find_matching_media(std::string const& name, std::string const& folder) { // This function is an example which searches the file system for matching media. // A real world studio implementation would likely look in an asset management system // and use studio-specific metadata in the clip's metadata dictionary instead // of matching the clip name. // For example: // shot = asset_database->find_shot( // otio::any_cast >(clip->metadata()["mystudio"])["shotID"]); // new_media = shot->latest_render("mov"); const auto matches = examples::glob(folder, name + ".*"); if (matches.size() == 0) { //std::cout << "DEBUG: No match for clip '" << name << "'" << std::endl; return std::string(); } if (matches.size() == 1) { return matches[0]; } else { std::cout << "WARNING: " << matches.size() << " matches found for clip '" << name << "', using '" << matches[0] << "'"; return matches[0]; } } // Look for replacement media for each clip in the given timeline. // // The clips are relinked in place if media with a matching name is found. // // Note the use of otio::SerializableObject::Retainer to wrap the timeline, // it provides a safe way to manage the memory of otio objects by keeping an // internal reference count. For more details on the usage of Retainers see // the C++ documentation: // https://opentimelineio.readthedocs.io/en/latest/cxx/cxx.html int conform_timeline( otio::SerializableObject::Retainer const& timeline, std::string const& folder) { int count = 0; otio::ErrorStatus error_status; const auto clips = timeline->find_clips(&error_status); if (otio::is_error(error_status)) { examples::print_error(error_status); exit(1); } for (const otio::SerializableObject::Retainer& clip : clips) { // look for a media file that matches the clip's name const std::string new_path = find_matching_media(clip->name(), folder); // if no media is found, keep going if (new_path.empty()) continue; // relink to the found path clip->set_media_reference(new otio::ExternalReference( "file://" + new_path, std::nullopt // the available range is unknown without opening the file )); count += 1; } return count; } int main(int argc, char** argv) { if (argc != 4) { std::cout << "Usage: conform (input) (folder) (output)" << std::endl; return 1; } const std::string input = examples::normalize_path(argv[1]); const std::string folder = examples::normalize_path(argv[2]); const std::string output = examples::normalize_path(argv[3]); otio::ErrorStatus error_status; otio::SerializableObject::Retainer timeline( dynamic_cast(otio::Timeline::from_json_file(input, &error_status))); if (!timeline || otio::is_error(error_status)) { examples::print_error(error_status); exit(1); } const int count = conform_timeline(timeline, folder); std::cout << "Relinked " << count << " clips to new media." << std::endl; if (!timeline.value->to_json_file(output, &error_status)) { examples::print_error(error_status); exit(1); } const auto clips = timeline->find_clips(&error_status); if (otio::is_error(error_status)) { examples::print_error(error_status); exit(1); } std::cout << "Saved " << output << " with " << clips.size() << " clips." << std::endl; return 0; } opentimelineio-0.18.1/examples/summarize_timing.cpp0000664000175000017500000001256115110656141020321 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project // Example OTIO C++ code that reads a timeline and then prints a summary // of the video clips found, including re-timing effects on each one. #include "util.h" #include #include #include #include #include #include #include #include #include namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; namespace otime = opentime::OPENTIME_VERSION; void summarize_effects(otio::SerializableObject::Retainer const& item) { for (auto effect : item.value->effects()) { if (auto freezeFrame = dynamic_cast(effect.value)) { std::cout << "Effect: Freeze Frame" << std::endl; } else if (auto linearTimeWarp = dynamic_cast(effect.value)) { std::cout.precision(2); std::cout << "Effect: Linear Time Warp (" << std::fixed << linearTimeWarp->time_scalar() * 100.0 << "%)" << std::endl; } else { std::cout << "Effect: " << effect.value->name() << "(" << typeid(effect.value).name() << ")" << std::endl; } } } void summarize_range(std::string const& label, otio::TimeRange const& time_range, otio::ErrorStatus const& errorStatus) { if (errorStatus.outcome != otio::ErrorStatus::Outcome::OK) { std::cerr << '\t' << label << ": None" << std::endl; } else { otime::ErrorStatus otimeErrorStatus; std::cout << '\t' << label << ": " << time_range.start_time().to_timecode(&otimeErrorStatus) << " - " << time_range.end_time_exclusive().to_timecode(&otimeErrorStatus) << " (Duration: " << time_range.duration().to_timecode(&otimeErrorStatus) << ")" << std::endl; } } void summarize_timeline(otio::SerializableObject::Retainer const& timeline) { // Here we iterate over each video track, and then just the top-level // items in each track. // See also: https://opentimelineio.readthedocs.io/en/latest/tutorials/otio-timeline-structure.html # noqa for (const auto i : timeline.value->tracks()->children()) { if (auto track = dynamic_cast(i.value)) { otio::ErrorStatus errorStatus; std::cout << "Track: " << track->name() << '\n' << "\tKind: " << track->kind() << '\n' << "\tDuration: " << track->duration(&errorStatus).to_time_string() << std::endl; summarize_effects(track); for (auto child : track->children()) { if (auto item = dynamic_cast(child.value)) { if (auto clip = dynamic_cast(item)) { std::cout << "Clip: " << clip->name() << std::endl; // See the documentation to understand the difference // between each of these ranges: // https://opentimelineio.readthedocs.io/en/latest/tutorials/time-ranges.html summarize_range(" Trimmed Range", clip->trimmed_range(&errorStatus), errorStatus); summarize_range(" Visible Range", clip->visible_range(&errorStatus), errorStatus); summarize_range("Available Range", clip->available_range(&errorStatus), errorStatus); } else if (auto gap = dynamic_cast(item)) { continue; } else if (auto transition = dynamic_cast(item)) { otime::ErrorStatus otimeErrorStatus; std::cout << "Transition: " << transition->transition_type() << '\n' << "\tDuration: " << transition->duration(&errorStatus).to_timecode(&otimeErrorStatus) << std::endl; } else if (auto composition = dynamic_cast(item)) { std::cout << "Nested Composition: " << composition->name() << '\n' << "\tDuration: " << composition->duration(&errorStatus).to_time_string() << std::endl; } else { std::cout << "Other: " << item->name() << "(" << typeid(item).name() << ")" << '\n' << "\tDuration: " << item->duration(&errorStatus).to_time_string() << std::endl; } summarize_effects(item); } } } } } int main(int argc, char** argv) { for (int i = 1; i < argc; ++i) { otio::ErrorStatus error_status; otio::SerializableObject::Retainer timeline(dynamic_cast(otio::Timeline::from_json_file(argv[i], &error_status))); if (!timeline) { examples::print_error(error_status); return 1; } summarize_timeline(timeline); } return 0; } opentimelineio-0.18.1/examples/conform.py0000775000175000017500000000710315110656141016246 0ustar meme#!/usr/bin/env python # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project """Example OTIO script that reads a timeline and then relinks clips to movie files found in a given folder, based on matching clip names to filenames. Demo: % ls -1R editorial_cut.otio media/ shot1.mov shot17.mov shot99.mov % conform.py -i editorial_cut.otio -f media -o conformed.otio Relinked 3 clips to new media. Saved conformed.otio with 100 clips. % diff editorial_cut.otio conformed.otio ... """ import argparse from argparse import RawTextHelpFormatter import glob import os import opentimelineio as otio def parse_args(): """ parse arguments out of sys.argv """ parser = argparse.ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) parser.add_argument( '-i', '--input', type=str, required=True, help='Timeline file to read. Supported formats: {adapters}' ''.format(adapters=otio.adapters.available_adapter_names()) ) parser.add_argument( '-f', '--folder', type=str, required=True, help='Folder to look for media in.' ) parser.add_argument( '-o', '--output', type=str, required=True, help="Timeline file to write out." ) return parser.parse_args() def _find_matching_media(name, folder): """Look for media with this name in this folder.""" # This function is an example which searches the file system for matching media. # A real world studio implementation would likely look in an asset management system # and use studio-specific metadata in the clip's metadata dictionary instead # of matching the clip name. # For example: # shot = asset_database.find_shot(clip.metadata['mystudio']['shotID']) # new_media = shot.latest_render(format='mov') matches = glob.glob(f"{folder}/{name}.*") matches = list(map(os.path.abspath, matches)) if not matches: # print "DEBUG: No match for clip '{0}'".format(name) return None if len(matches) == 1: return matches[0] else: print( "WARNING: {} matches found for clip '{}', using '{}'".format( len(matches), name, matches[0] ) ) return matches[0] def _conform_timeline(timeline, folder): """ Look for replacement media for each clip in the given timeline. The clips are relinked in place if media with a matching name is found. """ count = 0 for clip in timeline.find_clips(): # look for a media file that matches the clip's name new_path = _find_matching_media(clip.name, folder) # if no media is found, keep going if not new_path: continue # relink to the found path clip.media_reference = otio.schema.ExternalReference( target_url="file://" + new_path, available_range=None # the available range is unknown without # opening the file ) count += 1 return count def main(): args = parse_args() timeline = otio.adapters.read_from_file(args.input) count = _conform_timeline(timeline, args.folder) print(f"Relinked {count} clips to new media.") otio.adapters.write_to_file(timeline, args.output) print( "Saved {} with {} clips.".format( args.output, len(list(timeline.find_clips())) ) ) if __name__ == '__main__': main() opentimelineio-0.18.1/.readthedocs.yml0000664000175000017500000000045715110656141015503 0ustar meme# required by RTD version: 2 sphinx: # Path to your Sphinx configuration file. configuration: docs/conf.py build: os: "ubuntu-20.04" tools: python: "3.10" python: install: - method: pip path: . - requirements: docs/requirements.txt submodules: include: all recursive: true opentimelineio-0.18.1/OTIO_CLA_Corporate.pdf0000664000175000017500000017542015110656141016362 0ustar meme%PDF-1.5 % 1 0 obj <> endobj 7 0 obj <> stream x0 E| $cԪЇ:tȀhc A0v웜{c%syiBMhDNhb.óqOalsΝCc nϋwy!3% ^ϗ^/ާwynڱa(P̱goۅ*<O@p Y 8O]QȦO{PŮ> endobj 10 0 obj <> stream xXˎ6Wp3 T4l8@,Xi= 9TRY^L6)y{~yKb?, S?KxsC;S%ˁ&#$w;~e ܳ՝YV~<84rẇ~h5\&~s`^Iy'ឹ#=<")&p꩕뽍s?Jh؎iVl~dhœ'w$9/'vVab/kSte?y4,mGB#?pdE;0w z"y3AՒK~[8H#}^򎽄>߬a(ǣh٥w=S {`Q+< I^#GYBQ;g[QJ{X)EBwl3&U8q=$Dtpa+٨,c\bյA. /뮵 GX(4AEyg ;S͈Zŋ P3~?{b詹&R9e޵_?jPs4a8 ODIQm"6]68DՊ4YJb6O b:I%)Ec'bgiR=h逺*c9~30zy;hJr9e3-4a450dUk`͓tT37Pd ,z=L 6JG(" 3B/:Li_" D.<쵈9@:"Ajz7j L~y+^pzImRͱڙyG=DA(S]6N|0fxϑ05p蠫H$*ΡΌL 3R] )QRT`Lw;v┚QvHNb^P1RPk) 1IIK;kUi Ne+0@vQL`Ȋ(l@RNd+ 0*0`H|i/uLДw0ތ5x7Rem SN/fL^fmfQ(`ՕX?u}AF(Cm|4YY{Ybb-Ǧ|?na)(Ky5Ř/ RqĈ6=[vP2sBMD ZODRKgm#!k-|`y4nfLf(;(6z 룘fA4H]auq,bJ,'E/4e?{PäQdiB\,Pr }a# 4Cm vG*Ў-+fBADL0O;EKo 򃫝a!DMV uFz݌&=8~\W>S ےN|pƒlۇwcGbӵ!Vܠu eg.$<Ӳ~S:hދz",Hq﹧Mia;5.7rKO/K/Ro[t4 endstream endobj 11 0 obj 2469 endobj 3 0 obj <> /ProcSet [/PDF /Text]>>>> endobj 14 0 obj <> stream xXrH+GPhRt8%HVhZ@1-C^z=#~˫Փך-Vk޴N.D B/EڊZQo }rUʆk7au-V'IĜ> p4˙glZ^9՞mD-^lMb߯nW{xJGvS-r'q2r?jx-R6m-_X2Fc;QDH>tbyK1beU^kL͍(_/6VeF ƚ:=^u->W RF?N,:e]]- aC)r[3oa A˳ЍjvP)hy^[z;bB)r7N@#f Q Q^E{bf(I#P ;*зp {x-tF\ž^:!sῡ6N:<ӱb+ִ< J sTFh%#:zQKLM<3r%f/[Xwűe~Ftl$X&xGY:R-$FeO rD ~KYSTNU}zyT!?[b8 L{mq2ۗKUDdӏO׍J!tWI[tP.4Jx6KBbD^qf7D!"(=, Ez=k) y'%i$rX9D0$U+.~%ӒR hQ{?4iٔii5 ]QjnSgZ\" pR"8ݘ%_ÂQTz :-Sjq:t0`}٩8jֈ?衦Vm+ + LNQ4Tu|7_{UԸt6=]-TBCe˵v๭V =;>.joTF:E$ 4yȣÅ*q/$\YqGHg'0+KBtwȤRW.1gANű6 Ysҋj_/j+EH@4t2g:$p9TBinv%#%*i b QmD2ږI}Q87rl:e$bx n5Xj݆&߿t{ˏbMK}fH*H"MY.{W{X4sR|Jaa06[fAy(CQ%5?HH!h`KHH+Mfl2379~2|ȅIl)u_9~|Od7_&{c"@x{2`Z}!6L+ JE8Rڷ&X(KԮOjRtө*ZBFd~w;b5L~"B ¼̗/D"c);{@;رl2Zd%k¹+e? zI4h~U}i*Yߍ -l霄)~CFÿ'Pޜ3 HCbC%dߵp?v1o endstream endobj 15 0 obj 2435 endobj 4 0 obj <> /ProcSet [/PDF /Text]>>>> endobj 16 0 obj <> stream xQK0{lz6M${fʼn`ކHqDVڼv*lwܭ49C f+rqEf~&辶л]_. I0eBt?!& %Qѱao73{; E XSq޸zRr;Qg*/Azh|=!r>{m;]KeUug@!+_S1i (ſ+p8L6nw61;}lDrZ\HDL6g.7- N endstream endobj 17 0 obj 273 endobj 5 0 obj <> /ProcSet [/PDF /Text]>>>> endobj 12 0 obj <> endobj 18 0 obj <> endobj 19 0 obj <> stream x `E?^U}==3'$3̄L9r%ȭ$Ph kOwUP.o*d~O{z{TuFP/b|66_fO#$|k}'Roue϶ ߿?zzr3ܣf tqxի&$pWGj8~]Oz*K̿m\ "dcU arJ` Ї}D(JLfzRbNۃ>P8E1G %PyEe2UUdkFF׏ ?ab>M]4܅;пp2?p[Be.ȎP݃Aat WWQ4=ơVtF Z@, h7`?P 1GQ*5?c3ܧu!>m.4h:W( ɤ'";*GqM'߈~hu,4' 3@h+a ht7Ck:Zǽ>Vv|QS"Upf~tT2HBQ4]؂+/O#8h nG5C'WXa4lqؚѵh=@'^tW*b'v$hۆv@p3nG..o(O 6#e8/0Af5cWs7.Dcǟ_opl!?$ s XTȏjѥh.Z֠OW3D W;ƭNFx{ \= < K|^{ @x sfy#[q: wChZ!PNx5:('z5M&yƞn?SGH$õ)߱ PU?`ɳe13vf3s+iCn 7{Z&N Qa\1T2h"0.z FԏyDOs^GCB_¯_ Rw ^2~  '5L Md119x+L/l;C,bYU6=ɿ!ą՛gˆۇGyW{/?-.GP(S /`$ށC < Om:Ysa%mĽ&|3ބow+}l|~?Ÿ?1a@#$F$O:L"-2u8$9@ a*L7s?3][lggٛٷw3kp;Wx7ggYZ »BA%< /ɿWq%Z腃,Of2+;p)F~f)DaV%d\݆ ir|ZL?ϑ; lGkͅ:nAA{XG}^ҿd+jc3辇[ K62]v §=ol\Ari@aCCuốƒ̓xk2NLjĊ[)2y?d1 Z9ɣk@"1F@j@ޟο@{ r(S.C)A@uֆnEf" x!tO2Z@K;m#  ΃_׀o:,fEqmd BG;oQ #J DW}E0j P~2a c zN佧 p)بi`_GK 7yG hFa7~Tv2K`nOFE}`cQ?{Άm!+#ZV 69KȾB#tiɂkВ @.E>nVvIxK 'rf cԏՎfUdeEy4F¡`@J,fh4uZZ%CM@s&Oǡ1 .f@T..R+˕rJXQ}EޚK۠}P40+J[@ 5:Lp8дfIcD>fBhUrOZP>l7#HA BtLqK'<Ђ?`L( giVi_E3[Z8f~; S~w} ps󄶾 ϺFRI;/mlpio H\8\R̶P`j?ѳ_S('dg04t W?ittD) ҕ-TKjQp|1|k`!czBgX"CRW#=|D &ϵ2* (qr(_3HB]m%eA-KۊZޏd}t3Gϝ΢gzϝ9JaPEEqI_N_U<<#|6s3/:*=n'l(5%w6\)Ըs2q2qbV 3=h{^ XRӀ9Xk_,R6LuG_t|t s5ko IM B?̴1m]?X8=t[;<\M}!}2yҨw<{Kf^Ach860TREIVDaV ٚ3=?2=l[Xּu:4~'?CH|k'WmH5ՂKk/ޖ<բWUM-G#;k,׻f#JH "VnN| g^jpQrJ:%` bGx%Z=H浌j!;OQ?&[t{u$٪Rs2OQRWY9n+JYimj$(쟯qVZAljQ"G;;ǣlD JA!gvC#Láp0Lx ᅈu o#ڨiw> ?ZK вVAa Sy.S-(f*!(jͨP2s;Ϳ]ʍWژYLsu3dIrٸG^?e}wًs'\ 8q {8`Cv58 ^f !`7ȈB40 h$j*aO|l6)2n4n74F #MONPo7Q͡ Eǻ;,f[ٱ$K @_'6qEƳ|L_OD"[2V4</oڜqP8ialB0E#vզ懴75νnҷv{Y':mN!Z֛qNrno˩szI8k`a0 Z.5zI:smsG8~o.=cY.GVdyM>"'*w|/'1t9 pGw}p> mSo nOpL!8~pןRSv,^bN s3<߷'>w/^SbU%j<0ԃr.7Nݨi6ٷոTtfS# Z_TpH>gU @<.ArN 2 'ڰm?r+' p@ԗƟKP R]pv +vV ?8O>H>5r͐ c-/Yřs< ut zC= CC\A5I_Tcd` ""hTd+`Zml֨+{)ڈ#DٍQtu'sO6Ya)bJR Kmzl#zd2Gr\"<2Sܽe];]?p1ok;V4z|+6,p 0pWrܦFԨle:lp1xz,ӃVyd*a^B~=?V*WqN>"YV CAOف2NF t^P=(4?ܐK=Hqޔ3) # #mF@p[{5d Y 3~\{ͮ|#^t6<_ @qP [m7zoco Y bfIs=ku>mO]~{}3:2>}O'L+`vhˇ[8~  h8Uc㑬Nz )b XvXv1a9yR+Avj֩㎐!:Ċ̶;Y}|t#t OS  }\eϰUVM<;"`!{Ә 8!.|w~kwjfM~"SK@Aޜ(x|?][^z]".דg]EyM{M@K>bhƷ hTQ3< -Kؚؒm\Ыty[6}=L=iA3 >.Xjn \1V)D\,z85$)h"Y"g;;dzl6`@@4)lng`iTN.zT1@ȫI_dcUH ;.x 1ȂQwpZy͢E٪_$7gڎ/YԽuWފ -K[ӒYڴϙ[F/3 v4~٫_\ݹ :l*ݺJ/ o VD |AXe0 fVabk<; dxBJlZ\ Aӻ-VQ;q 2~xϨCW7~viȧ$f1=MCaeR<8Gu3C]#_]f\w=%h~I(@L'yq_c@=iu XDqgȶ>a}vl6+JJs$a[%*ۇuS?>=\V5~AuKv 2e8]& pxcS<3̤n5K2Sl1[[U5kSɝrn횝5NBITP٠sACW8[K/zTNN "~0$~1TH, ' =y.5ir ?Qu"H82q̔ըy/⓬*̵ qa#O"C \7a5Z;͋Eʷbm>u\}ʊL?`朲Yb-1i Y ϊffV%k%>G GnRCsiQq4;w{O]bY]77Ϳ~_:6n6|C~0] o~1޿*:O;\'e/xekZڞ/ ȃı(ʌ>"׏+(y+.a*Uc*͋]UFBmudʇ84/q7{LhԋF2n @-ԉJ_Z얢nmW[JWdF- y}>ڠqmۦ"[e5k}FQ}O$LNu벉J*nu*Xm6J dCvd Iha R6Hp+t:\ԙ;u(O{=\g(}JGbCX<>qëB=)NyM=MiQ89n3_G/ϚL s3?4//kB$bt~^~͗r[($~=;n>2/w/y_ʶmno\uxV&ìa<?wݢGpvάt?Ι,GcpF0&x/$$=f||Y!޺v;o{ǵyQKvKDS| $n|Hz{:hJ XdܦDOLRN^1>c~S\2^r` E<S" T~'ϏKɱ{#vQj G \/>yTdaUk}#_$>`~Ls=Qwq]Fq/onGe-xn{ SiO) @(Qרt,G@~{ EQ\8t$]KRJSriW)[nKkV ' ' AWW*oJwt^YVpBqy&󹩔nN`e0B4~z]:?8}$cH婪#Q^`Sr5^ E|ej$?{qivD"UzfEo^{k2Xdp>4Zhn13kf)7'Ut|#k5QlSHo^ % P"K9K5)!HH9 f(QXv[pP]Pї`᤬(LP0'B$酽$H!ԎApRԐT"˷ KIjGR 5icOb-vxVh8m =`  5vB`8*=& a1cZ x*%H (i+3x,x<$INbIxRc$mtfHfG?:=taݣDj$Jb\" Fw5TLT<~!5#WNU ??p4=޴ vq³w` .gi-}, (V8N@g5*:nnS d6PNBƩRdWAuWu\T8jUuyc*FT p$}d֛1p[Q_4S",>}ऺAݢ "Gƨ:)%iy~Co?c]_iNwҶL:Gw奄;$A_qYSĐBLasCUuFהFsJ69\s^p"I;֏X cL{4F(hЗ<!DʔRq\c ˰uzLRrA0t^2CT`fsuԏՕFv;s1©R$.sVV==oQF `&E|;&60y. >CU('έ/~V< u$VJ՞) G7`M܌9%pQwτ6YLWKvw$a7h\`!1'\o!ٯ/ң:>m/{Oqc4g tB|.mu~5_,;Jťce~KSyhos :($/Ϸ>{Е4(l3FǴװV$=;bU­E+H+|V3ie{g%P%_O؀TvS7ؔ`$+u{7:N/zs>Քbj5IƖ9)wp)IR89G֙M5TT/A:T?%u?7=Y=Pu??pykd<ޔkpz^c)C1c&ݮ@RA@C (B]JKÉ16,I9쩤#0vJP*R?;LV%)30vx쐫q5&z(M4U%iF\2 Q36@Pm?Xn`eA;)ps)9r샅c9{$W\buX+3Әi>펴D%O#ᣩ9!WglE"Y37EU?f+عDž!zbeߌ0&rKvf/1RaG>""XAH`zcA5TB2M9Xb6AC"c{FRՌy$)QY0Uoh(qGlʥX*;KRK 5H x A8hsZ Xz{>DYSPr uPxNs)n@9s jWe<>h~'F nÈvMCsʭT9h}hΙm;sি_+V,tϧIT*sW)SnŌߧ>{;Eiܴc8'*g ΘX"xrnliR ]RO?qޏ=rW(-B@y2o!"`-lVTf])rq)#KTʁJHcLe9V $VL!V $ dWhQQ9;锋}N9(S(&W,2g.ZP =Hh6F +1;J~@A*b':=3<7g~̻ɛf[k{`kռ 4G{md(C?(D b-BU0SOV^(̨:LBmx%Y_OVWIkk#Oit 6›bwg*^x?BތlEqnU]e]jQxiRS"@EnA!;$H8$^"t *t ve rddcuʬLs[(L1&b kj05"Q**ETAU(iU$ʺ#;_;hLHPiJ)9"M#FyO2AsK:csqʺKA*_%ӸEe"j7Qϯ%{ٴ贬"ӈ[{;:Wc_y푛$ 5fÞk7 Fڦ#w7m|B_XP̑˱AF6JD n!jS nָ7;195sz΀ʎֱ k].W+EuOgdKfwE>޴7 VIҊX<4)gʗc2cfjg爳Kg'~'?뮙j<}y5sGԢuRT]Dz%|p_eH;UWr֍V3mLF<2{&Tyޕ~wK{2J@tΐЕبZ;^ńtV ˾`c?!YL^2LX2=cĘIsH T*^YH'eʷARe |7nR&cHt9M 䒉SrdQ( 3 \Siu`E`G4*5R0@!PSK#<ZҲUSJqn-=4NRHX2AT ؎yuZ1Ǧtʨ%XI##k/>rnao8zn:M2OG{/_916^!q\GbXm`֘'N#3Hfa?wT%_4(殣ũ pHI! 1| <-|l^<3Uܴ+3uΜ>:ex,pT7F)pw˒'L'i:# EbաXc1+&r1ҚZ@7QP;ng X-42 YJ5h|JRFZu9 *"*+dSI =╴# 6Ieb wvRSl/)̔fJ 3ټтWZE]=gsgqGǹ|]DzsݪR͗7J 5&nፑX$l*hJ+V#(Po+Z\;@Q7UY}SY(%߈bӭ CoHAdfiۖ`qX$2=yڿ" D"p7{>>lA܅@r8qHkK%A4!1QܬL*D' 칆q|$]iTzAҨ&a`wSyR3njjIhԨɩvM U6[RwYY\%nKZH)($h;ڱ !|!x4vnKlԬe(+lȎ$!|+BW8֖c_RJpm{m1ܴH4tyʳ~hF"T0a:jћU+R19ƶDŽ @J$w9*h? آ3uQ !V97qpἁRT_R =8D'\lq*qp4.'+}ܹW4k1A0.=ʇR?!Ԓ(KnԀUΞ?<{iH&)/_o?SR;tBL"eL_W3,e(Q:b(pY QYObzΐ f8~VIq:\%=QL]xKHb-)'6oIsWS CT0SFVK pL*#*5ho45ɬ^V4U%+Urm:'(P$$5|2|liy*pStweHc7:Cu(W8ĈO/gQb$@sNYiVr2[8 J sWwߢSnyӟ&5,s Շ49aހ6oE[[lzXg7o-^ H(87khdGM2fv&&qf7k 4WVdCC.g]VFY1RѱzMjs-y& h'N\ 2MVt ,p~/Fahȡ?aȩH/U1$:q[Q-%q Nd߁)Ԡu[E %d@ض˟xw;eA>Y>x_=|?^Ʀ|qS(ضxoen(Tevy*Ҹ)Rq2QdCbI S/- O"il$I w$5Y llQ ^\&RP<p1O8*}{R!Qϲ$4@ IiSEN\Qe6͐QR4mfFQv*-7"V&SMN8'PիkdHj{U{՟RMZ)?gPYHoPo֧jhW/Y~(ϟK3Sz[.j~x8G'ϗ-ahm-@ZBL8h28 ҇ "a0~ 9Tl$s5}ruA2ryX/GoƠ@JpLziŹ*ژt7܍'N 0;+WDa".\tRG 0󣕓mw M%k;}Y¹q u̎׮W(|կ["~_n-v]O0hHX ; NrN 2QGfwTH&!(ʽ0Oi%$ @HYCZJ]!V5w%ʇs&}E8;:Y;v$͝  AKuN=jrWlFVVhbϙD}iZ\p.<-Y󩚂1R1 "Gk,95Fڨ*|L0ꡋzt:Fw3P>R4H٢ Gf$ >')OO:$tE)zDK(RKi\4y,gW;ܖ 9~=Z Ԯ#ƿ|kцzcu!ު J32cY:_e%2dڗƂI9S sT } P}YB,{/\4t!dNo֪!7 M gIi'I*+ L H*R(ĥ0XCF |5˒FϯPdfJSЫĂO%t|O:-la66>g4SinZe&4@Ҍ3 r_(ǂ9Bt Dc.oEyjpZ^W= 9Y\ÂdLl`6(A֣5mhݦ&ٍVRWr5S&LP4j76iLS*2Rv%%n緽g^cƦ+R,X3G {ԏ? GLҒ6r)T/0LsT;u_M%Wxfg7?kxsCMU s-Óeh4ٲٲ&ZBFڗV!w/[XɗG}OZeM7cP&ӢkHD|t+! ' Zփw}%]scc}~魯o=B^ڪ&VCN6Cl6akYaa74W[Mӱñgcll?$, d$qglXIC]H{ |mxeic$sX`As/Q)=UeOȗ٦FKhU&gׯ41_ܯjdzVv0K1,N?<%c@^cB_=tJ>Pg*ѩ蹨ZvGXQ2aVUPAvQWy50@lmeT "i`*3hmV~JuEzĹtJ{`n)]tsچ)7v~{}}έvFfNUMLge6f^`QEl^X2i^҄ϑe " q1GGsa`a>>X':O""c@S@AKdSmxa@MhI#IBHJ|5$%L9}?P)k'tJAtar/xptS]'SGL5"1e:I2 Pyچmd㫱\~K\^x2ҭ?RД"&ω( #-Oms*x gxl&yhNQ|;;CԊ(niE}7%?h2b~sȏ&rpi,`>0Kd1 1¡+^e[؝s^Ğ4<ݸ ͛gVD"UFئ3'!^?i/=ԖE,=YL`$$68B)-3 \+P!G W'J|BPRA\]׎rTq%iAe8*Yk)P[A>'cs\g0RfsP5i8x $BJI'Pj wO o/4]mxə5noڠh>rBn^Qwdn*swB+cD˅1rcZ(dn&E H>JwMv[ANjN.r3]H&z%4h8YvtCXMt[dBءh6.åT]!zsW* +¿xr Ulr ԟ{-V`D-?|$7(v\څ=nZ>KPˀ2qϗ+4XQ)f+gJW e = ' ЈlDwm\\Kt̓$;xKV #Uv=;:JLA&^弄ogF<9ws.YRCqY vk]Xru])!5ڶ2 jA9TI J6KaY͔wpa0e8d8gPfK}ojȨQ>RޤԬ^hjyM!7ikX|+ݿZ,`M+6{B_eCGEWJ^_$"Mc+Vb*">RZռkLNJ((/ !q;xMG&O:*cE(9/uzUkC%^A"qp׆ 76jVWc1%;dF<&EPAurRHSyJXܴ!{]UkoبuWPULEpS3}jv:@ MC6w9xvF=cY95D|ecϢU~ZT\^ w5=p=d6XT{yXY]*X /HWYRAEҩ`k<iNW -lHt6nhRuR16z&bbjTjUnܧ`+D<*ΐadK:nwtoc=2-ui,]_XI.-KklEQC؏M2SliL7aKY+s Uj]ȢvɊwZUoskwFF۲W 7t0aBeGQgj 벞eMU^&؞f4nioаwᦍ.!bƟI5B;]ìt%UJL݂cK/-ۀ'S (Y5H UP~R˙Br)8J]<pN=2|PZt8D8@3 ,)s d*r$E h!$J22DT&h֛yY+^/! vl\wy]IaZJ9s'G)GN䈗YA3@=40@J%4J"E Jjm6W 㿢]/ Kfpp?xL[*q-H#[e"XGjh$b6x3 (7ͽȩ8KUTwٛLMfR,J )dOțoa5uZV{KXVK_y#j\x^dqWоCt+٤j^yMzUPi}}cޅ=Bf7@3#m ^/ݹ}=̌Ai~`eFk:C*2vH $Th*W 1$όpQBGIMnUjT5QiTo4IiX,`1nIkbCp푐j[6?C$ L<Wn.UHl%0;\P֘)q>[vWN@n-@6 b CG f.$0`^YL0[[MZoqN'\o_}`F/Ԑ-3E1 FP9papӋ_}?]WaǷ}tx5{;ɇ>G|ssӃ{_X~"{6)*2e Qy*88h/䥳$hs5^kPiyb4rBB}}fop,eEw;H7 oE>%H^)U& vG.mϦ0} xwq>}x{PWoKݱɃ{lu6\ƙ`PV>9b#&͊nVcP(d"hG!<~<cstfRpt;g/:`T2S$:Χ{dEy>tࣵSM\QW\DIX1gD ;,Y]ci"W+ғurH&a]ݶPe[\{b'M{U\}X]o8&<j$3Of%%YrJ#5!%Aӄ;ҕo׆JiP#*! eaCtK#bu;=d3׍vHbM$n YចJ)pqod#:F5PSJ(-tKJ(CdG+U(TZ)Vcg{-Gi ;8%Ҫ˵xzɆ_=ukjM#[a3X^Uƈ7ృ=$j[v$y:zje~ ^8RUKKD3ӳRLJKL&*SR%a5{xcyqssq0&zqwxztx<l")LUasS;֜)xj|#jF+׶`p<cV=&5|[l1bV$UʌyRvO^b'{֕r&x2՚LR_t{ [ _y-SգՓs;\oL''cy)oU6FFomfvfeAvAs6?=3=3<3h|慲G8?[΀'ܫ_+rWToMLWu_-[2[!U3Lp~%=ad)w=ސ ^zzq251UuA#˗W"D:QM.p؝yұO[(k䫒)3kõ'B2 O/&0x GKhsD BW^ҏ?C3KԼW:-jۍF}IKw/*wsXTH,mcM#MDڋyO彤Q.%4fi})ǔw7忷t@TY&"%|әgԽ=i$EOQ0>Y Kb2!hm2"J|ExQY9";&V( fr^f+a"Ōh%iIf3D3YI$1B0n 5ww*e&s9]#_MT.MEK0SW(7Lv-:#j`/pmd*zk ^3zTCqӮ/&70cUG>|$A@]o0 mщVm/d%z.cvUDta dOʟ=an]O,?HmGN=);U<z7c؎1-NT>5LQYPm4tNlf ttopJ8[i;PfC1LS^˽g:jNYXv!:*ڲ 5Ek~/#fLaRnaۼu@ii(r&_BM'yt(w&]~$U;"SUS+Ndk闞^z:^nID&[r?_;Jgy*}o.^"3#s {"1 V9tSrTN~H *+3LVz`-*6d&wLMnM ckdd(jH,[% Ys|&|exw1*gWxb^>by)龊"/]]{Q+e7իe7HDv-J_c.ۋG{ŏo,5p5|{^3~٫?Y׸!DVQpy=vyQըU*X2&z9 S>Oj~GX4C g0z<^AD:ZSЩ AU8O H7˥9q Oɽ{l iB/ݿ% Xxw}vQpP;+>DgW#D| ^txIpuQ={%"'F~.nP*" r#a/8}Y N#S 'ɐb,.F*4>`ҙ\Yum^qdm| {J-pbb2dτtq7JBySS>*.տUԋB#tIAm7VdשPf'3h"c$N3l8*Ҥ&" J qhXS%s}ah?4/TR'93b|뛮oh] uNZnutU3ٙS;)LZ>3ygj2wE9p"v"l _~< !Jq\6KjRJa[}vANρȝ;9]wY,lQX BpY E7!O0ۏ $i=J6q:N<(|,V=vۓG+V0S \pK}ZN0Y!UH0q,Lȅ%~ѱܬ#>q+dʡ U &!QqO4|5[soOqxSjg^cl@}#Uʜ7..!bwW+ڂL,f~Mf'Yfm> SϾ8ﮉ9 L=qD٠E&4<2ԒTzM-wyI+:s.Q?mߐ3oUMB>ޔ,T4w ]Ud{԰ѳ10ݯ/ww}I;-L[~:4͂b΄%I)W6v7dP=4<5n^r \ P%75я6/ۤM_t973'xMt-Ritq00rbuT*Q j6jPDE^]m7\ՉG_yP-7ϑBf*`0|bĉ6=CA(Hs iU#-vK=8qQ50GçWӽ]g)}ض4ے 7{ #E6[Ϥ(nkq_p*#Ga[i9xIhw$qNr'p+W*;).aLDBOO.f~Y&@\25ə(%H঒f۹bL4 Y3h:8s-F0R;# 0k2E:%Y`.Zd$ NٱA>I6Gsb sKYLI0b1PƺD(?>#/ d",F_EMk|9>|8k⸶2Zaߙk YMlvQ_|wڮ ʮnYr}sanO%M.Wɓ[-큛/H,bTxv iE$p"+k'BA8CAU+-&go_STod)"KZCVRnFzy$ )mgA B =/("`ȍ@SK)Vs~B}Vyui7ZxZ tÞk2p{=[2//ޝ~gMxt:S^cʩ{{R8'zފr24e吤ܫS\\p\DaC6!܂ _ϑQ<#ӱ}Hd&EF<ؗLSxkEƨһ3Μh 8u?V>Vҁ-DˢlrƻiH5V%J8 1G6~E˕qy,y>]{eub6/URâˇ]{%ʖ{"Um Ɉג: ȉIv|dg'.?\,b0T_.} Ll &MT -:ɳɗj*e_,cpk>ƫ6w:1cM]Vrm!I73e+]}C*7,b4/To|<ojd+׌m~]P̢5D00SΦ` Kj$\(>-]ܷso5&\2R=*s+7KQ=:n &Cwd<#t;jTT.g%cg$R]6=,I{yYY;N7. tdmˎN{/ntŚM߉gELF[X5]R337 ⋾O-w7~3L)VI@5)k5Eg<0`ru~^Ps`2`ցlLL̀jM#$720mGTUQ5 Sх&a; aO,k4iW `?ւp; L/7 L+3Il B1#q!%Ma>\N8I0/C`~=ӀBG^͏c!Pn27BQ+B;*?P/6"T1P~U% ]IxP kvxuml;W?vak'B ̈́ztAh8o#tgC+صWƲcjM&Z`Q@JzjO L0w*0 BA)kPnQ`-F9T( *qPPrq'AZ ̢EV!S`5. aLa-VSrkT`Z9Fܩ3U,eVQ]Ia NaړN s(u)0!'>9PU`C };ЇS`C; }?Ї }+i `boa#!hJҏi!}؃s=H}Et97X# r"E6H7%eV)ԠU܇@JIrݲ$bnJ[_~l3NwH[r: u'QNቼ 5N)% -2N+ _n L[>BG؇Tm2*>86HC E̕!1w@}K_7$v3:$6><3'6!^9|u+v44[?|n+Gnܶʡk'vl+o@7l y\E=rxFUBC!u\wmC7nA毶F)C؆CmӶ1qBācCs-EjvkŮk$ӝ;@ұ#;3qȾcqX]32!޸m8{fBGn'u۾ִuCG3чѱqҊnIZpw#P4 R CDT{pJq;Iu1Z%}ylh7t%>VZFkRƇn$=86 ܼsȶBU6F&G'HBv~E0P.}=J|P}Y0'اo/7r#ˍ/77r - Kqy8{>JUj2k\Mq}_e}kL>tЅ0D;ֳoeCM g@?>@M`FF}aǣUug$`zG(A.:}4׳+V(@m -V̳Fðf_&sU皍ُ"3(0 WFUϲ??d?5Z O"+ '',Uy7Y<اa902E|Ǡ(`F.|o 6{=*!gJ~ ?Wᙸ{ {7<KMM7ݳpL/E/`wВ[ ݵo6hQt>}sRr,8{!^ v7/-ﻡ߉ `NSO=ybo~LA&Cd-JUMO@WK5G=t2n Bf#~Ch}bFu<ۘ 1:lc38 9? 8m+]gL4lLفkUZ܇Q#Ə_PGutx-v((>39wfpHm,5[WwR5pWˉ[g3$U )`T .nZ\h6B@Eh}(F]6Qu ͱؙm|4g w}7xc=Ak6bfǎ~y:8qlfO@'@\\zΑT@Cʆ )nn^]f6k휕8gx4c8ى4Y~5tX"C ;36i_XQ3u9+g3\G0\<0wa~3 (7c%98g}{g{zpj.μ_eFYFΛM⪖{6w`ug TB}f:']Hk f8=O[]W|kKb4TƌB4H(*#P>Juhe-GH$s4ҵr^Q(&Dc(J2A A̼43fـhf~H,Q21H] ".GbruzP=/Iq 8 qeO_ EZ#C`g>}uxdW$@a#_EZf#-m<@EZ HC-ۤmm-=G޿,rddpIYvRô+WG8ܜQFx{V8tp, ?{J`ҧ{f 3F0$(ۜm&A0:I J¾#Jޖ Fݻw31{|MaІ׵ϬbƙAM(%7'&zlًeL_Hee4$ǥ?Mx~- p'8&=;FeZ o=+nl RX9N endstream endobj 20 0 obj 35577 endobj 13 0 obj <> endobj 21 0 obj <> endobj 22 0 obj <> stream x @T8>s゚w}/ey,O. "1!y2iMmlޏ~D1iW6ئh[6/5i &i̙gfΜל !ډX$vnPNhy!l:*t<iTQo"$o7+~1BԓG:B5PG=АD7 z6^qG*oξĜB:qŠJL> Y\PгuH'W)qhÈa*2Xg:) r55R:ș( `fG˝3x눗/)r#4{H ә7|*߄ӗϜfa8 OF& =πGu8E2@N2Ԉ9dGrAȏQ Dm\A}P3j t/y ]^QQ- 3 2s@h݅ 'yFAף'ЫhCw(-h%9.E%3h G_Ew|>&ϴ" c%NÙ쮙PfN Dd3!)3=` iЅt;p[ (sA6hl@7ڞW⃬a>XM D;i|>x'lGfX_(݃C?}FǫU3͠*Au0ntp<؋?o߳`YĽLK^0u(+a%bԇCXė@o3ۘ2;dgJgE ]X/+5W=n¯2_e'\ Pb7Eb9Vc`Xٕ=晱!/JJ@5ZtG@=cг 98{1{){L&{TSI/O8y >mm^OY[b7H Ri=W}V|/~?'[}O|yy9Μ`3lv{7+eThg"33f=Y*t*T ڵ n݊< A > &'P~9Z A')f22wOY g+_yTePޘFӽӷM)]T楁Q Rr0ڊǿ s%ѓE:|ICSh3 O9VG=$S Ҏ [s%ޅowg?.W| kBLfÊZK6g: Uu,ǚX uFv;>7kYHV-kmDKXy||S~{{*?#QܢG1xSQ+[7*V>rF}j湹[%01ϘoG/{.dbs>] ~^"D?a\=b;1w2\.]+{y/sQ2ztv֠{Qf)hIt$@ \u(+àw; .:}ZYlR8rQb'L;`עُAfC?㘗Mv?H^^֓S T4ǴOZB3oց$G`]fasR#t,v `FtH}=<-]ȃOc.eFa>go?{ hxJW^:߉- ŨB3A"e?(k }ЯtÀ2PA93L/nhZ ;B<TaSȊŨ ؝E~(U + bќHvVf8 }^d]NfMѠi5jR! F9uva<>. y 4_3.S4BL0?)J& TInZ*pQX :G"a u[{Ե/i5ڄ&ija{%c+? wՍ;lkeںEn53k;Ƒf(N3Wi^tp(؞'9=wu\vh%s"0q3U\O:G@{\/XvS/[[a 7W 0smq|-L)UIKHK¸_sy;Ƶg&].i^WYОcNQp^$s3I=d0~>{F!Nƕsń"PqSJaM$K=?zwDzյ{rN˃_! }–T"}HdN,<ggQւLJZ/ldr>h-^"&E*;WN"1igɓcO';guo&?F)qUhcۿy77XVӞm jҹg)h,?k򚂐a8~*>$ȎnpF31z#S"!~?X34w,F%)Y$s9̭ILv fVCm [J8fC7{.neno]Tk_>9=/*X;IZ)m+E_z I6NjP8ʴPeAt7u(ayԥ1PFs׆%CݹunpZLOp(?qCKZvZAfN}tqlz /T/ƨcC]]GӢnzxbk~ pR+ %'m~dMF4UHRꠑ(b -A#0AHżrtj#Re.n&VXh⡐ߧ &~8pR9_(\b|WrӗżB\XѷaEM1p,2Fm>`{IUO}'/ͪəcZ}\53ȱ|Nt#9'g3bLOtLjcgpFڇϴ ̇MAUUg#4hWD22}Y~Z (Äa'--*{TVf2aHm2sTem-B8Uqqa ĮT`/𰤐mx;.`m|}ib8W 8w_MwM?N?n!9G8<5Wus[mpz ^A{vUffp@|yFMddl5`f {!H^a d\1>Élߌ6l{B~+3/4:::/$P {7hsNWأgUhT k'wˮ7FT_ឃA"eeĝ1)o_Qrʊ`Ra-!Bl*%Eq00˲e˧.O~>ꫜt/l &vDܜ*vwxy0h<4u+Q3_ >+3" &>)TRl@RӚ\nruߘdd3o4=xH'5  ڪwڮ_ޝ~DG [[ץ=aTL@Zd`<-"އMf pUL 9P]+^hsMo'$lk3Q[0֞=wEo\5v@U;&l .dJ5p6M)!: AvɃ8Hqj"6GQmcJ (nipwK_o>xgw_bJ羹}[m|_z2+$^hiE@އ qhp4d]~-l?%gMT @GM-plRq$p$t$[_%f_u]*U>}>,RC13-вÎM/f}yuyG<a^ &gBA.8<'Q` }O2E݌Rqb@]̬dmM>o\[Hd'{OQG*IQPVYcmD/dYtAejˢ+ 6j~gP߾t h5U=pP?=SfP/p2/22GYۼ€FeD0#Qc2|B$S]٘uc֍'ъb :!xO+z:\akVјM7ʶf7ޯ}\Q2je~yQ>/x{_B\ MbN4] DU<0s哸-w6g$͙*|۹m)E` $DZVc Vn _*.SmVwwofG[r/kxW9>z=^SAW+9yqs#,yVT_9骅d׿to(U0 DdWV V,Ttr[ICGc2UW=}-zOQuN4m}IZ{V~|V xg坲ΗʖN[{6A|tFek]6, bfu+=dAE/_nX.`Ӣua3ZedeV>_ۧo\бV\xEqI z9#sO0^!*d v8Mnh7Zðiq>.sH!.Xo:eY-YYƳe) dtNƙm2é5ws!z?Ѿ[ő!2iIZ,Ƶ G"5KBJfCrg;Hq$bWP!+(!!8C WإxW _ bEA|P=(ܦzPLKu!ۡ&=P[+6N!dA ^8IΌ~>Gڷ|Ͳe}JMVh-(\_Y#_65} w~|M^qBvt7o}~3g/8ƭ<3OFsl|z#UU3=i߷L2OX{?94!lMC!: :rp֛fu8@4#UzdZ#W"YEJѭ6HxC|:^Lpf$~BR|e+ү]%QLX #j:3LC\= SJ6<;E dbasј<}oV9=e y ߣ*[Sia^/:cސE C!`oF8) !#^q8bb,FYWKD11N"JZ N9sKv9MSGRt2Y@I&3g4l}Z 2 s(3f/-FzȂ&oj*Y#ZIlP?6) ;[;YWFޡݶ?ۣ9Ovo41DmԺcuRKY75jCl0!H׋ȥ3{ijAigfރ\{Ln[!(p!B^  SgʶMͨuTl(-;i#_{.>ѷb\=߻7+jbk ŽxÊ4>H+8,;02qz ?u^x, AE B8j&P`;ȝ1Nؘ¶<;dD(}b=WJ1=[wˆ^oҥH)Ʋ C۩ۧ;S k :y1&7S +ȡslyE AC?qEWX'`5+u4xNF9w )S91qz!&UbU2E&QaQ8~މ*mS Zhŧ,šTYnؕۖ./Tt=#a릶]v-cnPwhMWn2Z5Ƽ6zu<-+X^}CeGɬù>\eB\fKfյFՊ >!\Tfc:@+EsY +Eϫdeb!&j(]ˈ1ˏl ο= eH)jZ748'gNP׫/a%y%ݍUY95"tXir6r6)YH\1X8vg##$8N]j  bzRKo’ڠ$BqM)P b8U 1 &O/U GTׯ\tC_=]\w-ˋ+Z–no{o~??xA- LxE7y/]v-=Q_L|Q᪥NFl>EuiWMȌygo&۷OM;|= S8~P"ºt٤QCt,Ψ>aaxz]^D/ivZh^|܎1 W{=<YS 1i"\GmT!:NRIƑ>]9 UD׭{9vZYh(^2`Y"  !zY3lEDeLCO^KBE|&W+>cO0$s_\KlMhWen[)QbAfؒi^QÁc p72lS\X#Χǝ '(0RrZ[@ KtrPx u|cj6C͞2eb(7ʰNX,U(['1 ?O@f̘ZM2h C0.NyXtxNIxyIJHJNg$.^.ya4kPtg:0aXefr(3ؤ35xpAr{| =+p /K1|PCtTv:_d^5;pz5pU4uVp2j0} zWgy_mNl3o/\b7hr g枟\w|%Yǟnj[[{ّa% O/"昊_`8"-rj!ws%nt]i2C<xBipv%n%D>/ͩ qoNԵQDs]pW]5E͗PSꚤ˺RB !8PF(qdqY+ƙ;HfnA@ZlcgGhGlԲ9q0#rfmwz<`yH"+0^k^ηPdE^8;{yS ߐ_dƇ^_7P쏗D`I4ue?Ε>}V$xp8 a8ceZL 2pI3jGj6r[۴"֩7ōȃ e)f#= |Ѧ (z*㥗T7Me)<c<#^d4~BԢ,5GwxnlAF>~,^xܹg3R< !ʡA; \/^^YEAKm7WV5+5 WUJ]tZ0҄j.)l-qn'gey-%oadT\/^ͳrU2Ne2d Bor,.]g_kaIc~\'׫FBԊ:\ 1w09M6s\'4c8x~7r (\57~~fy(4{BfmʎE5/1K77HZc\ Κ7`DZ@|G`\7K/x ++s)g^f: O'.}(BFx\%.IA$ڡJxTV?d TIvm+ӢF^ 0-a Z0L΍i=CGXtMqn v6 Nd"cLaBgJ e)";OWDYN+N-'cLԘbD IXd`H;>1!T+Q0 #89 P9Bl:8s+S ~Oy*0?;*܇^7[Fk-5}MGy9_;y +ze&x_) Y?iMm-ܲ{ŗ;4\hom)Dwڽ:fZrN ZaBiE-V' Z1C=sLr^ȳl1%M}lK;v"Ms.,!7F|uY> oE'ZedeNL|Ot/Ɍr˹4)%Qb~t:x/W5΄Z"@Q]Ѽx $vW]Av$vWXPW0H) 5r&@ HTHLfKa@*'X bk ؐ  FNppp湗t gȐk_#[YӯAHyrA!P[!]MI{yJ+1"̅^jJ,w #SMpeIǮ{ *sS_DnefȌeyV{sDwG!;CJU5 ViPJ.Iyəˍ(]p9a.320)8G%ypn XRP#XQ4YcmL~I Q-qJ{D1ۧ&vc$sFSsmgIp#ύW D軃^9V!Ce8Sz&餥VR#5VYw(A&gOK>OL?_&أ/1#+e tt]`dc0 *wOfȓ Ȅs`fA%X$ڴvq,'S,1[^eUٗ[ۖךx6˻5]&&{m7v}3&\ޛ{GM'9@j>~d$'( d1%=W1c6Ґy,ÑL>,:Tڞ&ei6|H:ab8Ɏ<"99`y! gef1{T376s2s!#!N$Úpaר9,Dsr(RRt8]\51sq oI=,MIERϾTPϨxف֋Y6 jMoVg|o|~ ڛ:Qd|OdsK},-ygB&kg[CMS+6{Vpap` O.˧;jYˁ%n2ꝅF0 tF,I8_Gs 87u6 B9w VV4ej}Z}ƭ V. yw-Yu8-i*XčԚϵ_6b~*/LɍS7su"UEȕ:t1>qz%W7fENg!t=Oi A!/2~ 5CЯڛ2x-z/2  Gh*hO1MP*EG0, ?#Ed{?U4#^Rt !AمE@##BnBbXv"؅P(PV Bٹ kA(o(#CrE@EoP5̺8%=ey;J~iRzh X fQ~5P&~3ˑ1`[5)XN΍B!Fש})X#_~(kѰa_)XxOt9y tJ S0M)X,P #iA V iU VqT(t0Qa*Rguv ֳL)؀r@ R )Lqܙe(1Fa+Ϧ` 9V Y8^ uNS _(NW%J$_ +|%XK`I,W%J$_ +|%XK%ʹ0nvnJ2Tx#kq~'ίP@4y<P 9:wR0GO# "seh23moH2h *Уʕg#0J{F @]0G/L 63m0(.xFچ&hn߯L RZ% hjԋ`j5A+$~ uޓ-s4JTCyk(#sT~B.LV0 OF(FQּqϏk΍@j]=ZC+YW=h{Hq1W&ں鑆&^ilAJ ]&iȆmsoJPn h}:[y2Byz$o%V(gTm#%Ay[So FGspfVjۨ.'(#4vAwB[]_7/@j]cylKEu~ Ԧ%7$ӟ$SDr@+>?NxrO:`־Ghr\j49YHrТ wYG8MW\Hb?"P;?/J]ӒvP7j׽sVr~mT4{^\m'̭GkvAAdU)п[yXBy=AǞ]M'-;T?#d}꣜#4lO6оRJL=\+K'وeEkR|&ET&c`vQ!5(f=(ӱ%I(wQI>}fǟ{`3!<,IM$  w }¢ю/AU}[Hˈ嗕E!+{FGD͉9MX9!apGWbs&aN& '6&]Bo0 Z:FIXݝ+tw Ķ@˝3 5}]k#d!sx`d{4".EmY%M^RzzaْںUuBuuMuͫfuO0:`16w Q{C[F†1al` 9tKWb+a]iF?KT_^ m42*\76l۲wcI1& 1¸p"ʏ%?& mIi:; #=[@ H2 %IͭȂ F;:G˘,#EuKI &!1;1ZN.ZU-DxIP_͋5Bc^~~<yIaPR\TVTb۶m<+΁m"!,Fx DHF;@7ƀޑaMo(`Ypa@~YaUaEi}wȨ18H NJi$M(<;nKM.K>+f K!zRnW~{4G+Zϥ+c˗XLW$U&25t/jV/nAz䐮&7UxYlVV,+FYٗΰ?֧FZcJǗM_N36?~} cLwgK9&c' I(#LffIWzS2<4MdMM (.hj !12$(S5[P42bLZO'8 NM j'jĠq0c4~v@b`2O&bNh > E Ǵ.B"ONMߩd^\&8GAK+G<(=P (_`_DzJ}F`'w/ˎ,x|?@UhIy~.ְWQvšc7% xI>TdߝPk }&9kS[&d3eO(drB/Wc'ahn쯒00qjdvW{;EݓTbBo(8Vf_{3ʃSɋw=1yDPR'g'ƾ>Hw,!ǒde^ah@(*iNhWM UO Q0$u}7IXw$ 踝)v'rjʁA##{ <33鯆{!>$5F!2a0dKB)@Ր(͋'X$JmNv@$t&O'J d邛_jN9)PTKS;1QHRe͑ݱ{XNTH–J%` T-dvH C YN#[ k*F3X`1 ^HOC: IN[!1О3C#ƠA.BjH }IQ|S!!b#|Д !``v;d;;&X)/'Y.2!+iWw}ߓS j|y\Y^H?jVXs>>%sZn1qA'^e^|vBB}r !u !#$?[ >H:$?@ 1ot_^@ 0\@0(?fR~OGʄEd_Ft.??/ VA_.:+lzmN#q=gO0|I҈O2 YM2dմ`ɦ ɦj(ɦPMP%nՌȄg dX+dePȓe$Nf$ٝn%P|H⿡nIvoL2,3 1B9l#1TМD"? ᇒٙP<@Tܛ^;&o;3Pܕ#݉28w-GMnxٝ']RQ%%ʔ&X\2[A!|a~ rI* aݦ ;Mo?z?oT(je_? Ϳ?'3'|G-|{銖T +zT@[$ډ4,*R~UAZYNXTzEeVq*JҨT*JF^Ba|OעH\Fa!9CƋEKx4/4N*gVFǕ-=[qNԸAhkVk5q8s$FNZ7?P"s~ٶV9̕E_E,D.eָj#hm"OQ+Y8~'5mI#00>#KSӆRस811'{ X:g 䏠ҏнs2uI>&ih| 8ca&a5c!6E w0 +!W9$轆:w!s;u9A_a mlvY)ǁ>:=v+W|ks_9zU搃cT(Χ+|Z.twaI?}?~Y{+"V'~㿼l{` (wk^c궻up7z<| NKTKGY.KbOl/} ~}%8(=w%Y)򫵴 +8+$ 챔z--$" *@5VIѵɶY&E'RaXg>>w%B VM Wh.)Ϡ/!Ϡ0n殹 0v򕹬gn^b3~&BN$=VKDHKp'0EǩQĦ^H-N[j endstream endobj 23 0 obj 19183 endobj 24 0 obj <> endobj 25 0 obj <> stream xcbgdbgM endstream endobj xref 0 6 0000000000 65535 f 0000000016 00000 n 0000000420 00000 n 0000003054 00000 n 0000005742 00000 n 0000006256 00000 n 7 1 0000000080 00000 n 9 17 0000000400 00000 n 0000000488 00000 n 0000003032 00000 n 0000006401 00000 n 0000043056 00000 n 0000003210 00000 n 0000005720 00000 n 0000005887 00000 n 0000006235 00000 n 0000007168 00000 n 0000007366 00000 n 0000043033 00000 n 0000043748 00000 n 0000043948 00000 n 0000063221 00000 n 0000063244 00000 n 0000063313 00000 n trailer < <197C528D9EC14744B8B9E53273E88D28>]>> startxref 63447 %%EOF xref 0 0 trailer < <197C528D9EC14744B8B9E53273E88D28>]>> startxref 64091 %%EOF opentimelineio-0.18.1/OTIO_VERSION.json0000664000175000017500000000003515110656316015263 0ustar meme{"version": ["0", "18", "1"]}opentimelineio-0.18.1/tsc/0000775000175000017500000000000015110656141013200 5ustar memeopentimelineio-0.18.1/tsc/OpenTimelineIO_TSC_Charter.md0000664000175000017500000002770715110656141020540 0ustar meme# Technical Charter (the "Charter") for OpenTimelineIO a Series of LF Projects, LLC This charter (the "Charter") sets forth the responsibilities and procedures for technical contribution to, and oversight of, the OpenTimelineIO project, which has been established as OpenTimelineIO a Series of LF Projects, LLC (the "Project"). LF Projects, LLC ("LF Projects") is a Delaware series limited liability company. All Contributors to the Project must comply with the terms of this Charter. ## 1. Mission and Scope of the Project * **a.** The mission of the Project is to develop an open source project with the goals indicated in the "README" file within the Project's repositories. * **b.** The scope of the Project includes software development under an OSI-approved open source license supporting the mission, including documentation, testing, integration and the creation of other artifacts that aid the development, deployment, operation or adoption of the open source software project. ## 2. Technical Steering Committee * **a.** The Technical Steering Committee (the "TSC") will be responsible for all technical oversight of the open source Project. * **b.** The TSC voting members are initially the Project's Committers. At the inception of the project, the Committers of the Project will be as set forth within the "CONTRIBUTING" file within the Project's code repository. The TSC may choose an alternative approach for determining the voting members of the TSC, and any such alternative approach will be documented in the CONTRIBUTING file. Any meetings of the Technical Steering Committee are intended to be open to the public, and can be conducted electronically, via teleconference, or in person. * **c.** TSC projects generally will involve Contributors and Committers. The TSC may adopt or modify roles so long as the roles are documented in the CONTRIBUTING file. Unless otherwise documented: * **i.** Contributors include anyone in the technical community that contributes code, documentation, or other technical artifacts to the Project; * **ii.** Committers are Contributors who have earned the ability to modify ("commit") source code, documentation or other technical artifacts in a project's repository; and * **iii.** A Contributor may become a Committer by a majority approval of the existing Committers. A Committer may be removed by a majority approval of the other existing Committers. * **d.** Participation in the Project through becoming a Contributor and Committer is open to anyone so long as they abide by the terms of this Charter. * **e.** The TSC may (1) establish work flow procedures for the submission, approval, and closure/archiving of projects, (2) set requirements for the promotion of Contributors to Committer status, as applicable, and (3) amend, adjust, refine and/or eliminate the roles of Contributors, and Committers, and create new roles, and publicly document any TSC roles, as it sees fit. * **f.** The TSC may elect a TSC Chair, who will preside over meetings of the TSC and will serve until their resignation or replacement by the TSC. The TSC Chair, or any other TSC member so designated by the TSC, will serve as the primary communication contact between the Project and the Technical Advisory Council of the Academy Software Foundation of The Linux Foundation. * **g.** Responsibilities: The TSC will be responsible for all aspects of oversight relating to the Project, which may include: * **i.** coordinating the technical direction of the Project; * **ii.** approving project or system proposals (including, but not limited to, incubation, deprecation, and changes to a sub-project's scope); * **iii.** organizing sub-projects and removing projects; * **iv.** creating sub-committees or working groups to focus on cross-project technical issues and requirements; * **v.** appointing representatives to work with other open source or open standards communities; * **vi.** establishing community norms, workflows, issuing releases, and security issue reporting policies; * **vii.** approving and implementing policies and processes for contributing (to be published in the CONTRIBUTING file) and coordinating with the Series Manager to resolve matters or concerns that may arise as set forth in Section 7 of this Charter; * **viii.** discussions, seeking consensus, and where necessary, voting on technical matters relating to the code base that affect multiple projects; and * **ix.** coordinating any marketing, events, or communications regarding the Project with the LF Projects Manager or their designee. ## 3. TSC Voting * **a.** While the Project aims to operate as a consensus based community, if any TSC decision requires a vote to move the Project forward, the voting members of the TSC will vote on a one vote per voting member basis. * **b.** Quorum for TSC meetings requires at least two-thirds of all voting members of the TSC to be present. The TSC may continue to meet if quorum is not met, but will be prevented from making any decisions at the meeting. * **c.** Except as provided in Section 7.c. and 8.a, decisions by vote at a meeting require a majority vote of those in attendance, provided quorum is met. Decisions made by electronic vote without a meeting require a majority vote of all voting members of the TSC. * **d.** In the event a vote cannot be resolved by the TSC, any voting member of the TSC may refer the matter to the Series Manager or its designee for assistance in reaching a resolution. ## 4. Compliance with Policies * **a.** This Charter is subject to the Series Agreement for the Project and the Operating Agreement of LF Projects. Contributors will comply with the policies of LF Projects as may be adopted and amended by LF Projects, including, without limitation the policies listed at https://lfprojects.org/policies/. * **b.** The TSC may adopt a code of conduct ("CoC") for the Project, which is subject to approval by the Series Manager. Contributors to the Project will comply with the CoC or, in the event that a Project-specific CoC has not been approved, the LF Projects Code of Conduct listed at https://lfprojects.org/policies/. * **c.** When amending or adopting any policy applicable to the Project, LF Projects will publish such policy, as to be amended or adopted, on its web site at least 30 days prior to such policy taking effect; provided, however, that in the case of any amendment of the Trademark Policy or Terms of Use of LF Projects, any such amendment is effective upon publication on LF Projects web site. * **d.** All participants must allow open participation from any individual or organization meeting the requirements for contributing under this Charter and any policies adopted for all participants by the TSC, regardless of competitive interests. Put another way, the Project community must not seek to exclude any participant based on any criteria, requirement, or reason other than those that are reasonable and applied on a non-discriminatory basis to all participants in the Project community. * **e.** The Project will operate in a transparent, open, collaborative, and ethical manner at all times. The output of all Project discussions, proposals, timelines, decisions, and status should be made open and easily visible to all. Any potential violations of this requirement should be reported immediately to the LF Projects Manager. ## 5. Community Assets * **a.** LF Projects shall hold title to all trade or service marks created expressly for the Project ("Project Trademarks"), whether based on common law or registered rights. Any use of any Project Trademarks by participants in the Project shall be in accordance with the license from LF Projects and inure to the benefit of LF Projects. * **b.** The Project shall, as permitted and in accordance with such license from LF Projects, develop and own all Project GitHub and social media accounts, and domain name registrations created expressly for the Project by the Project community. * **c.** Under no circumstances shall LF Projects be expected or required to undertake any action on behalf of the Project that is inconsistent with the tax-exempt status or purpose, as applicable, of LFP, Inc. or LF Projects, LLC. ## 6. General Rules and Operations. * **a.** The Project will: * **i.** engage in the work of the project in a professional manner consistent with maintaining a cohesive community, while also maintaining the goodwill and esteem of LF Projects, LFP, Inc. and other partner organizations in the open source software community; and * **ii.** respect the rights of all trademark owners, including any branding and trademark usage guidelines. ## 7. Intellectual Property Policy * **a.** Participants acknowledge that the copyright in all new contributions shall be retained by the copyright holder as independent works of authorship and that no contributor or copyright holder will be required to assign copyrights to the Project. * **b.** Except as described in Section 7.c., all code contributions to the Project are subject to the following: * **i.** All new inbound code contributions to the Project must be made using the Apache License, Version 2.0 (available here: https://www.apache.org/licenses/LICENSE-2.0) (the "Project License"), with the exception of code repositories designated by the TSC to be used as a template repository, which will be must be made to be available under a choice of either the Project License or the MIT License ( available here: https://spdx.org/licenses/MIT.html ) * **ii.** All new inbound code contributions must: 1. be made pursuant to a duly executed Project Contribution License Agreement (the "CLA"), available on the Project's web site; and 2. be accompanied by a Developer Certificate of Origin (http://developercertificate.org) sign-off in the source code system that is submitted through a TSC-approved contribution process which will bind the authorized contributor and, if not self-employed, their employer to the applicable license; * **iii.** All outbound code will be made available under the Project License, with the exception of code repositories designated by the TSC to be used as a template repository, which will be made available under a choice of either the Project License or the MIT License ( available here: https://spdx.org/licenses/MIT.html ) * **iv.** Documentation will be received and made available by the Project under the Creative Commons Attribution 4.0 International License (available at http://creativecommons.org/licenses/by/4.0/). * **v.** The Project may seek to integrate and contribute back to other open source projects ("Upstream Projects"). In such cases, the Project will conform to all license requirements of the Upstream Projects, including dependencies, leveraged by the Project. Upstream Project code contributions not stored within the Project's main code repository shall comply with the contribution process and license terms for the applicable Upstream Project. * **c.** If an alternative inbound or outbound license is required for compliance with the license for a leveraged open source project or is otherwise required to achieve the Project's mission, the Governing Board of the Academy Software Foundation of The Linux Foundation ("Governing Board") or the Governing Board's designated committee may approve the use of an alternative license for specific inbound or outbound contributions on an exception basis. Any exceptions must be approved by a vote of the Governing Board and must be limited in scope to what is required for such purpose. To request an exception, please describe the contribution, the alternative open source license(s), and the justification for using an alternative open source license for the Project. * **d.** Contributed files should contain license information, such as SPDX short form identifiers, indicating the open source license or licenses pertaining to the file. ## 8. Amendments * **a.** This charter may be amended by a two-thirds vote of the entire TSC and is subject to approval by LF Projects, which will not be unreasonably withheld. opentimelineio-0.18.1/src/0000775000175000017500000000000015110656141013176 5ustar memeopentimelineio-0.18.1/src/opentime/0000775000175000017500000000000015110656316015022 5ustar memeopentimelineio-0.18.1/src/opentime/timeRange.cpp0000664000175000017500000000020015110656141017425 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentime/timeRange.h" opentimelineio-0.18.1/src/opentime/timeRange.h0000664000175000017500000004234315110656141017110 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/rationalTime.h" #include "opentime/version.h" #include #include namespace opentime { namespace OPENTIME_VERSION { /// @brief This default epsilon_s value is used in comparison between floating numbers. /// /// It is computed to be twice 192kHz, the fastest commonly used audio rate. That gives /// a resolution of half a frame at 192kHz. The value can be changed in the future if /// necessary, due to higher sampling rates or some other kind of numeric tolerance /// detected in the library. OPENTIME_API constexpr double DEFAULT_EPSILON_s = 1.0 / (2 * 192000.0); /// @brief This class represents a time range defined by a start time and duration. /// /// It is possible to construct a TimeRange object with a negative duration. /// However, the logical predicates are written as if duration is positive, /// and have undefined behavior for negative durations. /// /// The duration on a TimeRange indicates a time range that is inclusive of the /// start time, and exclusive of the end time. All of the predicates are /// computed accordingly. class OPENTIME_API_TYPE TimeRange { public: /// @brief Construct a new time range with a zero start time and duration. constexpr TimeRange() noexcept : _start_time{} , _duration{} {} /// @brief Construct a new time range with the given start time and duration of zero. explicit constexpr TimeRange(RationalTime start_time) noexcept : _start_time{ start_time } , _duration{ RationalTime{ 0, start_time.rate() } } {} /// @brief Construct a new time range with the given start time and duration. constexpr TimeRange(RationalTime start_time, RationalTime duration) noexcept : _start_time{ start_time } , _duration{ duration } {} /// @brief Construct a new time range with the given start time, duration, and rate. constexpr TimeRange( double start_time, double duration, double rate) noexcept : _start_time{ start_time, rate } , _duration{ duration, rate } {} /// @brief Returns true if the time range is invalid. /// /// The time range is considered invalid if either the start time or /// duration is invalid, or if the duration is less than zero. bool is_invalid_range() const noexcept { return _start_time.is_invalid_time() || _duration.is_invalid_time() || _duration.value() < 0.0; } /// @brief Returns true if the time range is valid. /// /// The time range is considered valid if both the start time and /// duration are valid, and the duration is greater than or equal to /// zero. bool is_valid_range() const noexcept { return _start_time.is_valid_time() && _duration.is_valid_time() && _duration.value() >= 0.0; } /// @brief Returns the start time. constexpr RationalTime start_time() const noexcept { return _start_time; } /// @brief Returns the duration. constexpr RationalTime duration() const noexcept { return _duration; } /// @brief Returns the inclusive end time. RationalTime end_time_inclusive() const noexcept { const RationalTime et = end_time_exclusive(); if ((et - _start_time.rescaled_to(_duration))._value > 1) { return _duration._value != std::floor(_duration._value) ? et.floor() : et - RationalTime(1, _duration._rate); } else { return _start_time; } } /// @brief Returns the exclusive end time. constexpr RationalTime end_time_exclusive() const noexcept { return _duration + _start_time.rescaled_to(_duration); } /// @brief Extend a time range's duration by the given time. The extended /// time range is returned. constexpr TimeRange duration_extended_by(RationalTime other) const noexcept { return TimeRange{ _start_time, _duration + other }; } /// @brief Extend a time range by the given time. The extended time range /// is returned. constexpr TimeRange extended_by(TimeRange other) const noexcept { const RationalTime new_start_time{ std::min(_start_time, other._start_time) }, new_end_time{ std::max(end_time_exclusive(), other.end_time_exclusive()) }; return TimeRange{ new_start_time, RationalTime::duration_from_start_end_time( new_start_time, new_end_time) }; } /// @brief Clamp a time to this time range. The clamped time is returned. RationalTime clamped(RationalTime other) const noexcept { return std::min(std::max(other, _start_time), end_time_inclusive()); } /// @brief Clamp a time range to this time range. The clamped time range /// is returned. constexpr TimeRange clamped(TimeRange other) const noexcept { const TimeRange r{ std::max(other._start_time, _start_time), other._duration }; const RationalTime end{ std::min(r.end_time_exclusive(), end_time_exclusive()) }; return TimeRange{ r._start_time, end - r._start_time }; } /// @name Time Range Relations /// /// These relations implement James F. Allen's thirteen basic time interval relations. /// Detailed background can be found here: https://dl.acm.org/doi/10.1145/182.358434 /// Allen, James F. "Maintaining knowledge about temporal intervals". /// Communications of the ACM 26(11) pp.832-843, Nov. 1983. /// /// In the relations that follow, epsilon_s indicates the tolerance, in the sense /// that if abs(a-b) < epsilon_s, we consider a and b to be equal. The time /// comparison is done in double seconds. /// ///@{ /// @brief Returns whether this time range contains the given time. /// /// The start of this precedes other. /// other precedes the end of this. ///

    ///                    other
    ///                      ↓
    ///                      *
    ///              [      this      ]
    /// 
constexpr bool contains(RationalTime other) const noexcept { return _start_time <= other && other < end_time_exclusive(); } /// @brief Returns whether this time range contains the given time range. /// /// The start of this precedes start of other. /// The end of this antecedes end of other. ///
    ///                   [ other ]
    ///              [      this      ]
    /// 
/// The converse would be other.contains(this) constexpr bool contains( TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisStart = _start_time.to_seconds(); const double thisEnd = end_time_exclusive().to_seconds(); const double otherStart = other._start_time.to_seconds(); const double otherEnd = other.end_time_exclusive().to_seconds(); return greater_than(otherStart, thisStart, epsilon_s) && lesser_than(otherEnd, thisEnd, epsilon_s); } /// @brief Returns whether this time range overlaps the given time. /// /// this contains other. ///
    ///                   other
    ///                    ↓
    ///                    *
    ///              [    this    ]
    /// 
constexpr bool overlaps(RationalTime other) const noexcept { return contains(other); } /// @brief Returns whether this and the given time range overlap. /// /// The start of this strictly precedes end of other by a value >= epsilon_s. /// The end of this strictly antecedes start of other by a value >= epsilon_s. ///
    ///              [ this ]
    ///                  [ other ]
    /// 
/// The converse would be other.overlaps(this) constexpr bool overlaps( TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisStart = _start_time.to_seconds(); const double thisEnd = end_time_exclusive().to_seconds(); const double otherStart = other._start_time.to_seconds(); const double otherEnd = other.end_time_exclusive().to_seconds(); return lesser_than(thisStart, otherStart, epsilon_s) && greater_than(thisEnd, otherStart, epsilon_s) && greater_than(otherEnd, thisEnd, epsilon_s); } /// @brief Returns whether this time range precedes the given time range. /// /// The end of this strictly precedes the start of other by a value >= epsilon_s. ///
    ///              [ this ]    [ other ]
    /// 
/// The converse would be other.before(this) constexpr bool before(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisEnd = end_time_exclusive().to_seconds(); const double otherStart = other._start_time.to_seconds(); return greater_than(otherStart, thisEnd, epsilon_s); } /// @brief Returns whether this time range precedes the given time. /// /// The end of this strictly precedes other by a value >= epsilon_s. ///
    ///                        other
    ///                          ↓
    ///              [ this ]    *
    /// 
constexpr bool before( RationalTime other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisEnd = end_time_exclusive().to_seconds(); const double otherTime = other.to_seconds(); return lesser_than(thisEnd, otherTime, epsilon_s); } /// @brief Returns whether this time range meets the given time range. /// /// The end of this strictly equals the start of other and /// the start of this strictly equals the end of other. ///
    ///              [this][other]
    /// 
/// The converse would be other.meets(this) constexpr bool meets(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisEnd = end_time_exclusive().to_seconds(); const double otherStart = other._start_time.to_seconds(); return otherStart - thisEnd <= epsilon_s && otherStart - thisEnd >= 0; } /// @brief Returns whether this time range begins in the given time range. /// /// The start of this strictly equals the start of other. /// The end of this strictly precedes the end of other by a value >= epsilon_s. ///
    ///              [ this ]
    ///              [    other    ]
    /// 
/// The converse would be other.begins(this) constexpr bool begins(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisStart = _start_time.to_seconds(); const double thisEnd = end_time_exclusive().to_seconds(); const double otherStart = other._start_time.to_seconds(); const double otherEnd = other.end_time_exclusive().to_seconds(); return fabs(otherStart - thisStart) <= epsilon_s && lesser_than(thisEnd, otherEnd, epsilon_s); } /// @brief Returns whether this range begins at the given time. /// /// The start of this strictly equals other. ///
    ///            other
    ///              ↓
    ///              *
    ///              [ this ]
    /// 
constexpr bool begins( RationalTime other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisStart = _start_time.to_seconds(); const double otherStart = other.to_seconds(); return fabs(otherStart - thisStart) <= epsilon_s; } /// @brief Returns whether this time range finishes in the given time range. /// /// The start of this strictly antecedes the start of other by a value >= epsilon_s. /// The end of this strictly equals the end of other. ///
    ///                      [ this ]
    ///              [     other    ]
    /// 
/// The converse would be other.finishes(this) constexpr bool finishes( TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisStart = _start_time.to_seconds(); const double thisEnd = end_time_exclusive().to_seconds(); const double otherStart = other._start_time.to_seconds(); const double otherEnd = other.end_time_exclusive().to_seconds(); return fabs(thisEnd - otherEnd) <= epsilon_s && greater_than(thisStart, otherStart, epsilon_s); } /// @brief Return whether this time range finishes at the given time. /// /// The end of this strictly equals other. ///
    ///                   other
    ///                     ↓
    ///                     *
    ///              [ this ]
    /// 
constexpr bool finishes( RationalTime other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisEnd = end_time_exclusive().to_seconds(); const double otherEnd = other.to_seconds(); return fabs(thisEnd - otherEnd) <= epsilon_s; } /// Return whether this time range intersects the given time range. /// /// The start of this precedes or equals the end of other by a value >= epsilon_s. /// The end of this antecedes or equals the start of other by a value >= epsilon_s. ///
    ///         [    this    ]           OR      [    other    ]
    ///              [     other    ]                    [     this    ]
    /// 
/// The converse would be other.finishes(this) constexpr bool intersects( TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const noexcept { const double thisStart = _start_time.to_seconds(); const double thisEnd = end_time_exclusive().to_seconds(); const double otherStart = other._start_time.to_seconds(); const double otherEnd = other.end_time_exclusive().to_seconds(); return lesser_than(thisStart, otherEnd, epsilon_s) && greater_than(thisEnd, otherStart, epsilon_s); } ///@} /// @brief Returns whether two time ranges are strictly equal. /// /// The start of lhs strictly equals the start of rhs. /// The end of lhs strictly equals the end of rhs. ///
    ///              [ lhs ]
    ///              [ rhs ]
    /// 
friend constexpr bool operator==(TimeRange lhs, TimeRange rhs) noexcept { const RationalTime start = lhs._start_time - rhs._start_time; const RationalTime duration = lhs._duration - rhs._duration; return fabs(start.to_seconds()) < DEFAULT_EPSILON_s && fabs(duration.to_seconds()) < DEFAULT_EPSILON_s; } /// @brief Returns whether two time ranges are not equal. friend constexpr bool operator!=(TimeRange lhs, TimeRange rhs) noexcept { return !(lhs == rhs); } /// @brief Create a time range from a start time and exclusive end time. static constexpr TimeRange range_from_start_end_time( RationalTime start_time, RationalTime end_time_exclusive) noexcept { return TimeRange{ start_time, RationalTime::duration_from_start_end_time( start_time, end_time_exclusive) }; } /// @brief Create a time range from a start time and inclusive end time. static constexpr TimeRange range_from_start_end_time_inclusive( RationalTime start_time, RationalTime end_time_inclusive) noexcept { return TimeRange{ start_time, RationalTime::duration_from_start_end_time_inclusive( start_time, end_time_inclusive) }; } private: RationalTime _start_time, _duration; friend class TimeTransform; constexpr bool greater_than(double lhs, double rhs, double epsilon) const noexcept { return lhs - rhs >= epsilon; } constexpr bool lesser_than(double lhs, double rhs, double epsilon) const noexcept { return rhs - lhs >= epsilon; } /// @brief Returns the absolute value. /// /// \todo This function is used instead of "std::fabs()" so we can mark it as /// constexpr. We can remove this and replace it with the std version when we /// upgrade to C++23. Note that there are two copies of this function, in both /// RationalTime and TimeRange. static constexpr double fabs(double val) noexcept { union { double f; uint64_t i; } bits = { val }; bits.i &= std::numeric_limits::max() / 2; return bits.f; } }; }} // namespace opentime::OPENTIME_VERSION opentimelineio-0.18.1/src/opentime/timeTransform.h0000664000175000017500000000526415110656141020030 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/rationalTime.h" #include "opentime/timeRange.h" #include "opentime/version.h" #include namespace opentime { namespace OPENTIME_VERSION { /// @brief One-dimensional time transform. class OPENTIME_API_TYPE TimeTransform { public: /// @brief Construct a new transform with an optional offset, scale, and rate. explicit constexpr TimeTransform( RationalTime offset = RationalTime{}, double scale = 1, double rate = -1) noexcept : _offset{ offset } , _scale{ scale } , _rate{ rate } {} /// @brief Return the offset. constexpr RationalTime offset() const noexcept { return _offset; } /// @brief Return the scale. constexpr double scale() const noexcept { return _scale; } /// @brief Return the rate. constexpr double rate() const noexcept { return _rate; } /// @brief Apply the transform to a TimeRange. The transformed TimeRange is /// returned. constexpr TimeRange applied_to(TimeRange other) const noexcept { return TimeRange::range_from_start_end_time( applied_to(other._start_time), applied_to(other.end_time_exclusive())); } /// @brief Apply the transform to another TimeTransform. The transformed /// TimeTransform is returned. constexpr TimeTransform applied_to(TimeTransform other) const noexcept { return TimeTransform{ _offset + other._offset, _scale * other._scale, _rate > 0 ? _rate : other._rate }; } /// @brief Apply the transform to a RationalTime. The transformed /// RationalTime is returned. constexpr RationalTime applied_to(RationalTime other) const noexcept { RationalTime result{ RationalTime{ other._value * _scale, other._rate } + _offset }; double target_rate = _rate > 0 ? _rate : other._rate; return target_rate > 0 ? result.rescaled_to(target_rate) : result; } /// @brief Return whether two transforms are equal. friend constexpr bool operator==(TimeTransform lhs, TimeTransform rhs) noexcept { return lhs._offset == rhs._offset && lhs._scale == rhs._scale && lhs._rate == rhs._rate; } /// @brief Return whether two transforms are not equal. friend constexpr bool operator!=(TimeTransform lhs, TimeTransform rhs) noexcept { return !(lhs == rhs); } private: RationalTime _offset; double _scale; double _rate; }; }} // namespace opentime::OPENTIME_VERSION opentimelineio-0.18.1/src/opentime/version.h.in0000664000175000017500000000074515110656141017267 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #define OPENTIME_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define OPENTIME_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define OPENTIME_VERSION_PATCH @PROJECT_VERSION_PATCH@ #define OPENTIME_VERSION v@PROJECT_VERSION_MAJOR@_@PROJECT_VERSION_MINOR@_@PROJECT_VERSION_PATCH@ namespace opentime { namespace OPENTIME_VERSION { } using namespace OPENTIME_VERSION; } // namespace opentime opentimelineio-0.18.1/src/opentime/stringPrintf.h0000664000175000017500000000137615110656141017667 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/version.h" #include #include #include namespace opentime { namespace OPENTIME_VERSION { /// @brief This provides printf style functionality for std::string. template std::string string_printf(char const* format, Args... args) { char buffer[4096]; size_t size = snprintf(buffer, sizeof(buffer), format, args...) + 1; if (size < sizeof(buffer)) { return std::string(buffer); } std::unique_ptr buf(new char[size]); std::snprintf(buf.get(), size, format, args...); return std::string(buf.get()); } }} // namespace opentime::OPENTIME_VERSION opentimelineio-0.18.1/src/opentime/errorStatus.cpp0000664000175000017500000000177515110656141020071 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentime/errorStatus.h" namespace opentime { namespace OPENTIME_VERSION { std::string ErrorStatus::outcome_to_string(Outcome o) { switch (o) { case OK: return std::string(); case INVALID_TIMECODE_RATE: return "SMPTE timecode does not support this rate"; case INVALID_TIMECODE_STRING: return "string is not a SMPTE timecode string"; case TIMECODE_RATE_MISMATCH: return "timecode specifies a frame higher than its rate"; case INVALID_TIME_STRING: return "invalid time string"; case NEGATIVE_VALUE: return "value cannot be negative here"; case INVALID_RATE_FOR_DROP_FRAME_TIMECODE: return "rate is not valid for drop frame timecode"; default: return "unknown/illegal ErrorStatus::Outcome code"; }; } }} // namespace opentime::OPENTIME_VERSION opentimelineio-0.18.1/src/opentime/OpenTimeConfig.cmake.in0000664000175000017500000000011315110656141021266 0ustar meme@PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/OpenTimeTargets.cmake") opentimelineio-0.18.1/src/opentime/timeTransform.cpp0000664000175000017500000000020415110656141020350 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentime/timeTransform.h" opentimelineio-0.18.1/src/opentime/rationalTime.h0000664000175000017500000003471015110656141017624 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/errorStatus.h" #include "opentime/version.h" #include #include #include #include namespace opentime { namespace OPENTIME_VERSION { /// @brief This enumeration provides options for drop frame timecode. enum OPENTIME_API_TYPE IsDropFrameRate : int { InferFromRate = -1, ForceNo = 0, ForceYes = 1, }; /// @brief This class represents a measure of time defined by a value and rate. class OPENTIME_API_TYPE RationalTime { public: /// @brief Construct a new time with an optional value and rate. explicit constexpr RationalTime(double value = 0, double rate = 1) noexcept : _value{ value } , _rate{ rate } {} /// @brief Returns true if the time is invalid. /// /// The time is considered invalid if the value or rate are a NaN value, /// or if the rate is less than or equal to zero. bool is_invalid_time() const noexcept { return (std::isnan(_rate) || std::isnan(_value)) ? true : (_rate <= 0); } /// @brief Returns true if the time is valid. /// /// The time is considered valid if the value and rate are not NaN values, /// and the rate is greater than zero. bool is_valid_time() const noexcept { return !std::isnan(_rate) && !std::isnan(_value) && (_rate > 0); } /// @brief Returns the time value. constexpr double value() const noexcept { return _value; } /// @brief Returns the time rate. constexpr double rate() const noexcept { return _rate; } /// @brief Returns the time converted to a new rate. constexpr RationalTime rescaled_to(double new_rate) const noexcept { return RationalTime{ value_rescaled_to(new_rate), new_rate }; } /// @brief Returns the time converted to a new rate. constexpr RationalTime rescaled_to(RationalTime rt) const noexcept { return RationalTime{ value_rescaled_to(rt._rate), rt._rate }; } /// @brief Returns the time value converted to a new rate. constexpr double value_rescaled_to(double new_rate) const noexcept { return new_rate == _rate ? _value : (_value * new_rate) / _rate; } /// @brief Returns the time value converted to a new rate. constexpr double value_rescaled_to(RationalTime rt) const noexcept { return value_rescaled_to(rt._rate); } /// @brief Returns whether this time is almost equal to another time. /// /// @param other The other time for comparison. /// @param delta The tolerance used for the comparison. constexpr bool almost_equal(RationalTime other, double delta = 0) const noexcept { return fabs(value_rescaled_to(other._rate) - other._value) <= delta; } /// @brief Returns whether this value and rate are exactly equal to another time. /// /// Note that this is different from the equality operator that rescales the time /// before comparison. constexpr bool strictly_equal(RationalTime other) const noexcept { return _value == other._value && _rate == other._rate; } /// @brief Returns a time with the largest integer value not greater than /// this value. RationalTime floor() const { return RationalTime{ std::floor(_value), _rate }; } /// @brief Returns a time with the smallest integer value not less than /// this value. RationalTime ceil() const { return RationalTime{ std::ceil(_value), _rate }; } /// @brief Returns a time with the nearest integer value to this value. RationalTime round() const { return RationalTime{ std::round(_value), _rate }; } /// @brief Compute the duration of samples from first to last (excluding /// last). /// /// Note that this is not the same as distance. /// /// For example, the duration of a clip from frame 10 to frame 15 is 5 /// frames. The result will be in the rate of start time. /// /// @param start_time The start time. /// @param end_time_exclusive The exclusive end time. static constexpr RationalTime duration_from_start_end_time( RationalTime start_time, RationalTime end_time_exclusive) noexcept { return start_time._rate == end_time_exclusive._rate ? RationalTime{ end_time_exclusive._value - start_time._value, start_time._rate } : RationalTime{ end_time_exclusive.value_rescaled_to( start_time) - start_time._value, start_time._rate }; } /// @brief Compute the duration of samples from first to last (including /// last). /// /// Note that this is not the same as distance. /// /// For example, the duration of a clip from frame 10 to frame 15 is 6 /// frames. Result will be in the rate of start time. /// /// @param start_time The start time. /// @param end_time_exclusive The inclusive end time. static constexpr RationalTime duration_from_start_end_time_inclusive( RationalTime start_time, RationalTime end_time_inclusive) noexcept { return start_time._rate == end_time_inclusive._rate ? RationalTime{ end_time_inclusive._value - start_time._value + 1, start_time._rate } : RationalTime{ end_time_inclusive.value_rescaled_to( start_time) - start_time._value + 1, start_time._rate }; } /// @brief Returns true is the rate is supported by SMPTE timecode. [[deprecated("Use is_smpte_timecode_rate() instead")]] static OPENTIME_API bool is_valid_timecode_rate(double rate); /// @brief Returns true is the rate is supported by SMPTE timecode. static OPENTIME_API bool is_smpte_timecode_rate(double rate); /// @brief Returns the SMPTE timecode rate nearest to the given rate. [[deprecated("Use nearest_smpte_timecode_rate() instead")]] static OPENTIME_API double nearest_valid_timecode_rate(double rate); /// @brief Returns the SMPTE timecode rate nearest to the given rate. static OPENTIME_API double nearest_smpte_timecode_rate(double rate); /// @brief Convert a frame number and rate into a time. static constexpr RationalTime from_frames(double frame, double rate) noexcept { return RationalTime{ double(int(frame)), rate }; } /// @brief Convert a value in seconds and rate into a time. static constexpr RationalTime from_seconds(double seconds, double rate) noexcept { return RationalTime{ seconds, 1 }.rescaled_to(rate); } /// @brief Convert a value in seconds into a time. static constexpr RationalTime from_seconds(double seconds) noexcept { return RationalTime{ seconds, 1 }; } /// @brief Convert a timecode string ("HH:MM:SS;FRAME") into a time. /// /// @param timecode The timecode string. /// @param rate The timecode rate. /// @param error_status Optional error status. static OPENTIME_API RationalTime from_timecode( std::string const& timecode, double rate, ErrorStatus* error_status = nullptr); /// @brief Parse a string in the form "hours:minutes:seconds". /// /// The string may have a leading negative sign. /// /// Seconds may have up to microsecond precision. /// /// @param time_string The time string. /// @param rate The time rate. /// @param error_status Optional error status. static OPENTIME_API RationalTime from_time_string( std::string const& time_string, double rate, ErrorStatus* error_status = nullptr); /// @brief Returns the frame number based on the current rate. constexpr int to_frames() const noexcept { return int(_value); } /// @brief Returns the frame number based on the given rate. constexpr int to_frames(double rate) const noexcept { return int(value_rescaled_to(rate)); } /// @brief Returns the value in seconds. constexpr double to_seconds() const noexcept { return value_rescaled_to(1); } /// @brief Convert to timecode (e.g., "HH:MM:SS;FRAME"). /// /// @param rate The timecode rate. /// @param drop_frame Whether to use drop frame timecode. /// @param error_status Optional error status. OPENTIME_API std::string to_timecode( double rate, IsDropFrameRate drop_frame, ErrorStatus* error_status = nullptr) const; /// @brief Convert to timecode (e.g., "HH:MM:SS;FRAME"). std::string to_timecode(ErrorStatus* error_status = nullptr) const { return to_timecode(_rate, IsDropFrameRate::InferFromRate, error_status); } /// @brief Convert to the nearest timecode (e.g., "HH:MM:SS;FRAME"). /// /// @param rate The timecode rate. /// @param drop_frame Whether to use drop frame timecode. /// @param error_status Optional error status. OPENTIME_API std::string to_nearest_timecode( double rate, IsDropFrameRate drop_frame, ErrorStatus* error_status = nullptr) const; /// @brief Convert to the nearest timecode (e.g., "HH:MM:SS;FRAME"). std::string to_nearest_timecode(ErrorStatus* error_status = nullptr) const { return to_nearest_timecode( _rate, IsDropFrameRate::InferFromRate, error_status); } /// @brief Return a string in the form "hours:minutes:seconds". /// /// Seconds may have up to microsecond precision. /// /// @return The time string, which may have a leading negative sign. OPENTIME_API std::string to_time_string() const; /// @brief Add a time to this time. constexpr RationalTime const& operator+=(RationalTime other) noexcept { if (_rate < other._rate) { _value = other._value + value_rescaled_to(other._rate); _rate = other._rate; } else { _value += other.value_rescaled_to(_rate); } return *this; } /// @brief Subtract a time from this time. constexpr RationalTime const& operator-=(RationalTime other) noexcept { if (_rate < other._rate) { _value = value_rescaled_to(other._rate) - other._value; _rate = other._rate; } else { _value -= other.value_rescaled_to(_rate); } return *this; } /// @brief Return the addition of two times. friend constexpr RationalTime operator+(RationalTime lhs, RationalTime rhs) noexcept { return (lhs._rate < rhs._rate) ? RationalTime{ lhs.value_rescaled_to(rhs._rate) + rhs._value, rhs._rate } : RationalTime{ rhs.value_rescaled_to(lhs._rate) + lhs._value, lhs._rate }; } /// @brief Return the subtraction of two times. friend constexpr RationalTime operator-(RationalTime lhs, RationalTime rhs) noexcept { return (lhs._rate < rhs._rate) ? RationalTime{ lhs.value_rescaled_to(rhs._rate) - rhs._value, rhs._rate } : RationalTime{ lhs._value - rhs.value_rescaled_to(lhs._rate), lhs._rate }; } /// @brief Return the negative of this time. friend constexpr RationalTime operator-(RationalTime lhs) noexcept { return RationalTime{ -lhs._value, lhs._rate }; } /// @brief Return whether a time is greater than another time. friend constexpr bool operator>(RationalTime lhs, RationalTime rhs) noexcept { return (lhs._value / lhs._rate) > (rhs._value / rhs._rate); } /// @brief Return whether a time is greater or equal to another time. friend constexpr bool operator>=(RationalTime lhs, RationalTime rhs) noexcept { return (lhs._value / lhs._rate) >= (rhs._value / rhs._rate); } /// @brief Return whether a time is less than another time. friend constexpr bool operator<(RationalTime lhs, RationalTime rhs) noexcept { return !(lhs >= rhs); } /// @brief Return whether a time is less than or equal to another time. friend constexpr bool operator<=(RationalTime lhs, RationalTime rhs) noexcept { return !(lhs > rhs); } /// @brief Return whether two times are equal. /// /// Note that the right hand side time is rescaled to the rate of the /// left hand side time. To compare two times without scaling, use /// strictly_equal(). /// /// @param lhs Left hand side time. /// @param lhs Right hand side time. friend constexpr bool operator==(RationalTime lhs, RationalTime rhs) noexcept { return lhs.value_rescaled_to(rhs._rate) == rhs._value; } /// @brief Return whether two times are not equal. /// /// Note that the right hand side time is rescaled to the rate of the /// left hand side time. To compare two times without scaling, use /// strictly_equal(). /// /// @param lhs Left hand side time. /// @param lhs Right hand side time. friend constexpr bool operator!=(RationalTime lhs, RationalTime rhs) noexcept { return !(lhs == rhs); } private: static RationalTime _invalid_time; static constexpr double _invalid_rate = -1; friend class TimeTransform; friend class TimeRange; double _value, _rate; /// @brief Returns the absolute value. /// /// \todo This function is used instead of "std::fabs()" so we can mark it as /// constexpr. We can remove this and replace it with the std version when we /// upgrade to C++23. Note that there are two copies of this function, in both /// RationalTime and TimeRange. static constexpr double fabs(double val) noexcept { union { double f; uint64_t i; } bits = { val }; bits.i &= std::numeric_limits::max() / 2; return bits.f; } }; }} // namespace opentime::OPENTIME_VERSION opentimelineio-0.18.1/src/opentime/export.h0000664000175000017500000000562415110656141016517 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once // For an explanation of how these export defines work, see: // https://github.com/PixarAnimationStudios/OpenUSD/blob/dev/pxr/base/arch/export.h #if defined(_WINDOWS) # if defined(__GNUC__) && __GNUC__ >= 4 || defined(__clang__) # define OPENTIMELINEIO_EXPORT __attribute__((dllexport)) # define OPENTIMELINEIO_IMPORT __attribute__((dllimport)) # define OPENTIMELINEIO_HIDDEN # define OPENTIMELINEIO_EXPORT_TYPE # define OPENTIMELINEIO_IMPORT_TYPE # else # define OPENTIMELINEIO_EXPORT __declspec(dllexport) # define OPENTIMELINEIO_IMPORT __declspec(dllimport) # define OPENTIMELINEIO_HIDDEN # define OPENTIMELINEIO_EXPORT_TYPE # define OPENTIMELINEIO_IMPORT_TYPE # endif #elif defined(__GNUC__) && __GNUC__ >= 4 || defined(__clang__) # define OPENTIMELINEIO_EXPORT __attribute__((visibility("default"))) # define OPENTIMELINEIO_IMPORT # define OPENTIMELINEIO_HIDDEN __attribute__((visibility("hidden"))) # if defined(__clang__) # define OPENTIMELINEIO_EXPORT_TYPE \ __attribute__((type_visibility("default"))) # else # define OPENTIMELINEIO_EXPORT_TYPE \ __attribute__((visibility("default"))) # endif # define OPENTIMELINEIO_IMPORT_TYPE #else # define OPENTIMELINEIO_EXPORT # define OPENTIMELINEIO_IMPORT # define OPENTIMELINEIO_HIDDEN # define OPENTIMELINEIO_EXPORT_TYPE # define OPENTIMELINEIO_IMPORT_TYPE #endif #define OPENTIMELINEIO_EXPORT_TEMPLATE(type, ...) #define OPENTIMELINEIO_IMPORT_TEMPLATE(type, ...) \ extern template type OPENTIMELINEIO_IMPORT __VA_ARGS__ #if defined(OPENTIME_STATIC) # define OPENTIME_API # define OPENTIME_API_TYPE # define OPENTIME_API_TEMPLATE_CLASS(...) # define OPENTIME_API_TEMPLATE_STRUCT(...) # define OPENTIME_LOCAL #else # if defined(OPENTIME_EXPORTS) # define OPENTIME_API OPENTIMELINEIO_EXPORT # define OPENTIME_API_TYPE OPENTIMELINEIO_EXPORT_TYPE # define OPENTIME_API_TEMPLATE_CLASS(...) \ OPENTIMELINEIO_EXPORT_TEMPLATE(class, __VA_ARGS__) # define OPENTIME_API_TEMPLATE_STRUCT(...) \ OPENTIMELINEIO_EXPORT_TEMPLATE(struct, __VA_ARGS__) # else # define OPENTIME_API OPENTIMELINEIO_IMPORT # define OPENTIME_API_TYPE OPENTIMELINEIO_IMPORT_TYPE # define OPENTIME_API_TEMPLATE_CLASS(...) \ OPENTIMELINEIO_IMPORT_TEMPLATE(class, __VA_ARGS__) # define OPENTIME_API_TEMPLATE_STRUCT(...) \ OPENTIMELINEIO_IMPORT_TEMPLATE(struct, __VA_ARGS__) # endif # define OPENTIME_LOCAL OPENTIMELINEIO_HIDDEN #endif opentimelineio-0.18.1/src/opentime/rationalTime.cpp0000664000175000017500000004350115110656141020155 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentime/rationalTime.h" #include "opentime/stringPrintf.h" #include #include #include #include #include namespace opentime { namespace OPENTIME_VERSION { RationalTime RationalTime::_invalid_time{ 0, RationalTime::_invalid_rate }; static constexpr std::array dropframe_timecode_rates{ { 30000.0 / 1001.0, 60000.0 / 1001.0, } }; // See the official source of these numbers here: // ST 12-1:2014 - SMPTE Standard - Time and Control Code // https://ieeexplore.ieee.org/document/7291029 // static constexpr std::array smpte_timecode_rates{ { 24000.0 / 1001.0, 24.0, 25.0, 30000.0 / 1001.0, 30.0, 48000.0 / 1001.0, 48.0, 50.0, 60000.0 / 1001.0, 60.0 } }; // deprecated in favor of `is_smpte_timecode_rate` bool RationalTime::is_valid_timecode_rate(double fps) { return is_smpte_timecode_rate(fps); } bool RationalTime::is_smpte_timecode_rate(double fps) { auto b = smpte_timecode_rates.begin(), e = smpte_timecode_rates.end(); return std::find(b, e, fps) != e; } // deprecated in favor of `is_smpte_timecode_rate` double RationalTime::nearest_valid_timecode_rate(double rate) { return nearest_smpte_timecode_rate(rate); } double RationalTime::nearest_smpte_timecode_rate(double rate) { double nearest_rate = 0; double min_diff = std::numeric_limits::max(); for (auto smpte_rate: smpte_timecode_rates) { if (smpte_rate == rate) { return rate; } auto diff = std::abs(rate - smpte_rate); if (diff >= min_diff) { continue; } min_diff = diff; nearest_rate = smpte_rate; } return nearest_rate; } static bool is_dropframe_rate(double rate) { auto b = dropframe_timecode_rates.begin(), e = dropframe_timecode_rates.end(); return std::find(b, e, rate) != e; } static bool parseFloat( char const* pCurr, char const* pEnd, bool allow_negative, double* result) { if (pCurr >= pEnd || !pCurr) { *result = 0.0; return false; } double ret = 0.0; double sign = 1.0; if (*pCurr == '+') { ++pCurr; } else if (*pCurr == '-') { if (!allow_negative) { *result = 0.0; return false; } sign = -1.0; ++pCurr; } // get integer part // // Note that uint64_t is used because overflow is well defined for // unsigned integers, but it is undefined behavior for signed integers, // and floating point values are couched in the specification with // the caveat that an implementation may be IEEE-754 compliant, or only // partially compliant. // uint64_t uintPart = 0; while (pCurr < pEnd) { char c = *pCurr; if (c < '0' || c > '9') { break; } uint64_t accumulated = uintPart * 10 + c - '0'; if (accumulated < uintPart) { // if there are too many digits, resulting in an overflow, fail *result = 0.0; return false; } uintPart = accumulated; ++pCurr; } ret = static_cast(uintPart); if (uintPart != static_cast(ret)) { // if the double cannot be casted precisely back to uint64_t, fail // A double has 15 digits of precision, but a uint64_t can encode more. *result = 0.0; return false; } // check for end of string or delimiter if (pCurr == pEnd || *pCurr == '\0') { *result = sign * ret; return true; } // if the next character is not a decimal point, the string is malformed. if (*pCurr != '.') { *result = 0.0; // zero consistent with earlier error condition return false; } ++pCurr; // skip decimal double position_scale = 0.1; while (pCurr < pEnd) { char c = *pCurr; if (c < '0' || c > '9') { break; } ret = ret + static_cast(c - '0') * position_scale; ++pCurr; position_scale *= 0.1; } *result = sign * ret; return true; } RationalTime RationalTime::from_timecode( std::string const& timecode, double rate, ErrorStatus* error_status) { if (!RationalTime::is_smpte_timecode_rate(rate)) { if (error_status) { *error_status = ErrorStatus{ ErrorStatus::INVALID_TIMECODE_RATE }; } return RationalTime::_invalid_time; } bool rate_is_dropframe = is_dropframe_rate(rate); if (timecode.find(';') != std::string::npos) { if (!rate_is_dropframe) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::INVALID_RATE_FOR_DROP_FRAME_TIMECODE, string_printf( "Timecode '%s' indicates drop frame rate due " "to the ';' frame divider. " "Passed in rate %g is not a valid drop frame rate.", timecode.c_str(), rate)); } return RationalTime::_invalid_time; } } else { rate_is_dropframe = false; } std::vector fields{ "", "", "", "" }; int hours, minutes, seconds, frames; try { // split the fields unsigned int last_pos = 0; for (unsigned int i = 0; i < 4; i++) { fields[i] = timecode.substr(last_pos, 2); last_pos = last_pos + 3; } hours = std::stoi(fields[0]); minutes = std::stoi(fields[1]); seconds = std::stoi(fields[2]); frames = std::stoi(fields[3]); } catch (std::exception const&) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::INVALID_TIMECODE_STRING, string_printf( "Input timecode '%s' is an invalid timecode", timecode.c_str())); } return RationalTime::_invalid_time; } const int nominal_fps = static_cast(std::ceil(rate)); if (frames >= nominal_fps) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::TIMECODE_RATE_MISMATCH, string_printf( "Frame rate mismatch. Timecode '%s' has " "frames beyond %d", timecode.c_str(), nominal_fps - 1)); } return RationalTime::_invalid_time; } int dropframes = 0; if (rate_is_dropframe) { if ((rate == 29.97) or (rate == 30000 / 1001.0)) { dropframes = 2; } else if ((rate == 59.94) or (rate == 60000 / 1001.0)) { dropframes = 4; } } // to use for drop frame compensation int total_minutes = hours * 60 + minutes; // convert to frames const int value = (((total_minutes * 60) + seconds) * nominal_fps + frames - (dropframes * (total_minutes - static_cast(std::floor(total_minutes / 10))))); return RationalTime{ double(value), rate }; } static void set_error( std::string const& time_string, ErrorStatus::Outcome code, ErrorStatus* err) { if (err) { *err = ErrorStatus( code, string_printf( "Error: '%s' - %s", time_string.c_str(), ErrorStatus::outcome_to_string(code).c_str())); } } RationalTime RationalTime::from_time_string( std::string const& time_string, double rate, ErrorStatus* error_status) { if (!RationalTime::is_smpte_timecode_rate(rate)) { set_error( time_string, ErrorStatus::INVALID_TIMECODE_RATE, error_status); return RationalTime::_invalid_time; } const char* start = time_string.data(); const char* end = start + time_string.length(); char* current = const_cast(end); char* parse_end = current; char* prev_parse_end = current; double power[3] = { 1.0, // seconds 60.0, // minutes 3600.0 // hours }; double accumulator = 0.0; int radix = 0; while (start <= current) { if (*current == ':') { parse_end = current + 1; char c = *parse_end; if (c != '\0' && c != ':') { if (c < '0' || c > '9') { set_error( time_string, ErrorStatus::INVALID_TIME_STRING, error_status); return RationalTime::_invalid_time; } double val = 0.0; if (!parseFloat(parse_end, prev_parse_end + 1, false, &val)) { set_error( time_string, ErrorStatus::INVALID_TIME_STRING, error_status); return RationalTime::_invalid_time; } prev_parse_end = nullptr; if (radix < 2 && val >= 60.0) { set_error( time_string, ErrorStatus::INVALID_TIME_STRING, error_status); return RationalTime::_invalid_time; } accumulator += val * power[radix]; } ++radix; if (radix == sizeof(power) / sizeof(power[0])) { set_error( time_string, ErrorStatus::INVALID_TIME_STRING, error_status); return RationalTime::_invalid_time; } } else if ( current < prev_parse_end && (*current < '0' || *current > '9') && *current != '.') { set_error( time_string, ErrorStatus::INVALID_TIME_STRING, error_status); return RationalTime::_invalid_time; } if (start == current) { if (prev_parse_end) { double val = 0.0; if (!parseFloat(start, prev_parse_end + 1, true, &val)) { set_error( time_string, ErrorStatus::INVALID_TIME_STRING, error_status); return RationalTime::_invalid_time; } accumulator += val * power[radix]; } break; } --current; if (!prev_parse_end) { prev_parse_end = current; } } return from_seconds(accumulator).rescaled_to(rate); } std::string RationalTime::to_timecode( double rate, IsDropFrameRate drop_frame, ErrorStatus* error_status) const { if (error_status) { *error_status = ErrorStatus(); } double frames_in_target_rate = this->value_rescaled_to(rate); if (frames_in_target_rate < 0) { if (error_status) { *error_status = ErrorStatus(ErrorStatus::NEGATIVE_VALUE); } return std::string(); } // It is common practice to use truncated or rounded values // like 29.97 instead of exact SMPTE rates like 30000/1001 // so as a convenience we will snap the rate to the nearest // SMPTE rate if it is close enough. double nearest_smpte_rate = nearest_smpte_timecode_rate(rate); if (abs(nearest_smpte_rate - rate) > 0.1) { if (error_status) { *error_status = ErrorStatus(ErrorStatus::INVALID_TIMECODE_RATE); } return std::string(); } // Let's assume this is the rate instead of the given rate. rate = nearest_smpte_rate; bool rate_is_dropframe = is_dropframe_rate(rate); if (drop_frame == IsDropFrameRate::ForceYes and not rate_is_dropframe) { if (error_status) { *error_status = ErrorStatus(ErrorStatus::INVALID_RATE_FOR_DROP_FRAME_TIMECODE); } return std::string(); } if (drop_frame != IsDropFrameRate::InferFromRate) { if (drop_frame == IsDropFrameRate::ForceYes) { rate_is_dropframe = true; } else { rate_is_dropframe = false; } } // extra math for dropframes stuff int dropframes = 0; char div = ':'; if (!rate_is_dropframe) { if (std::round(rate) == 24) { rate = 24.0; } } else { if (rate == 30000 / 1001.0) { dropframes = 2; } else if (rate == 60000 / 1001.0) { dropframes = 4; } div = ';'; } // Number of frames in an hour int frames_per_hour = static_cast(std::round(rate * 60 * 60)); // Number of frames in a day - timecode rolls over after 24 hours int frames_per_24_hours = frames_per_hour * 24; // Number of frames per ten minutes int frames_per_10_minutes = static_cast(std::round(rate * 60 * 10)); // Number of frames per minute is the round of the framerate * 60 minus // the number of dropped frames int frames_per_minute = static_cast((std::round(rate) * 60) - dropframes); // If the number of frames is more than 24 hours, roll over clock double value = std::fmod(frames_in_target_rate, frames_per_24_hours); if (rate_is_dropframe) { int ten_minute_chunks = static_cast(std::floor(value / frames_per_10_minutes)); int frames_over_ten_minutes = static_cast(std::fmod(value, frames_per_10_minutes)); if (frames_over_ten_minutes > dropframes) { value += (dropframes * 9 * ten_minute_chunks) + dropframes * std::floor( (frames_over_ten_minutes - dropframes) / frames_per_minute); } else { value += dropframes * 9 * ten_minute_chunks; } } int nominal_fps = static_cast(std::ceil(rate)); // compute the fields int frames = static_cast(std::fmod(value, nominal_fps)); int seconds_total = static_cast(std::floor(value / nominal_fps)); int seconds = static_cast(std::fmod(seconds_total, 60)); int minutes = static_cast(std::fmod(std::floor(seconds_total / 60), 60)); int hours = static_cast(std::floor(std::floor(seconds_total / 60) / 60)); return string_printf( "%02d:%02d:%02d%c%02d", hours, minutes, seconds, div, frames); } std::string RationalTime::to_nearest_timecode( double rate, IsDropFrameRate drop_frame, ErrorStatus* error_status) const { std::string result = to_timecode(rate, drop_frame, error_status); if (error_status) { *error_status = ErrorStatus(); double nearest_rate = nearest_smpte_timecode_rate(rate); return to_timecode(nearest_rate, drop_frame, error_status); } return result; } std::string RationalTime::to_time_string() const { double total_seconds = to_seconds(); bool is_negative = false; // We always want to compute with positive numbers to get the right string // result and return the string at the end with a '-'. This provides // compatibility with ffmpeg, which allows negative time strings. if (std::signbit(total_seconds)) { total_seconds = fabs(total_seconds); is_negative = true; } // @TODO: fun fact, this will print the wrong values for numbers at a // certain number of decimal places, if you just std::cerr << total_seconds // reformat in time string constexpr double time_units_per_minute = 60.0; constexpr double time_units_per_hour = time_units_per_minute * 60.0; constexpr double time_units_per_day = time_units_per_hour * 24.0; double hour_units = std::fmod((double) total_seconds, time_units_per_day); int hours = static_cast(std::floor(hour_units / time_units_per_hour)); double minute_units = std::fmod(hour_units, time_units_per_hour); int minutes = static_cast(std::floor(minute_units / time_units_per_minute)); double seconds = std::fmod(minute_units, time_units_per_minute); // split the seconds string apart double fractpart, intpart; fractpart = modf(seconds, &intpart); // clamp to 2 digits and zero-pad std::string seconds_str = string_printf("%02d", (int) intpart); // get the fractional component (with enough digits of resolution) std::string microseconds_str = string_printf("%.7g", fractpart); // trim leading 0 microseconds_str = microseconds_str.substr(1); // enforce the minimum string of '.0' if (microseconds_str.length() == 0) { microseconds_str = std::string(".0"); } else { // ...and the string size microseconds_str.resize(7, '\0'); } // if the initial time value was negative, return the string with a '-' // sign std::string sign = is_negative ? "-" : ""; return string_printf( // decimal should already be in the microseconds_str "%s%02d:%02d:%s%s", sign.c_str(), hours, minutes, seconds_str.c_str(), microseconds_str.c_str()); } }} // namespace opentime::OPENTIME_VERSION opentimelineio-0.18.1/src/opentime/CMakeLists.txt0000664000175000017500000000546415110656316017573 0ustar meme#------------------------------------------------------------------------------ # opentime/CMakeLists.txt set(OPENTIME_HEADER_FILES errorStatus.h export.h rationalTime.h stringPrintf.h timeRange.h timeTransform.h) add_library(opentime ${OTIO_SHARED_OR_STATIC_LIB} errorStatus.cpp rationalTime.cpp ${OPENTIME_HEADER_FILES}) add_library(OTIO::opentime ALIAS opentime) target_include_directories( opentime PRIVATE "${PROJECT_SOURCE_DIR}/src" "${CMAKE_CURRENT_BINARY_DIR}/.." ) set_target_properties(opentime PROPERTIES DEBUG_POSTFIX "${OTIO_DEBUG_POSTFIX}" LIBRARY_OUTPUT_NAME "opentime" POSITION_INDEPENDENT_CODE TRUE) if(OTIO_SHARED_LIBS) set_target_properties(opentime PROPERTIES SOVERSION ${OTIO_SOVERSION} VERSION ${OTIO_VERSION}) target_compile_definitions( opentime PUBLIC OPENTIME_EXPORTS) else() target_compile_definitions( opentime PUBLIC OPENTIME_STATIC) endif() if(APPLE) set_target_properties(opentime PROPERTIES INSTALL_NAME_DIR "@loader_path" MACOSX_RPATH ON) endif() target_compile_options(opentime PRIVATE $<$,$,$>: -Wall> $<$: /W4> $<$: /EHsc> ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h ) if(OTIO_CXX_INSTALL) install(FILES ${OPENTIME_HEADER_FILES} DESTINATION "${OTIO_RESOLVED_CXX_INSTALL_DIR}/include/opentime") install(TARGETS opentime EXPORT OpenTimeTargets INCLUDES DESTINATION "${OTIO_RESOLVED_CXX_INSTALL_DIR}/include" ARCHIVE DESTINATION "${OTIO_RESOLVED_CXX_DYLIB_INSTALL_DIR}" LIBRARY DESTINATION "${OTIO_RESOLVED_CXX_DYLIB_INSTALL_DIR}" RUNTIME DESTINATION "${OTIO_RESOLVED_CXX_DYLIB_INSTALL_DIR}") install(EXPORT OpenTimeTargets DESTINATION "${OTIO_RESOLVED_CXX_INSTALL_DIR}/share/opentime" NAMESPACE OTIO:: ) include(CMakePackageConfigHelpers) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/OpenTimeConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/OpenTimeConfig.cmake INSTALL_DESTINATION ${OTIO_RESOLVED_CXX_INSTALL_DIR}/share/opentime NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenTimeConfig.cmake DESTINATION ${OTIO_RESOLVED_CXX_INSTALL_DIR}/share/opentime ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/version.h DESTINATION "${OTIO_RESOLVED_CXX_INSTALL_DIR}/include/opentime" ) endif() opentimelineio-0.18.1/src/opentime/errorStatus.h0000664000175000017500000000352515110656141017531 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/export.h" #include "opentime/version.h" #include namespace opentime { namespace OPENTIME_VERSION { /// @brief This struct represents the return status of a function. struct OPENTIME_API_TYPE ErrorStatus { /// @brief This enumeration represents the possible outcomes. enum Outcome { OK = 0, INVALID_TIMECODE_RATE, INVALID_TIMECODE_STRING, INVALID_TIME_STRING, TIMECODE_RATE_MISMATCH, NEGATIVE_VALUE, INVALID_RATE_FOR_DROP_FRAME_TIMECODE, }; /// @brief Construct a new status with no error. ErrorStatus() : outcome{ OK } {} /// @brief Construct a new status with the given outcome. ErrorStatus(Outcome in_outcome) : outcome{ in_outcome } , details{ outcome_to_string(in_outcome) } {} /// @brief Construct a new status with the given outcome and details. ErrorStatus(Outcome in_outcome, std::string const& in_details) : outcome{ in_outcome } , details{ in_details } {} /// @brief The outcome of the function. Outcome outcome; /// @brief A human readable string that provides details about the outcome. std::string details; ///! @brief Return a human readable string for the given outcome. static OPENTIME_API std::string outcome_to_string(Outcome); }; ///! @brief Check whether the given ErrorStatus is an error. constexpr bool is_error(const ErrorStatus& es) noexcept { return ErrorStatus::Outcome::OK != es.outcome; } ///! @brief Check whether the given ErrorStatus is non-null and an error. constexpr bool is_error(const ErrorStatus* es) noexcept { return es && ErrorStatus::Outcome::OK != es->outcome; } }} // namespace opentime::OPENTIME_VERSION opentimelineio-0.18.1/src/opentimelineio/0000775000175000017500000000000015110656316016222 5ustar memeopentimelineio-0.18.1/src/opentimelineio/effect.h0000664000175000017500000000347215110656141017631 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/serializableObjectWithMetadata.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief An effect that can be applied to an item, such as an image or audio filter. class OTIO_API_TYPE Effect : public SerializableObjectWithMetadata { public: /// @brief This struct provides the Effect schema. struct Schema { static auto constexpr name = "Effect"; static int constexpr version = 1; }; using Parent = SerializableObjectWithMetadata; /// @brief Create a new effect. /// /// @param name The name of the effect object. /// @param name The name of the effect. /// @param metadata The metadata for the clip. /// @param enabled Whether the effect is enabled. Effect( std::string const& name = std::string(), std::string const& effect_name = std::string(), AnyDictionary const& metadata = AnyDictionary(), bool enabled = true); /// @brief Return the effect name. std::string effect_name() const noexcept { return _effect_name; } /// @brief Set the effect name. void set_effect_name(std::string const& effect_name) { _effect_name = effect_name; } /// @brief Return whether the effect is enabled. bool enabled() const { return _enabled; }; /// @brief Set whether the effect is enabled. void set_enabled(bool enabled) { _enabled = enabled; } protected: virtual ~Effect(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::string _effect_name; bool _enabled; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/typeRegistry.cpp0000664000175000017500000002717615110656141021451 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/typeRegistry.h" #include "anyDictionary.h" #include "opentimelineio/clip.h" #include "opentimelineio/composable.h" #include "opentimelineio/composition.h" #include "opentimelineio/effect.h" #include "opentimelineio/externalReference.h" #include "opentimelineio/freezeFrame.h" #include "opentimelineio/gap.h" #include "opentimelineio/generatorReference.h" #include "opentimelineio/imageSequenceReference.h" #include "opentimelineio/item.h" #include "opentimelineio/linearTimeWarp.h" #include "opentimelineio/marker.h" #include "opentimelineio/mediaReference.h" #include "opentimelineio/missingReference.h" #include "opentimelineio/serializableCollection.h" #include "opentimelineio/serializableObject.h" #include "opentimelineio/serializableObjectWithMetadata.h" #include "opentimelineio/stack.h" #include "opentimelineio/timeEffect.h" #include "opentimelineio/timeline.h" #include "opentimelineio/track.h" #include "opentimelineio/transition.h" #include "opentimelineio/unknownSchema.h" #include "stringUtils.h" #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { TypeRegistry& TypeRegistry::TypeRegistry::instance() { static TypeRegistry r; return r; } TypeRegistry::TypeRegistry() { register_type( UnknownSchema::Schema::name, UnknownSchema::Schema::version, &typeid(UnknownSchema), []() { fatal_error( "UnknownSchema should not be created from type registry"); return nullptr; }, "UnknownSchema"); register_type(); register_type(); register_type(); register_type(); register_type(); register_type(); register_type(); register_type_from_existing_type("Filler", 1, "Gap", nullptr); register_type(); register_type(); register_type(); register_type(); register_type(); register_type(); register_type(); register_type(); register_type(); register_type(); register_type_from_existing_type( "SerializeableCollection", 1, "SerializableCollection", nullptr); register_type(); register_type(); register_type(); register_type(); register_type_from_existing_type("Sequence", 1, "Track", nullptr); register_type(); /* * Upgrade functions: */ register_upgrade_function(Marker::Schema::name, 2, [](AnyDictionary* d) { (*d)["marked_range"] = (*d)["range"]; d->erase("range"); }); register_upgrade_function(Clip::Schema::name, 2, [](AnyDictionary* d) { auto media_ref = (*d)["media_reference"]; // The default ctor of Clip used to set media_reference to // MissingReference. To preserve the same behaviour, if we don't have a // valid MediaReference, do it here too. if (media_ref.type() != typeid(SerializableObject::Retainer<>)) { media_ref = SerializableObject::Retainer<>(new MissingReference); } (*d)["media_references"] = AnyDictionary{ { Clip::default_media_key, media_ref } }; (*d)["active_media_reference_key"] = std::string(Clip::default_media_key); d->erase("media_reference"); }); // 2->1 register_downgrade_function(Clip::Schema::name, 2, [](AnyDictionary* d) { AnyDictionary mrefs; std::string active_rkey = ""; if (d->get_if_set("media_references", &mrefs)) { if (d->get_if_set("active_media_reference_key", &active_rkey)) { AnyDictionary active_ref; if (mrefs.get_if_set(active_rkey, &active_ref)) { (*d)["media_reference"] = active_ref; } } } d->erase("media_references"); d->erase("active_media_reference_key"); }); } bool TypeRegistry::register_type( std::string const& schema_name, int schema_version, std::type_info const* type, std::function create, std::string const& class_name) { std::lock_guard lock(_registry_mutex); // auto existing_tr = _find_type_record(schema_name); // // // if the exact type record has already been added (happens in unit tests // // and re-setting manifest stuff) // if (existing_tr) // { // if ( // existing_tr->schema_name == schema_name // && existing_tr->schema_version == schema_version // && existing_tr->class_name == class_name // && ( // existing_tr->create.target() // == create.target() // ) // ) { // return true; // } // } if (!_find_type_record(schema_name)) { _TypeRecord* r = new _TypeRecord{ schema_name, schema_version, class_name, create }; _type_records[schema_name] = r; if (type) { _type_records_by_type_name[type->name()] = r; } return true; } return false; } bool TypeRegistry::register_type_from_existing_type( std::string const& schema_name, int /* schema_version */, std::string const& existing_schema_name, ErrorStatus* error_status) { std::lock_guard lock(_registry_mutex); if (auto r = _find_type_record(existing_schema_name)) { if (!_find_type_record(schema_name)) { _type_records[schema_name] = new _TypeRecord{ r->schema_name, r->schema_version, r->class_name, r->create }; return true; } if (error_status) { *error_status = ErrorStatus( ErrorStatus::SCHEMA_ALREADY_REGISTERED, schema_name); } return false; } if (error_status) { *error_status = ErrorStatus( ErrorStatus::SCHEMA_NOT_REGISTERED, string_printf( "cannot define schema %s in terms of %s; %s has not been registered", schema_name.c_str(), existing_schema_name.c_str(), existing_schema_name.c_str())); } return false; } bool TypeRegistry::register_upgrade_function( std::string const& schema_name, int version_to_upgrade_to, std::function upgrade_function) { std::lock_guard lock(_registry_mutex); if (auto r = _find_type_record(schema_name)) { auto result = r->upgrade_functions.insert( { version_to_upgrade_to, upgrade_function }); return result.second; } return false; } bool TypeRegistry::register_downgrade_function( std::string const& schema_name, int version_to_downgrade_from, std::function downgrade_function) { std::lock_guard lock(_registry_mutex); if (auto r = _find_type_record(schema_name)) { auto result = r->downgrade_functions.insert( { version_to_downgrade_from, downgrade_function }); return result.second; } return false; } SerializableObject* TypeRegistry::_instance_from_schema( std::string schema_name, int schema_version, AnyDictionary& dict, bool internal_read, ErrorStatus* error_status) { _TypeRecord const* type_record; bool create_unknown = false; { std::lock_guard lock(_registry_mutex); type_record = _find_type_record(schema_name); if (!type_record) { create_unknown = true; type_record = _find_type_record(UnknownSchema::Schema::name); assert(type_record); } } SerializableObject* so; if (create_unknown) { so = new UnknownSchema(schema_name, schema_version); schema_name = type_record->schema_name; schema_version = type_record->schema_version; } else { so = type_record->create_object(); } if (schema_version > type_record->schema_version) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::SCHEMA_VERSION_UNSUPPORTED, string_printf( "Schema %s has highest version %d, but the requested " "schema version %d is even greater.", schema_name.c_str(), type_record->schema_version, schema_version)); } return nullptr; } else if (schema_version < type_record->schema_version) { for (const auto& e: type_record->upgrade_functions) { if (schema_version <= e.first && e.first <= type_record->schema_version) { e.second(&dict); } } } if (internal_read) { return so; } auto error_function = [error_status](ErrorStatus const& status) { if (error_status) { *error_status = status; } }; // g++ compiler bug if we pass error_function directly into Reader std::function ef = error_function; SerializableObject::Reader r(dict, ef, nullptr); return so->read_from(r) ? so : nullptr; } TypeRegistry::_TypeRecord* TypeRegistry::_lookup_type_record(std::string const& schema_name) { std::lock_guard lock(_registry_mutex); auto e = _type_records.find(schema_name); return e != _type_records.end() ? e->second : nullptr; } TypeRegistry::_TypeRecord* TypeRegistry::_lookup_type_record(std::type_info const& type) { std::lock_guard lock(_registry_mutex); auto e = _type_records_by_type_name.find(type.name()); return e != _type_records_by_type_name.end() ? e->second : nullptr; } SerializableObject* TypeRegistry::_TypeRecord::create_object() const { SerializableObject* so = create(); so->_set_type_record(this); return so; } bool TypeRegistry::set_type_record( SerializableObject* so, std::string const& schema_name, ErrorStatus* error_status) { auto r = _lookup_type_record(schema_name); if (r) { so->_set_type_record(r); return true; } if (error_status) { *error_status = ErrorStatus( ErrorStatus::SCHEMA_NOT_REGISTERED, string_printf( "Cannot set type record on instance of type %s: schema %s unregistered", type_name_for_error_message(so).c_str(), schema_name.c_str())); } return false; } void TypeRegistry::type_version_map(schema_version_map& result) { std::lock_guard lock(_registry_mutex); for (const auto& pair: _type_records) { const auto record_ptr = pair.second; result[record_ptr->schema_name] = record_ptr->schema_version; } } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/clip.h0000664000175000017500000000767115110656141017331 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/item.h" #include "opentimelineio/mediaReference.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief A clip is a segment of editable media (usually audio or video). /// /// Contains a MediaReference and a trim on that media reference. class OTIO_API_TYPE Clip : public Item { public: /// @brief The default media within a clip. static char constexpr default_media_key[] = "DEFAULT_MEDIA"; /// @brief This struct provides the Clip schema. struct Schema { static auto constexpr name = "Clip"; static int constexpr version = 2; }; using Parent = Item; /// @brief Create a new clip. /// /// @param name The name of the clip. /// @param media_reference The media reference for the clip. Note /// that the Clip keeps a Retainer to the media reference. /// @param source_range The source range of the clip. /// @param metadata The metadata for the clip. /// @param effects The list of effects for the clip. Note that the /// the clip keeps a retainer to each effect. /// @param markers The list of markers for the clip. Note that the /// the clip keeps a retainer to each marker. /// @param active_media_reference_key The active media reference. OTIO_API Clip( std::string const& name = std::string(), MediaReference* media_reference = nullptr, std::optional const& source_range = std::nullopt, AnyDictionary const& metadata = AnyDictionary(), std::vector const& effects = std::vector(), std::vector const& markers = std::vector(), std::string const& active_media_reference_key = default_media_key, std::optional const& color = std::nullopt); /// @name Media References ///@{ /// @brief Set the media reference. Note that the Clip keeps a Retainer to /// the media reference. OTIO_API void set_media_reference(MediaReference* media_reference); /// @brief Return the media reference. OTIO_API MediaReference* media_reference() const noexcept; using MediaReferences = std::map; /// @brief Return the list of media references. OTIO_API MediaReferences media_references() const noexcept; /// @brief Set the list of media references. Note that the Clip keeps a /// Retainer to each media reference. OTIO_API void set_media_references( MediaReferences const& media_references, std::string const& new_active_key, ErrorStatus* error_status = nullptr) noexcept; /// @brief Return the active media reference. OTIO_API std::string active_media_reference_key() const noexcept; /// @brief Set the active media reference. OTIO_API void set_active_media_reference_key( std::string const& new_active_key, ErrorStatus* error_status = nullptr) noexcept; ///@} OTIO_API TimeRange available_range(ErrorStatus* error_status = nullptr) const override; OTIO_API std::optional available_image_bounds(ErrorStatus* error_status = nullptr) const override; protected: virtual ~Clip(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: template bool check_for_valid_media_reference_key( std::string const& caller, std::string const& key, MediaRefMap const& media_references, ErrorStatus* error_status); private: std::map> _media_references; std::string _active_media_reference_key; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/freezeFrame.cpp0000664000175000017500000000064515110656141021162 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/freezeFrame.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { FreezeFrame::FreezeFrame(std::string const& name, AnyDictionary const& metadata) : Parent(name, "FreezeFrame", 0.0, metadata) {} FreezeFrame::~FreezeFrame() {} }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/serializableCollection.cpp0000664000175000017500000000530515110656141023407 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/serializableCollection.h" #include "opentimelineio/clip.h" #include "opentimelineio/vectorIndexing.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { SerializableCollection::SerializableCollection( std::string const& name, std::vector children, AnyDictionary const& metadata) : Parent(name, metadata) , _children(children.begin(), children.end()) {} SerializableCollection::~SerializableCollection() {} void SerializableCollection::clear_children() { _children.clear(); } void SerializableCollection::set_children( std::vector const& children) { _children = decltype(_children)(children.begin(), children.end()); } void SerializableCollection::insert_child(int index, SerializableObject* child) { index = adjusted_vector_index(index, _children); if (index >= int(_children.size())) { _children.emplace_back(child); } else { _children.insert(_children.begin() + std::max(index, 0), child); } } bool SerializableCollection::set_child( int index, SerializableObject* child, ErrorStatus* error_status) { index = adjusted_vector_index(index, _children); if (index < 0 || index >= int(_children.size())) { if (error_status) { *error_status = ErrorStatus::ILLEGAL_INDEX; } return false; } _children[index] = child; return true; } bool SerializableCollection::remove_child(int index, ErrorStatus* error_status) { if (_children.empty()) { if (error_status) { *error_status = ErrorStatus::ILLEGAL_INDEX; } return false; } index = adjusted_vector_index(index, _children); if (size_t(index) >= _children.size()) { _children.pop_back(); } else { _children.erase(_children.begin() + std::max(index, 0)); } return true; } bool SerializableCollection::read_from(Reader& reader) { return reader.read("children", &_children) && Parent::read_from(reader); } void SerializableCollection::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("children", _children); } std::vector> SerializableCollection::find_clips( ErrorStatus* error_status, std::optional const& search_range, bool shallow_search) const { return find_children(error_status, search_range, shallow_search); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/trackAlgorithm.h0000664000175000017500000000074415110656141021347 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/track.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Trim the track to the given range. OTIO_API Track* track_trimmed_to_range( Track* in_track, TimeRange trim_range, ErrorStatus* error_status = nullptr); }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/serialization.cpp0000664000175000017500000011543615110656141021611 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/serialization.h" #include "errorStatus.h" #include "opentimelineio/anyDictionary.h" #include "opentimelineio/color.h" #include "opentimelineio/serializableObject.h" #include "opentimelineio/unknownSchema.h" #include "stringUtils.h" #include #include #define RAPIDJSON_NAMESPACE OTIO_rapidjson #include #include #include #include #include #if defined(_WINDOWS) # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif // WIN32_LEAN_AND_MEAN # ifndef NOMINMAX # define NOMINMAX # endif // NOMINMAX # include #endif namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /** * Base class for encoders. Since rapidjson is templated (no virtual functions) * we need to do our dynamically classed hierarchy to abstract away which writer * we are using. This also lets us create the CloningEncoder, which is what * we use not to serialize a class, but to copy it in memory, thereby cloning * an instance of a SerializableObject. * * This hierarchy is not visible outside this library, so we're not very concerned * about access control within this class. */ class Encoder { public: virtual ~Encoder() {} bool has_errored(ErrorStatus* error_status) { if (error_status) { *error_status = _error_status; } return is_error(_error_status); } bool has_errored() { return is_error(_error_status); } virtual bool encoding_to_anydict() { return false; } virtual void start_object() = 0; virtual void end_object() = 0; virtual void start_array(size_t) = 0; virtual void end_array() = 0; virtual void write_key(std::string const& key) = 0; virtual void write_null_value() = 0; virtual void write_value(bool value) = 0; virtual void write_value(int value) = 0; virtual void write_value(int64_t value) = 0; virtual void write_value(uint64_t value) = 0; virtual void write_value(double value) = 0; virtual void write_value(std::string const& value) = 0; virtual void write_value(class RationalTime const& value) = 0; virtual void write_value(class TimeRange const& value) = 0; virtual void write_value(class TimeTransform const& value) = 0; virtual void write_value(class Color const& value) = 0; virtual void write_value(struct SerializableObject::ReferenceId) = 0; virtual void write_value(IMATH_NAMESPACE::Box2d const&) = 0; virtual void write_value(IMATH_NAMESPACE::V2d const&) = 0; protected: void _error(ErrorStatus const& error_status) { _error_status = error_status; } private: friend class SerializableObject; ErrorStatus _error_status; }; /** * This encoder builds up a AnyDictionary as its method of "encoding". * The dictionary is than handed off to a CloningDecoder, to complete * copying of a SerializableObject instance. */ class CloningEncoder : public Encoder { public: enum class ResultObjectPolicy { CloneBackToSerializableObject = 0, MathTypesConcreteAnyDictionaryResult, OnlyAnyDictionary, }; CloningEncoder( CloningEncoder::ResultObjectPolicy result_object_policy, const schema_version_map* schema_version_targets = nullptr) : _result_object_policy(result_object_policy) , _downgrade_version_manifest(schema_version_targets) { using namespace std::placeholders; _error_function = std::bind(&CloningEncoder::_error, this, _1); } virtual ~CloningEncoder() {} virtual bool encoding_to_anydict() override { return (_result_object_policy == ResultObjectPolicy::OnlyAnyDictionary); } void write_key(std::string const& key) override { if (has_errored()) { return; } if (_stack.empty() || !_stack.back().is_dict) { _internal_error( "Encoder::write_key called while not decoding an object"); return; } _stack.back().cur_key = key; } void _replace_back(AnyDictionary&& a) { if (has_errored()) { return; } if (_stack.size() == 1) { std::any newstack(std::move(a)); _root.swap(newstack); } else { _stack.pop_back(); auto& top = _stack.back(); if (top.is_dict) { top.dict.emplace(top.cur_key, a); } else { std::any newstack(std::move(a)); top.array.emplace_back(newstack); } } } void _store(std::any&& a) { if (has_errored()) { return; } if (_stack.empty()) { _root.swap(a); } else { auto& top = _stack.back(); if (top.is_dict) { top.dict.emplace(_stack.back().cur_key, a); } else { top.array.emplace_back(a); } } } void write_null_value() override { _store(std::any()); } void write_value(bool value) override { _store(std::any(value)); } void write_value(int value) override { _store(std::any(value)); } void write_value(int64_t value) override { _store(std::any(value)); } void write_value(uint64_t value) override { _store(std::any(value)); } void write_value(std::string const& value) override { _store(std::any(value)); } void write_value(double value) override { _store(std::any(value)); } void write_value(RationalTime const& value) override { if (_result_object_policy == ResultObjectPolicy::OnlyAnyDictionary) { AnyDictionary result = { { "OTIO_SCHEMA", "RationalTime.1" }, { "value", value.value() }, { "rate", value.rate() }, }; _store(std::any(std::move(result))); } else { _store(std::any(value)); } } void write_value(TimeRange const& value) override { if (_result_object_policy == ResultObjectPolicy::OnlyAnyDictionary) { AnyDictionary result = { { "OTIO_SCHEMA", "TimeRange.1" }, { "duration", value.duration() }, { "start_time", value.start_time() }, }; _store(std::any(std::move(result))); } else { _store(std::any(value)); } } void write_value(TimeTransform const& value) override { if (_result_object_policy == ResultObjectPolicy::OnlyAnyDictionary) { AnyDictionary result{ { "OTIO_SCHEMA", "TimeTransform.1" }, { "offset", value.offset() }, { "rate", value.rate() }, { "scale", value.scale() }, }; _store(std::any(std::move(result))); } else { _store(std::any(value)); } } void write_value(Color const& value) override { if (_result_object_policy == ResultObjectPolicy::OnlyAnyDictionary) { AnyDictionary result{ { "OTIO_SCHEMA", "Color.1" }, { "r", value.r() }, { "g", value.g() }, { "b", value.b() }, { "a", value.a() }, { "name", value.name() }, }; _store(std::any(std::move(result))); } else { _store(std::any(value)); } } void write_value(SerializableObject::ReferenceId value) override { if (_result_object_policy == ResultObjectPolicy::OnlyAnyDictionary) { AnyDictionary result{ { "OTIO_SCHEMA", "SerializableObjectRef.1" }, { "id", value.id.c_str() }, }; _store(std::any(std::move(result))); } else { _store(std::any(value)); } _store(std::any(value)); } void write_value(IMATH_NAMESPACE::V2d const& value) override { if (_result_object_policy == ResultObjectPolicy::OnlyAnyDictionary) { AnyDictionary result{ { "OTIO_SCHEMA", "V2d.1" }, { "x", value.x }, { "y", value.y }, }; _store(std::any(std::move(result))); } else { _store(std::any(value)); } } void write_value(IMATH_NAMESPACE::Box2d const& value) override { if (_result_object_policy == ResultObjectPolicy::OnlyAnyDictionary) { AnyDictionary result{ { "OTIO_SCHEMA", "Box2d.1" }, { "min", value.min }, { "max", value.max }, }; _store(std::any(std::move(result))); } else { _store(std::any(value)); } } // @} void start_array(size_t /* n */) override { if (has_errored()) { return; } _stack.emplace_back(_DictOrArray{ false /* is_dict*/ }); } void start_object() override { if (has_errored()) { return; } _stack.emplace_back(_DictOrArray{ true /* is_dict*/ }); } void end_array() override { if (has_errored()) { return; } if (_stack.empty()) { _internal_error( "Encoder::end_array() called without matching start_array()"); } else { auto& top = _stack.back(); if (top.is_dict) { _internal_error( "Encoder::end_array() called without matching start_array()"); _stack.pop_back(); } else { AnyVector va; va.swap(top.array); _stack.pop_back(); _store(std::any(std::move(va))); } } } void end_object() override { if (has_errored()) { return; } if (_stack.empty()) { _internal_error( "Encoder::end_object() called without matching start_object()"); return; } auto& top = _stack.back(); if (!top.is_dict) { _internal_error( "Encoder::end_object() called without matching start_object()"); _stack.pop_back(); return; } /* * Convert back to SerializableObject* right here. */ if (_result_object_policy == ResultObjectPolicy::CloneBackToSerializableObject) { SerializableObject::Reader reader( top.dict, _error_function, nullptr); _stack.pop_back(); _store(reader._decode(_resolver)); return; } AnyDictionary m; m.swap(top.dict); if ((_downgrade_version_manifest != nullptr) && (!_downgrade_version_manifest->empty())) { _downgrade_dictionary(m); } _replace_back(std::move(m)); } private: std::any _root; SerializableObject::Reader::_Resolver _resolver; std::function _error_function; struct _DictOrArray { _DictOrArray(bool is_dict) { this->is_dict = is_dict; } bool is_dict; AnyDictionary dict; AnyVector array; std::string cur_key; }; void _internal_error(std::string const& err_msg) { _error(ErrorStatus(ErrorStatus::INTERNAL_ERROR, err_msg)); } friend class SerializableObject; std::vector<_DictOrArray> _stack; ResultObjectPolicy _result_object_policy; const schema_version_map* _downgrade_version_manifest = nullptr; void _downgrade_dictionary(AnyDictionary& m) { std::string schema_string = ""; if (!m.get_if_set("OTIO_SCHEMA", &schema_string)) { return; } const auto sep = schema_string.rfind('.'); const std::string& schema_name = schema_string.substr(0, sep); const auto dg_version_it = _downgrade_version_manifest->find(schema_name); if (dg_version_it == _downgrade_version_manifest->end()) { return; } const std::string& schema_vers = schema_string.substr(sep + 1); int current_version = -1; if (!schema_vers.empty()) { current_version = std::stoi(schema_vers); } // @TODO: is 0 a legitimate schema version? if (current_version < 0) { _internal_error(string_printf( "Could not parse version number from Schema" " string: %s", schema_string.c_str())); return; } const int target_version = static_cast(dg_version_it->second); const auto& type_rec = (TypeRegistry::instance()._find_type_record(schema_name)); while (current_version > target_version) { const auto& next_dg_fn = (type_rec->downgrade_functions.find(current_version)); if (next_dg_fn == type_rec->downgrade_functions.end()) { _internal_error(string_printf( "No downgrader function available for " "going from version %d to version %d.", current_version, target_version)); return; } // apply it next_dg_fn->second(&m); current_version--; } m["OTIO_SCHEMA"] = schema_name + "." + std::to_string(current_version); } }; template class JSONEncoder : public Encoder { public: JSONEncoder(RapidJSONWriterType& writer) : _writer(writer) {} virtual ~JSONEncoder() {} void write_key(std::string const& key) { _writer.Key(key.c_str()); } void write_null_value() { _writer.Null(); } void write_value(bool value) { _writer.Bool(value); } void write_value(int value) { _writer.Int(value); } void write_value(int64_t value) { _writer.Int64(value); } void write_value(uint64_t value) { _writer.Uint64(value); } void write_value(std::string const& value) { _writer.String(value.c_str()); } void write_value(double value) { _writer.Double(value); } void write_value(RationalTime const& value) { _writer.StartObject(); _writer.Key("OTIO_SCHEMA"); _writer.String("RationalTime.1"); _writer.Key("rate"); _writer.Double(value.rate()); _writer.Key("value"); _writer.Double(value.value()); _writer.EndObject(); } void write_value(TimeRange const& value) { _writer.StartObject(); _writer.Key("OTIO_SCHEMA"); _writer.String("TimeRange.1"); _writer.Key("duration"); write_value(value.duration()); _writer.Key("start_time"); write_value(value.start_time()); _writer.EndObject(); } void write_value(TimeTransform const& value) { _writer.StartObject(); _writer.Key("OTIO_SCHEMA"); _writer.String("TimeTransform.1"); _writer.Key("offset"); write_value(value.offset()); _writer.Key("rate"); _writer.Double(value.rate()); _writer.Key("scale"); _writer.Double(value.scale()); _writer.EndObject(); } void write_value(Color const& value) { _writer.StartObject(); _writer.Key("OTIO_SCHEMA"); _writer.String("Color.1"); _writer.Key("r"); _writer.Double(value.r()); _writer.Key("g"); _writer.Double(value.g()); _writer.Key("b"); _writer.Double(value.b()); _writer.Key("a"); _writer.Double(value.a()); _writer.Key("name"); _writer.String(value.name().c_str()); _writer.EndObject(); } void write_value(SerializableObject::ReferenceId value) { _writer.StartObject(); _writer.Key("OTIO_SCHEMA"); _writer.String("SerializableObjectRef.1"); _writer.Key("id"); _writer.String(value.id.c_str()); _writer.EndObject(); } void write_value(IMATH_NAMESPACE::V2d const& value) { _writer.StartObject(); _writer.Key("OTIO_SCHEMA"); _writer.String("V2d.1"); _writer.Key("x"); _writer.Double(value.x); _writer.Key("y"); _writer.Double(value.y); _writer.EndObject(); } void write_value(IMATH_NAMESPACE::Box2d const& value) { _writer.StartObject(); _writer.Key("OTIO_SCHEMA"); _writer.String("Box2d.1"); _writer.Key("min"); write_value(value.min); _writer.Key("max"); write_value(value.max); _writer.EndObject(); } void start_array(size_t) { _writer.StartArray(); } void start_object() { _writer.StartObject(); } void end_array() { _writer.EndArray(); } void end_object() { _writer.EndObject(); } private: RapidJSONWriterType& _writer; }; template bool _simple_any_comparison(std::any const& lhs, std::any const& rhs) { return lhs.type() == typeid(T) && rhs.type() == typeid(T) && std::any_cast(lhs) == std::any_cast(rhs); } template <> bool _simple_any_comparison(std::any const& lhs, std::any const& rhs) { return lhs.type() == typeid(void) && rhs.type() == typeid(void); } template <> bool _simple_any_comparison(std::any const& lhs, std::any const& rhs) { return lhs.type() == typeid(char const*) && rhs.type() == typeid(char const*) && !strcmp( std::any_cast(lhs), std::any_cast(rhs)); } void SerializableObject::Writer::_build_dispatch_tables() { /* * These are basically atomic writes to the encoder: */ auto& wt = _write_dispatch_table; wt[&typeid(void)] = [this](std::any const&) { _encoder.write_null_value(); }; wt[&typeid(bool)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(int64_t)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(double)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(std::string)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(char const*)] = [this](std::any const& value) { _encoder.write_value(std::string(std::any_cast(value))); }; wt[&typeid(RationalTime)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(TimeRange)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(TimeTransform)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(Color)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(IMATH_NAMESPACE::V2d)] = [this](std::any const& value) { _encoder.write_value(std::any_cast(value)); }; wt[&typeid(IMATH_NAMESPACE::Box2d)] = [this](std::any const& value) { _encoder.write_value( std::any_cast(value)); }; /* * These next recurse back through the Writer itself: */ wt[&typeid(SerializableObject::Retainer<>)] = [this](std::any const& value) { this->write( _no_key, std::any_cast>(value)); }; wt[&typeid(AnyDictionary)] = [this](std::any const& value) { this->write(_no_key, std::any_cast(value)); }; wt[&typeid(AnyVector)] = [this](std::any const& value) { this->write(_no_key, std::any_cast(value)); }; /* * Install a backup table, using the actual type name as a key. * This is to deal with type aliasing across compilation units. */ for (const auto& e: wt) { _write_dispatch_table_by_name[e.first->name()] = e.second; } auto& et = _equality_dispatch_table; et[&typeid(void)] = &_simple_any_comparison; et[&typeid(bool)] = &_simple_any_comparison; et[&typeid(int64_t)] = &_simple_any_comparison; et[&typeid(double)] = &_simple_any_comparison; et[&typeid(std::string)] = &_simple_any_comparison; et[&typeid(char const*)] = &_simple_any_comparison; et[&typeid(RationalTime)] = &_simple_any_comparison; et[&typeid(TimeRange)] = &_simple_any_comparison; et[&typeid(TimeTransform)] = &_simple_any_comparison; et[&typeid(Color)] = &_simple_any_comparison; et[&typeid(SerializableObject::ReferenceId)] = &_simple_any_comparison; et[&typeid(IMATH_NAMESPACE::V2d)] = &_simple_any_comparison; et[&typeid(IMATH_NAMESPACE::Box2d)] = &_simple_any_comparison; /* * These next recurse back through the Writer itself: */ et[&typeid(AnyDictionary)] = [this](std::any const& lhs, std::any const& rhs) { return _any_dict_equals(lhs, rhs); }; et[&typeid(AnyVector)] = [this](std::any const& lhs, std::any const& rhs) { return _any_array_equals(lhs, rhs); }; } bool SerializableObject::Writer::_any_dict_equals( std::any const& lhs, std::any const& rhs) { if (lhs.type() != typeid(AnyDictionary) || rhs.type() != typeid(AnyDictionary)) { return false; } AnyDictionary const& ld = std::any_cast(lhs); AnyDictionary const& rd = std::any_cast(rhs); auto r_it = rd.begin(); for (const auto& l_it: ld) { if (r_it == rd.end()) { return false; } if (l_it.first != r_it->first || !_any_equals(l_it.second, r_it->second)) { return false; } ++r_it; } return r_it == rd.end(); } bool SerializableObject::Writer::_any_array_equals( std::any const& lhs, std::any const& rhs) { if (lhs.type() != typeid(AnyVector) || rhs.type() != typeid(AnyVector)) { return false; } AnyVector const& lv = std::any_cast(lhs); AnyVector const& rv = std::any_cast(rhs); if (lv.size() != rv.size()) { return false; } for (size_t i = 0; i < lv.size(); i++) { if (!_any_equals(lv[i], rv[i])) { return false; } } return true; } bool SerializableObject::Writer::_any_equals( std::any const& lhs, std::any const& rhs) { auto e = _equality_dispatch_table.find(&lhs.type()); return (e != _equality_dispatch_table.end()) && e->second(lhs, rhs); } bool SerializableObject::Writer::write_root( std::any const& value, Encoder& encoder, const schema_version_map* schema_version_targets, ErrorStatus* error_status) { Writer w(encoder, schema_version_targets); w.write(w._no_key, value); return !encoder.has_errored(error_status); } void SerializableObject::Writer::_encoder_write_key(std::string const& key) { if (&key != &_no_key) { _encoder.write_key(key); } } void SerializableObject::Writer::write(std::string const& key, bool value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write(std::string const& key, int64_t value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write(std::string const& key, double value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write( std::string const& key, std::string const& value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write(std::string const& key, RationalTime value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write(std::string const& key, TimeRange value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write( std::string const& key, std::optional value) { _encoder_write_key(key); value ? _encoder.write_value(*value) : _encoder.write_null_value(); } void SerializableObject::Writer::write( std::string const& key, std::optional value) { _encoder_write_key(key); value ? _encoder.write_value(*value) : _encoder.write_null_value(); } void SerializableObject::Writer::write( std::string const& key, std::optional value) { _encoder_write_key(key); value ? _encoder.write_value(*value) : _encoder.write_null_value(); } void SerializableObject::Writer::write(std::string const& key, TimeTransform value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write( std::string const& key, std::optional value) { _encoder_write_key(key); value ? _encoder.write_value(*value) : _encoder.write_null_value(); } void SerializableObject::Writer::write( std::string const& key, SerializableObject const* value) { _encoder_write_key(key); if (!value) { _encoder.write_null_value(); return; } auto e = _id_for_object.find(value); if (e != _id_for_object.end()) { #ifdef OTIO_INSTANCING_SUPPORT /* * We've already written this value. */ _encoder.write_value(SerializableObject::ReferenceId{ e->second }); #else /* * We're encountering the same object while it is still * in the map, meaning we're in the middle of writing it out. * That's a cycle, as opposed to mere instancing, which we * allow so as not to break old allowed behavior. */ std::string s = string_printf( "cyclically encountered object has schema %s", value->schema_name().c_str()); _encoder._error(ErrorStatus(ErrorStatus::OBJECT_CYCLE, s)); #endif return; } std::string const& schema_type_name = value->_schema_name_for_reference(); if (_next_id_for_type.find(schema_type_name) == _next_id_for_type.end()) { _next_id_for_type[schema_type_name] = 0; } std::string next_id = schema_type_name + "-" + std::to_string(++_next_id_for_type[schema_type_name]); _id_for_object[value] = next_id; // detect if downgrading needs to happen const std::string& schema_name = value->schema_name(); int schema_version = value->schema_version(); std::any downgraded = {}; // if there is a manifest & the encoder is not converting to AnyDictionary if ((_downgrade_version_manifest != nullptr) && (!_downgrade_version_manifest->empty()) && (!_encoder.encoding_to_anydict())) { const auto& target_version_it = _downgrade_version_manifest->find(schema_name); // ...and if that downgrade manifest specifies a target version for // this schema if (target_version_it != _downgrade_version_manifest->end()) { const int target_version = static_cast(target_version_it->second); // and the current_version is greater than the target version if (schema_version > target_version) { if (_child_writer == nullptr) { _child_cloning_encoder = new CloningEncoder( CloningEncoder::ResultObjectPolicy::OnlyAnyDictionary, _downgrade_version_manifest); _child_writer = new Writer(*_child_cloning_encoder, {}); } else { _child_cloning_encoder->_stack.clear(); } _child_writer->write(_child_writer->_no_key, value); if (_child_cloning_encoder->has_errored( &_encoder._error_status)) { return; } downgraded.swap(_child_cloning_encoder->_root); schema_version = target_version; } } } std::string schema_str = ""; // if its an unknown schema, the schema name is computed from the // _original_schema_name and _original_schema_version attributes if (UnknownSchema const* us = dynamic_cast(value)) { schema_str = (us->_original_schema_name + "." + std::to_string(us->_original_schema_version)); } else { // otherwise, use the schema_name and schema_version attributes schema_str = schema_name + "." + std::to_string(schema_version); } _encoder.start_object(); #ifdef OTIO_INSTANCING_SUPPORT _encoder.write_key("OTIO_REF_ID"); _encoder.write_value(next_id); #endif // write the contents of the object to the encoder, either the downgraded // anydictionary or the SerializableObject if (downgraded.has_value()) { for (const auto& kv: std::any_cast(downgraded)) { this->write(kv.first, kv.second); } } else { _encoder.write_key("OTIO_SCHEMA"); _encoder.write_value(schema_str); value->write_to(*this); } _encoder.end_object(); #ifndef OTIO_INSTANCING_SUPPORT auto valueEntry = _id_for_object.find(value); if (valueEntry != _id_for_object.end()) { _id_for_object.erase(valueEntry); } #endif } void SerializableObject::Writer::write( std::string const& key, IMATH_NAMESPACE::V2d value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write( std::string const& key, IMATH_NAMESPACE::Box2d value) { _encoder_write_key(key); _encoder.write_value(value); } void SerializableObject::Writer::write( std::string const& key, AnyDictionary const& value) { _encoder_write_key(key); _encoder.start_object(); for (const auto& e: value) { write(e.first, e.second); } _encoder.end_object(); } void SerializableObject::Writer::write( std::string const& key, AnyVector const& value) { _encoder_write_key(key); _encoder.start_array(value.size()); for (const auto& e: value) { write(_no_key, e); } _encoder.end_array(); } void SerializableObject::Writer::write(std::string const& key, std::any const& value) { std::type_info const& type = value.type(); _encoder_write_key(key); auto e = _write_dispatch_table.find(&type); if (e == _write_dispatch_table.end()) { /* * Using the address of a type_info suffers from aliasing across * compilation units. If we fail on a lookup, we fallback on the * by_name table, but that's slow because we have to keep making a * string each time. * * So when we fail, we insert the address of the type_info that failed * to be found, so that we'll catch it the next time. This ensures we * fail exactly once per alias per type while using this writer. */ const auto& backup_e = _write_dispatch_table_by_name.find(type.name()); if (backup_e != _write_dispatch_table_by_name.end()) { e = _write_dispatch_table.insert({ &type, backup_e->second }).first; } } if (e != _write_dispatch_table.end()) { e->second(value); } else { std::string s; std::string bad_type_name = (type == typeid(UnknownType)) ? type_name_for_error_message( std::any_cast(value).type_name) : type_name_for_error_message(type); if (&key != &_no_key) { s = string_printf( "Encountered object of unknown type '%s' under key '%s'", bad_type_name.c_str(), key.c_str()); } else { s = string_printf( "Encountered object of unknown type '%s'", bad_type_name.c_str()); } _encoder._error(ErrorStatus(ErrorStatus::TYPE_MISMATCH, s)); _encoder.write_null_value(); } } bool SerializableObject::is_equivalent_to(SerializableObject const& other) const { if (_type_record() != other._type_record()) { return false; } const auto policy = (CloningEncoder::ResultObjectPolicy:: MathTypesConcreteAnyDictionaryResult); CloningEncoder e1(policy), e2(policy); SerializableObject::Writer w1(e1, {}); SerializableObject::Writer w2(e2, {}); w1.write(w1._no_key, std::any(Retainer<>(this))); w2.write(w2._no_key, std::any(Retainer<>(&other))); return ( !e1.has_errored() && !e2.has_errored() && w1._any_equals(e1._root, e2._root)); } SerializableObject* SerializableObject::clone(ErrorStatus* error_status) const { CloningEncoder e( CloningEncoder::ResultObjectPolicy::CloneBackToSerializableObject); SerializableObject::Writer w(e, {}); w.write(w._no_key, std::any(Retainer<>(this))); if (e.has_errored(error_status)) { return nullptr; } std::function error_function = [error_status](ErrorStatus const& status) { if (error_status) { *error_status = status; } }; e._resolver.finalize(error_function); return e._root.type() == typeid(SerializableObject::Retainer<>) ? std::any_cast&>(e._root) .take_value() : nullptr; } // to json_string std::string serialize_json_to_string_pretty( const std::any& value, const schema_version_map* schema_version_targets, ErrorStatus* error_status, int indent) { OTIO_rapidjson::StringBuffer output_string_buffer; OTIO_rapidjson::PrettyWriter< decltype(output_string_buffer), OTIO_rapidjson::UTF8<>, OTIO_rapidjson::UTF8<>, OTIO_rapidjson::CrtAllocator, OTIO_rapidjson::kWriteNanAndInfFlag> json_writer(output_string_buffer); json_writer.SetIndent(' ', indent); JSONEncoder json_encoder(json_writer); if (!SerializableObject::Writer::write_root( value, json_encoder, schema_version_targets, error_status)) { return std::string(); } return std::string(output_string_buffer.GetString()); } // to json_string std::string serialize_json_to_string_compact( const std::any& value, const schema_version_map* schema_version_targets, ErrorStatus* error_status) { OTIO_rapidjson::StringBuffer output_string_buffer; OTIO_rapidjson::Writer< decltype(output_string_buffer), OTIO_rapidjson::UTF8<>, OTIO_rapidjson::UTF8<>, OTIO_rapidjson::CrtAllocator, OTIO_rapidjson::kWriteNanAndInfFlag> json_writer(output_string_buffer); JSONEncoder json_encoder(json_writer); if (!SerializableObject::Writer::write_root( value, json_encoder, schema_version_targets, error_status)) { return std::string(); } return std::string(output_string_buffer.GetString()); } // to json_string std::string serialize_json_to_string( const std::any& value, const schema_version_map* schema_version_targets, ErrorStatus* error_status, int indent) { if (indent > 0) { return serialize_json_to_string_pretty( value, schema_version_targets, error_status, indent); } return serialize_json_to_string_compact( value, schema_version_targets, error_status); } bool serialize_json_to_file( std::any const& value, std::string const& file_name, const schema_version_map* schema_version_targets, ErrorStatus* error_status, int indent) { #if defined(_WINDOWS) const int wlen = MultiByteToWideChar(CP_UTF8, 0, file_name.c_str(), -1, NULL, 0); std::vector wchars(wlen); MultiByteToWideChar(CP_UTF8, 0, file_name.c_str(), -1, wchars.data(), wlen); std::ofstream os(wchars.data()); #else // _WINDOWS std::ofstream os(file_name); #endif // _WINDOWS if (!os.is_open()) { if (error_status) { *error_status = ErrorStatus(ErrorStatus::FILE_WRITE_FAILED, file_name); } return false; } OTIO_rapidjson::OStreamWrapper osw(os); bool status; OTIO_rapidjson::PrettyWriter< decltype(osw), OTIO_rapidjson::UTF8<>, OTIO_rapidjson::UTF8<>, OTIO_rapidjson::CrtAllocator, OTIO_rapidjson::kWriteNanAndInfFlag> json_writer(osw); JSONEncoder json_encoder(json_writer); if (indent >= 0) { json_writer.SetIndent(' ', indent); } status = SerializableObject::Writer::write_root( value, json_encoder, schema_version_targets, error_status); return status; } SerializableObject::Writer::~Writer() { if (_child_writer) { delete _child_writer; } if (_child_cloning_encoder) { delete _child_cloning_encoder; } } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/transition.h0000664000175000017500000000575715110656141020577 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/composable.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Represents a transition between the two adjacent items in a Track. /// /// For example, a cross dissolve or wipe. class OTIO_API_TYPE Transition : public Composable { public: /// @brief This struct provides base set of transitions. struct Type { static auto constexpr SMPTE_Dissolve = "SMPTE_Dissolve"; static auto constexpr Custom = "Custom_Transition"; }; /// @brief This struct provides the Transition schema. struct Schema { static auto constexpr name = "Transition"; static int constexpr version = 1; }; using Parent = Composable; /// @brief Create a new transition. /// /// @param name The transition name. /// @param transition_type The transition type. /// @param in_offset The in time offset. /// @param out_offset The out time offset. /// @param metadata The metadata for the transition. OTIO_API Transition( std::string const& name = std::string(), std::string const& transition_type = std::string(), RationalTime in_offset = RationalTime(), RationalTime out_offset = RationalTime(), AnyDictionary const& metadata = AnyDictionary()); bool overlapping() const override; /// @brief Return the transition type. std::string transition_type() const noexcept { return _transition_type; } /// @brief Set the transition type. void set_transition_type(std::string const& transition_type) { _transition_type = transition_type; } /// @brief Return the transition in time offset. RationalTime in_offset() const noexcept { return _in_offset; } /// @brief Set the transition in time offset. void set_in_offset(RationalTime const& in_offset) noexcept { _in_offset = in_offset; } /// @brief Return the transition out time offset. RationalTime out_offset() const noexcept { return _out_offset; } /// @brief Set the transition out time offset. void set_out_offset(RationalTime const& out_offset) noexcept { _out_offset = out_offset; } RationalTime duration(ErrorStatus* error_status = nullptr) const override; /// @brief Return the range in the parent's time. std::optional range_in_parent(ErrorStatus* error_status = nullptr) const; /// @brief Return the range trimmed in the parent's time. std::optional trimmed_range_in_parent(ErrorStatus* error_status = nullptr) const; protected: virtual ~Transition(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::string _transition_type; RationalTime _in_offset, _out_offset; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/stringUtils.h0000664000175000017500000000161715110656141020723 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/stringPrintf.h" #include "opentimelineio/version.h" using opentime::string_printf; #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @name String Utilities ///@{ void fatal_error(std::string const& errMsg); std::string type_name_for_error_message(std::type_info const&); std::string type_name_for_error_message(std::any const& a); std::string type_name_for_error_message(class SerializableObject*); template std::string type_name_for_error_message() { return type_name_for_error_message(typeid(T)); } bool split_schema_string( std::string const& schema_and_version, std::string* schema_name, int* schema_version); ///@} }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/stack.h0000664000175000017500000000456515110656141017506 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/composition.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class Clip; /// @brief A stack of items in a timeline, for example a stack of tracks in a timelime. class OTIO_API_TYPE Stack : public Composition { public: /// @brief This struct provides the Stack schema. struct Schema { static auto constexpr name = "Stack"; static int constexpr version = 1; }; using Parent = Composition; /// @brief Create a new stack. /// /// @param name The name of the stack. /// @param source_range The source range of the stack. /// @param metadata The metadata for the stack. /// @param effects The list of effects for the stack. Note that the /// the stack keeps a retainer to each effect. /// @param markers The list of markers for the stack. Note that the /// the stack keeps a retainer to each marker. OTIO_API Stack( std::string const& name = std::string(), std::optional const& source_range = std::nullopt, AnyDictionary const& metadata = AnyDictionary(), std::vector const& effects = std::vector(), std::vector const& markers = std::vector()); TimeRange range_of_child_at_index( int index, ErrorStatus* error_status = nullptr) const override; TimeRange trimmed_range_of_child_at_index( int index, ErrorStatus* error_status = nullptr) const override; TimeRange available_range(ErrorStatus* error_status = nullptr) const override; std::map range_of_all_children(ErrorStatus* error_status = nullptr) const override; std::vector> children_in_range( TimeRange const& search_range, ErrorStatus* error_status = nullptr) const override; std::optional available_image_bounds(ErrorStatus* error_status) const override; protected: virtual ~Stack(); std::string composition_kind() const override; bool read_from(Reader&) override; void write_to(Writer&) const override; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/serializableObject.h0000664000175000017500000005622615110656141022177 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/rationalTime.h" #include "opentime/timeRange.h" #include "opentime/timeTransform.h" #include "opentimelineio/anyDictionary.h" #include "opentimelineio/anyVector.h" #include "opentimelineio/color.h" #include "opentimelineio/errorStatus.h" #include "opentimelineio/typeRegistry.h" #include "opentimelineio/version.h" #include "Imath/ImathBox.h" #include "serialization.h" #include #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class CloningEncoder; /// @brief A serializable object. class OTIO_API_TYPE SerializableObject { public: /// @brief This struct provides the SerializableObject schema. struct Schema { static auto constexpr name = "SerializableObject"; static int constexpr version = 1; }; /// @brief Create a new serializable object. OTIO_API SerializableObject(); /// @brief Delete a serializable object. /// /// You cannot directly delete a SerializableObject* (or, hopefully, anything /// derived from it, as all derivations are required to protect the destructor). /// /// Instead, call the member function possibly_delete(), which deletes the object /// (and, recursively, the objects owned by this object), provided the objects /// are not under external management (e.g. prevented from being deleted because an /// external scripting system is holding a reference to them). OTIO_API bool possibly_delete(); /// @brief Serialize this object to a JSON file. /// /// @param file_name The file name. /// @param error_status The return status. /// @param target_family_label_spec @todo Add comment. /// @param indent The number of spaces to use for indentation. OTIO_API bool to_json_file( std::string const& file_name, ErrorStatus* error_status = nullptr, const schema_version_map* target_family_label_spec = nullptr, int indent = 4) const; /// @brief Serialize this object to a JSON string. /// /// @param error_status The return status. /// @param target_family_label_spec @todo Add comment. /// @param indent The number of spaces to use for indentation. OTIO_API std::string to_json_string( ErrorStatus* error_status = nullptr, const schema_version_map* target_family_label_spec = nullptr, int indent = 4) const; /// @brief Deserialize this object from a JSON file. /// /// @param file_name The file name. /// @param error_status The return status. static OTIO_API SerializableObject* from_json_file( std::string const& file_name, ErrorStatus* error_status = nullptr); /// @brief Deserialize this object from a JSON file. /// /// @param input The input string. /// @param error_status The return status. static OTIO_API SerializableObject* from_json_string( std::string const& input, ErrorStatus* error_status = nullptr); /// @brief Return whether this object is equivalent to another. OTIO_API bool is_equivalent_to(SerializableObject const& other) const; /// @brief Makes a (deep) clone of this instance. /// /// Descendent objects are cloned as well. /// /// If the operation fails, nullptr is returned and error_status /// is set appropriately. OTIO_API SerializableObject* clone(ErrorStatus* error_status = nullptr) const; /// @brief Allow external system (e.g. Python, Swift) to add serializable /// fields on the fly. /// /// C++ implementations should have no need for this functionality. AnyDictionary& dynamic_fields() { return _dynamic_fields; } template struct Retainer; /// @brief This class provides reading functionality. class Reader { public: void debug_dict() { for (auto e: _dict) { printf("Key: %s\n", e.first.c_str()); } } bool read(std::string const& key, bool* dest); bool read(std::string const& key, int* dest); bool read(std::string const& key, double* dest); bool read(std::string const& key, std::string* dest); bool read(std::string const& key, RationalTime* dest); bool read(std::string const& key, TimeRange* dest); bool read(std::string const& key, class TimeTransform* dest); bool read(std::string const& key, Color* dest); bool read(std::string const& key, IMATH_NAMESPACE::V2d* value); bool read(std::string const& key, IMATH_NAMESPACE::Box2d* value); bool read(std::string const& key, AnyVector* dest); bool read(std::string const& key, AnyDictionary* dest); bool read(std::string const& key, std::any* dest); bool read(std::string const& key, std::optional* dest); bool read(std::string const& key, std::optional* dest); bool read(std::string const& key, std::optional* dest); bool read(std::string const& key, std::optional* dest); bool read(std::string const& key, std::optional* dest); bool read(std::string const& key, std::optional* dest); bool read(std::string const& key, std::optional* dest); bool read( std::string const& key, std::optional* value); // skipping std::string because we translate null into the empty // string, so the conversion is somewhat ambiguous // no other std::optionals are allowed: template bool read(std::string const& key, std::optional* dest) = delete; template bool read(std::string const& key, T* dest) { std::any a; return read(key, &a) && _from_any(a, dest); } template bool read(std::string const& key, Retainer* dest) { SerializableObject* so; if (!read(key, &so)) { return false; } if (!so) { *dest = Retainer(); return true; } if (T* tso = dynamic_cast(so)) { *dest = Retainer(tso); return true; } _error(ErrorStatus( ErrorStatus::TYPE_MISMATCH, std::string( "Expected object of type " + fwd_type_name_for_error_message(typeid(T)) + "; read type " + fwd_type_name_for_error_message(so) + " instead"))); return false; } bool has_key(std::string const& key) { return _dict.find(key) != _dict.end(); } template bool read_if_present(std::string const& key, T* dest) { return has_key(key) ? read(key, dest) : true; } void error(ErrorStatus const& error_status) { _error(error_status); } private: typedef std::function error_function_t; // forward functions to keep stringUtils.h private static std::string fwd_type_name_for_error_message(std::type_info const&); static std::string fwd_type_name_for_error_message(std::any const& a); static std::string fwd_type_name_for_error_message(class SerializableObject*); struct _Resolver { std::map data_for_object; std::map object_for_id; std::map line_number_for_object; void finalize(error_function_t error_function) { for (auto e: data_for_object) { int line_number = line_number_for_object[e.first]; Reader::_fix_reference_ids( e.second, error_function, *this, line_number); Reader r(e.second, error_function, e.first, line_number); e.first->read_from(r); } } }; std::any _decode(_Resolver& resolver); template bool _from_any(std::any const& source, std::vector* dest) { if (!_type_check(typeid(AnyVector), source.type())) { return false; } AnyVector const& av = std::any_cast(source); std::vector result; result.reserve(av.size()); for (auto e: av) { T elem; if (!_from_any(e, &elem)) { break; } result.emplace_back(elem); } dest->swap(result); return true; } template bool _from_any(std::any const& source, std::list* dest) { if (!_type_check(typeid(AnyVector), source.type())) { return false; } AnyVector const& av = std::any_cast(source); std::list result; for (auto e: av) { T elem; if (!_from_any(e, &elem)) { break; } result.emplace_back(elem); } dest->swap(result); return true; } template bool _from_any(std::any const& source, std::map* dest) { if (!_type_check(typeid(AnyDictionary), source.type())) { return false; } AnyDictionary const& dict = std::any_cast(source); std::map result; for (auto e: dict) { T elem; if (!_from_any(e.second, &elem)) { break; } result.emplace(e.first, elem); } dest->swap(result); return true; } template bool _from_any(std::any const& source, T** dest) { if (source.type() == typeid(void)) { *dest = nullptr; return true; } if (!_type_check_so(typeid(Retainer<>), source.type(), typeid(T))) { return false; } SerializableObject* so = std::any_cast>(source).value; if (!so) { *dest = nullptr; } else if (T* tptr = dynamic_cast(so)) { *dest = tptr; } else { _type_check_so(typeid(T), typeid(*so), typeid(T)); return false; } return true; } template bool _from_any(std::any const& source, Retainer* dest) { if (!_type_check_so(typeid(Retainer<>), source.type(), typeid(T))) { return false; } Retainer<> const& rso = std::any_cast const&>(source); if (!rso.value) { *dest = Retainer(nullptr); return true; } else if (T* tptr = dynamic_cast(rso.value)) { *dest = Retainer(tptr); return true; } _type_check_so(typeid(T), typeid(*rso.value), typeid(T)); return false; } template bool _from_any(std::any const& source, T* dest) { if (!_type_check(typeid(T), source.type())) { return false; } *dest = std::any_cast(source); return true; } Reader( AnyDictionary&, error_function_t const& error_function, SerializableObject* source, int line_number = -1); void _error(ErrorStatus const& error_status); template bool _fetch(std::string const& key, T* dest, bool* had_null = nullptr); template bool _read_optional(std::string const& key, std::optional* value); bool _fetch(std::string const& key, int64_t* dest); bool _fetch(std::string const& key, double* dest); bool _fetch(std::string const& key, SerializableObject** dest); bool _type_check(std::type_info const& wanted, std::type_info const& found); bool _type_check_so( std::type_info const& wanted, std::type_info const& found, std::type_info const& so_type); static void _fix_reference_ids( AnyDictionary&, error_function_t const& error_function, _Resolver&, int line_number); static void _fix_reference_ids( std::any&, error_function_t const& error_function, _Resolver&, int line_number); Reader(Reader const&) = delete; Reader operator=(Reader const&) = delete; AnyDictionary _dict; error_function_t const& _error_function; SerializableObject* _source; int _line_number; friend class UnknownSchema; friend class JSONDecoder; friend class CloningEncoder; friend class SerializableObject; friend class TypeRegistry; }; /// @brief This class provides writing functionality. class Writer { public: static bool write_root( std::any const& value, class Encoder& encoder, const schema_version_map* downgrade_version_manifest = nullptr, ErrorStatus* error_status = nullptr); void write(std::string const& key, bool value); void write(std::string const& key, int64_t value); void write(std::string const& key, double value); void write(std::string const& key, std::string const& value); void write(std::string const& key, RationalTime value); void write(std::string const& key, TimeRange value); void write(std::string const& key, IMATH_NAMESPACE::V2d value); void write(std::string const& key, IMATH_NAMESPACE::Box2d value); void write(std::string const& key, std::optional value); void write(std::string const& key, std::optional value); void write(std::string const& key, std::optional value); void write( std::string const& key, std::optional value); void write(std::string const& key, class TimeTransform value); void write(std::string const& key, Color value); void write(std::string const& key, SerializableObject const* value); void write(std::string const& key, SerializableObject* value) { write(key, (SerializableObject const*) (value)); } void write(std::string const& key, AnyDictionary const& value); void write(std::string const& key, AnyVector const& value); void write(std::string const& key, std::any const& value); template void write(std::string const& key, T const& value) { write(key, _to_any(value)); } template void write(std::string const& key, Retainer const& retainer) { write(key, retainer.value); } private: /// Convenience routines for converting various STL structures of specific /// types to a parallel hierarchy holding std::any. ///@{ template static std::any _to_any(std::vector const& value) { AnyVector av; av.reserve(value.size()); for (const auto& e: value) { av.emplace_back(_to_any(e)); } return std::any(std::move(av)); } template static std::any _to_any(std::map const& value) { AnyDictionary am; for (const auto& e: value) { am.emplace(e.first, _to_any(e.second)); } return std::any(std::move(am)); } template static std::any _to_any(std::list const& value) { AnyVector av; av.reserve(value.size()); for (const auto& e: value) { av.emplace_back(_to_any(e)); } return std::any(std::move(av)); } template static std::any _to_any(T const* value) { SerializableObject* so = (SerializableObject*) value; return std::any(SerializableObject::Retainer<>(so)); } template static std::any _to_any(T* value) { SerializableObject* so = (SerializableObject*) value; return std::any(SerializableObject::Retainer<>(so)); } template static std::any _to_any(Retainer const& value) { SerializableObject* so = value.value; return std::any(SerializableObject::Retainer<>(so)); } template static std::any _to_any(T const& value) { return std::any(value); } ///@} Writer( class Encoder& encoder, const schema_version_map* downgrade_version_manifest) : _encoder(encoder) , _downgrade_version_manifest(downgrade_version_manifest) { _build_dispatch_tables(); } ~Writer(); Writer(Writer const&) = delete; Writer operator=(Writer const&) = delete; void _build_dispatch_tables(); void _write(std::string const& key, std::any const& value); void _encoder_write_key(std::string const& key); bool _any_dict_equals(std::any const& lhs, std::any const& rhs); bool _any_array_equals(std::any const& lhs, std::any const& rhs); bool _any_equals(std::any const& lhs, std::any const& rhs); std::string _no_key; std::unordered_map< std::type_info const*, std::function> _write_dispatch_table; std::unordered_map< std::type_info const*, std::function> _equality_dispatch_table; std::unordered_map> _write_dispatch_table_by_name; std::unordered_map _id_for_object; std::unordered_map _next_id_for_type; Writer* _child_writer = nullptr; CloningEncoder* _child_cloning_encoder = nullptr; class Encoder& _encoder; const schema_version_map* _downgrade_version_manifest; friend class SerializableObject; }; /// @brief Deserialize from the given reader. virtual bool read_from(Reader&); /// @brief Serialize to the given writer. virtual void write_to(Writer&) const; /// @brief Return whether this schema is unknown. virtual bool is_unknown_schema() const; /// @brief Return the schema name. std::string schema_name() const { return _type_record()->schema_name; } /// @brief Return the schema version. int schema_version() const { return _type_record()->schema_version; } /// @brief This struct provides similar functionality to a smart pointer. template struct Retainer { operator T*() const noexcept { return value; } T* operator->() const noexcept { return value; } operator bool() const noexcept { return value != nullptr; } Retainer(T const* so = nullptr) : value((T*) so) { if (value) value->_managed_retain(); } Retainer(Retainer const& rhs) : value(rhs.value) { if (value) value->_managed_retain(); } Retainer& operator=(Retainer const& rhs) { if (rhs.value) rhs.value->_managed_retain(); if (value) value->_managed_release(); value = rhs.value; return *this; } ~Retainer() { if (value) value->_managed_release(); } T* take_value() { if (!value) return nullptr; T* ptr = value; value = nullptr; ptr->_managed_ref_count--; return ptr; } T* value; }; protected: virtual ~SerializableObject(); virtual bool _is_deletable(); virtual std::string _schema_name_for_reference() const; private: SerializableObject(SerializableObject const&) = delete; SerializableObject& operator=(SerializableObject const&) = delete; template friend struct Retainer; OTIO_API void _managed_retain(); OTIO_API void _managed_release(); public: /// @brief This struct provides a reference ID. struct ReferenceId { std::string id; friend bool operator==(ReferenceId lhs, ReferenceId rhs) { return lhs.id == rhs.id; } }; /// @todo Add comment. void install_external_keepalive_monitor( std::function monitor, bool apply_now); /// @brief Return the current reference count. int current_ref_count() const; /// @brief This struct provides an unknown type. struct UnknownType { std::string type_name; }; private: void _set_type_record(TypeRegistry::_TypeRecord const* type_record) { _cached_type_record = type_record; } TypeRegistry::_TypeRecord const* _type_record() const; mutable TypeRegistry::_TypeRecord const* _cached_type_record; int _managed_ref_count; std::function _external_keepalive_monitor; mutable std::mutex _mutex; AnyDictionary _dynamic_fields; friend class TypeRegistry; }; template SerializableObject::Retainer dynamic_retainer_cast(SerializableObject::Retainer const& retainer) { return dynamic_cast(retainer.value); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/missingReference.cpp0000664000175000017500000000163015110656141022212 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/missingReference.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { MissingReference::MissingReference( std::string const& name, std::optional const& available_range, AnyDictionary const& metadata, std::optional const& available_image_bounds) : Parent(name, available_range, metadata, available_image_bounds) {} MissingReference::~MissingReference() {} bool MissingReference::is_missing_reference() const { return true; } bool MissingReference::read_from(Reader& reader) { return Parent::read_from(reader); } void MissingReference::write_to(Writer& writer) const { Parent::write_to(writer); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/marker.cpp0000664000175000017500000000205715110656141020207 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/marker.h" #include "opentimelineio/missingReference.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Marker::Marker( std::string const& name, TimeRange const& marked_range, std::string const& color, AnyDictionary const& metadata, std::string const& comment) : Parent(name, metadata) , _color(color) , _marked_range(marked_range) , _comment(comment) {} Marker::~Marker() {} bool Marker::read_from(Reader& reader) { return reader.read_if_present("color", &_color) && reader.read("marked_range", &_marked_range) && reader.read_if_present("comment", &_comment) && Parent::read_from(reader); } void Marker::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("color", _color); writer.write("marked_range", _marked_range); writer.write("comment", _comment); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/gap.h0000664000175000017500000000440515110656141017141 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/item.h" #include "opentimelineio/mediaReference.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief An empty item within a timeline. class OTIO_API_TYPE Gap : public Item { public: /// @brief This struct provides the Gap schema. struct Schema { static auto constexpr name = "Gap"; static int constexpr version = 1; }; using Parent = Item; /// @brief Create a new gap. /// /// @param source_range The source range of the gap. /// @param name The name of the gap. /// @param effects The list of effects for the gap. Note that the /// the gap keeps a retainer to each effect. /// @param markers The list of markers for the gap. Note that the /// the gap keeps a retainer to each marker. /// @param metadata The metadata for the gap. OTIO_API Gap(TimeRange const& source_range = TimeRange(), std::string const& name = std::string(), std::vector const& effects = std::vector(), std::vector const& markers = std::vector(), AnyDictionary const& metadata = AnyDictionary()); /// @brief Create a new gap. /// /// @param duration The duration of the gap. /// @param name The name of the gap. /// @param effects The list of effects for the gap. Note that the /// the gap keeps a retainer to each effect. /// @param markers The list of markers for the gap. Note that the /// the gap keeps a retainer to each marker. /// @param metadata The metadata for the gap. Gap(RationalTime duration, std::string const& name = std::string(), std::vector const& effects = std::vector(), std::vector const& markers = std::vector(), AnyDictionary const& metadata = AnyDictionary()); bool visible() const override; protected: virtual ~Gap(); bool read_from(Reader&) override; void write_to(Writer&) const override; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/externalReference.h0000664000175000017500000000330615110656141022032 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/mediaReference.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief A reference to a media file. class OTIO_API_TYPE ExternalReference final : public MediaReference { public: /// @brief This struct provides the ExternalReference schema. struct Schema { static auto constexpr name = "ExternalReference"; static int constexpr version = 1; }; using Parent = MediaReference; /// @brief Create a new external reference. /// /// @param target_url The URL to the media. /// @param available_range The available range of the media. /// @param metadata The metadata for the media. /// @param available_image_bounds The spatial bounds of the media. OTIO_API ExternalReference( std::string const& target_url = std::string(), std::optional const& available_range = std::nullopt, AnyDictionary const& metadata = AnyDictionary(), std::optional const& available_image_bounds = std::nullopt); /// @brief Return the media file URL. std::string target_url() const noexcept { return _target_url; } /// @brief Set the media file URL. void set_target_url(std::string const& target_url) { _target_url = target_url; } protected: virtual ~ExternalReference(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::string _target_url; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/transition.cpp0000664000175000017500000000415615110656141021122 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/transition.h" #include "opentimelineio/composition.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Transition::Transition( std::string const& name, std::string const& transition_type, RationalTime in_offset, RationalTime out_offset, AnyDictionary const& metadata) : Parent(name, metadata) , _transition_type(transition_type) , _in_offset(in_offset) , _out_offset(out_offset) {} Transition::~Transition() {} bool Transition::overlapping() const { return true; } bool Transition::read_from(Reader& reader) { return reader.read("in_offset", &_in_offset) && reader.read("out_offset", &_out_offset) && reader.read("transition_type", &_transition_type) && Parent::read_from(reader); } void Transition::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("in_offset", _in_offset); writer.write("out_offset", _out_offset); writer.write("transition_type", _transition_type); } RationalTime Transition::duration(ErrorStatus* /* error_status */) const { return _in_offset + _out_offset; } std::optional Transition::range_in_parent(ErrorStatus* error_status) const { if (!parent()) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::NOT_A_CHILD, "cannot compute range in parent because item has no parent", this); } } return parent()->range_of_child(this, error_status); } std::optional Transition::trimmed_range_in_parent(ErrorStatus* error_status) const { if (!parent()) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::NOT_A_CHILD, "cannot compute trimmed range in parent because item has no parent", this); } } return parent()->trimmed_range_of_child(this, error_status); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/stack.cpp0000664000175000017500000001027315110656141020032 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/stack.h" #include "opentimelineio/clip.h" #include "opentimelineio/vectorIndexing.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Stack::Stack( std::string const& name, std::optional const& source_range, AnyDictionary const& metadata, std::vector const& effects, std::vector const& markers) : Parent(name, source_range, metadata, effects, markers) {} Stack::~Stack() {} std::string Stack::composition_kind() const { static std::string kind = "Stack"; return kind; } bool Stack::read_from(Reader& reader) { return Parent::read_from(reader); } void Stack::write_to(Writer& writer) const { Parent::write_to(writer); } TimeRange Stack::range_of_child_at_index(int index, ErrorStatus* error_status) const { index = adjusted_vector_index(index, children()); if (index < 0 || index >= int(children().size())) { if (error_status) { *error_status = ErrorStatus::ILLEGAL_INDEX; } return TimeRange(); } Composable* child = children()[index]; auto duration = child->duration(error_status); if (is_error(error_status)) { return TimeRange(); } return TimeRange(RationalTime(0, duration.rate()), duration); } std::map Stack::range_of_all_children(ErrorStatus* error_status) const { std::map result; auto kids = children(); for (size_t i = 0; i < kids.size(); i++) { result[kids[i]] = range_of_child_at_index(int(i), error_status); if (is_error(error_status)) { break; } } return result; } std::vector> Stack::children_in_range( TimeRange const& search_range, ErrorStatus* error_status) const { std::vector> children; for (const auto& child: this->children()) { if (const auto& item = dynamic_retainer_cast(child)) { const auto range = item->trimmed_range_in_parent(error_status); if (range.has_value() && range.value().intersects(search_range)) { children.push_back(child); } } } return children; } TimeRange Stack::trimmed_range_of_child_at_index(int index, ErrorStatus* error_status) const { auto range = range_of_child_at_index(index, error_status); if (is_error(error_status) || !source_range()) { return range; } const TimeRange sr = *source_range(); return TimeRange( sr.start_time(), std::min(range.duration(), sr.duration())); } TimeRange Stack::available_range(ErrorStatus* error_status) const { if (children().empty()) { return TimeRange(); } auto duration = children()[0].value->duration(error_status); for (size_t i = 1; i < children().size() && !is_error(error_status); i++) { duration = std::max(duration, children()[i].value->duration(error_status)); } return TimeRange(RationalTime(0, duration.rate()), duration); } std::optional Stack::available_image_bounds(ErrorStatus* error_status) const { std::optional box; bool found_first_child = false; for (auto clip: find_children(error_status)) { std::optional child_box; if (auto clip_box = clip->available_image_bounds(error_status)) { child_box = clip_box; } if (is_error(error_status)) { return std::optional(); } if (child_box) { if (found_first_child) { box->extendBy(*child_box); } else { box = child_box; found_first_child = true; } } } return box; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/item.cpp0000664000175000017500000001116315110656141017662 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/item.h" #include "opentimelineio/composition.h" #include "opentimelineio/effect.h" #include "opentimelineio/marker.h" #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Item::Item( std::string const& name, std::optional const& source_range, AnyDictionary const& metadata, std::vector const& effects, std::vector const& markers, bool enabled, std::optional const& color) : Parent(name, metadata) , _source_range(source_range) , _effects(effects.begin(), effects.end()) , _markers(markers.begin(), markers.end()) , _color(color) , _enabled(enabled) {} Item::~Item() {} bool Item::visible() const { return _enabled; } bool Item::overlapping() const { return false; } RationalTime Item::duration(ErrorStatus* error_status) const { return trimmed_range(error_status).duration(); } TimeRange Item::available_range(ErrorStatus* error_status) const { if (error_status) { *error_status = ErrorStatus::NOT_IMPLEMENTED; } return TimeRange(); } TimeRange Item::visible_range(ErrorStatus* error_status) const { TimeRange result = trimmed_range(error_status); if (parent() && !is_error(error_status)) { auto head_tail = parent()->handles_of_child(this, error_status); if (is_error(error_status)) { return result; } if (head_tail.first) { result = TimeRange( result.start_time() - *head_tail.first, result.duration() + *head_tail.first); } if (head_tail.second) { result = TimeRange( result.start_time(), result.duration() + *head_tail.second); } } return result; } std::optional Item::trimmed_range_in_parent(ErrorStatus* error_status) const { if (!parent() && error_status) { *error_status = ErrorStatus::NOT_A_CHILD; error_status->object_details = this; } return parent()->trimmed_range_of_child(this, error_status); } TimeRange Item::range_in_parent(ErrorStatus* error_status) const { if (!parent() && error_status) { *error_status = ErrorStatus::NOT_A_CHILD; error_status->object_details = this; } return parent()->range_of_child(this, error_status); } RationalTime Item::transformed_time( RationalTime time, Item const* to_item, ErrorStatus* error_status) const { if (!to_item) { return time; } auto root = _highest_ancestor(); auto item = this; auto result = time; while (item != root && item != to_item) { auto parent = item->parent(); result -= item->trimmed_range(error_status).start_time(); if (is_error(error_status)) { return result; } result += parent->range_of_child(item, error_status).start_time(); item = parent; } auto ancestor = item; item = to_item; while (item != root && item != ancestor) { auto parent = item->parent(); result += item->trimmed_range(error_status).start_time(); if (is_error(error_status)) { return result; } result -= parent->range_of_child(item, error_status).start_time(); if (is_error(error_status)) { return result; } item = parent; } assert(item == ancestor); return result; } TimeRange Item::transformed_time_range( TimeRange time_range, Item const* to_item, ErrorStatus* error_status) const { return TimeRange( transformed_time(time_range.start_time(), to_item, error_status), time_range.duration()); } bool Item::read_from(Reader& reader) { return reader.read_if_present("source_range", &_source_range) && reader.read_if_present("effects", &_effects) && reader.read_if_present("markers", &_markers) && reader.read_if_present("enabled", &_enabled) && reader.read_if_present("color", &_color) && Parent::read_from(reader); } void Item::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("source_range", _source_range); writer.write("effects", _effects); writer.write("markers", _markers); writer.write("enabled", _enabled); writer.write("color", _color); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/timeline.h0000664000175000017500000001041215110656141020173 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/serializableObjectWithMetadata.h" #include "opentimelineio/stack.h" #include "opentimelineio/track.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class Clip; /// @brief A timeline contains a stack of tracks. class OTIO_API_TYPE Timeline : public SerializableObjectWithMetadata { public: /// @brief This struct provides the Timeline schema. struct Schema { static auto constexpr name = "Timeline"; static int constexpr version = 1; }; using Parent = SerializableObjectWithMetadata; /// @brief Create a new timelime. /// /// @param name The timeline name. /// @param global_start_time The global start time of the timeline. /// @param metadata The metadata for the timeline. OTIO_API Timeline( std::string const& name = std::string(), std::optional global_start_time = std::nullopt, AnyDictionary const& metadata = AnyDictionary()); /// @brief Return the timeline stack. Stack* tracks() const noexcept { return _tracks; } /* Stack* tracks() { return _tracks; }*/ /// @brief Set the timeline stack. void set_tracks(Stack* stack); /// @brief Return the global start time. std::optional global_start_time() const noexcept { return _global_start_time; } /// @brief Set the global start time. void set_global_start_time(std::optional const& global_start_time) { _global_start_time = global_start_time; } /// @brief Return the duration of the timeline. RationalTime duration(ErrorStatus* error_status = nullptr) const { return _tracks.value->duration(error_status); } /// @brief Return the range of the given child. TimeRange range_of_child( Composable const* child, ErrorStatus* error_status = nullptr) const { return _tracks.value->range_of_child(child, error_status); } /// @brief Return the list of audio tracks. OTIO_API std::vector audio_tracks() const; /// @brief Return the list of video tracks. OTIO_API std::vector video_tracks() const; /// @brief Find child clips. /// /// @param error_status The return status. /// @param search_range An optional range to limit the search. /// @param shallow_search The search is recursive unless shallow_search is /// set to true. OTIO_API std::vector> find_clips( ErrorStatus* error_status = nullptr, std::optional const& search_range = std::nullopt, bool shallow_search = false) const; /// @brief Find child objects that match the given template type. /// /// @param error_status The return status. /// @param search_range An optional range to limit the search. /// @param shallow_search The search is recursive unless shallow_search is /// set to true. template OTIO_API std::vector> find_children( ErrorStatus* error_status = nullptr, std::optional search_range = std::nullopt, bool shallow_search = false) const; /// @brief Return the spatial bounds of the timeline. std::optional available_image_bounds(ErrorStatus* error_status) const { return _tracks.value->available_image_bounds(error_status); } protected: virtual ~Timeline(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::optional _global_start_time; Retainer _tracks; }; template inline std::vector> Timeline::find_children( ErrorStatus* error_status, std::optional search_range, bool shallow_search) const { return _tracks.value->find_children( error_status, search_range, shallow_search); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/serializableObject.cpp0000664000175000017500000001401015110656141022513 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/serializableObject.h" #include "opentimelineio/deserialization.h" #include "opentimelineio/serialization.h" #include "stringUtils.h" #include "typeRegistry.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { SerializableObject::SerializableObject() : _cached_type_record(nullptr) { _managed_ref_count = 0; } SerializableObject::~SerializableObject() {} // forwarded functions std::string SerializableObject::Reader::fwd_type_name_for_error_message( std::type_info const& t) { return type_name_for_error_message(t); } std::string SerializableObject::Reader::fwd_type_name_for_error_message(std::any const& a) { return type_name_for_error_message(a); } std::string SerializableObject::Reader::fwd_type_name_for_error_message( class SerializableObject* so) { return type_name_for_error_message(so); } TypeRegistry::_TypeRecord const* SerializableObject::_type_record() const { std::lock_guard lock(_mutex); if (!_cached_type_record) { _cached_type_record = TypeRegistry::instance()._lookup_type_record(typeid(*this)); if (!_cached_type_record) { fatal_error(string_printf( "Code for C++ type %s has not been registered via " "TypeRegistry::register_type()", type_name_for_error_message(typeid(*this)).c_str())); } } return _cached_type_record; } bool SerializableObject::_is_deletable() { std::lock_guard lock(_mutex); return _managed_ref_count == 0; } bool SerializableObject::possibly_delete() { if (!_is_deletable()) { return false; } delete this; return true; } bool SerializableObject::read_from(Reader& reader) { /* * Want to move everything from reader._dict into * _dynamic_fields, overwriting as we go. */ for (auto& e: reader._dict) { auto it = _dynamic_fields.find(e.first); if (it != _dynamic_fields.end()) { it->second.swap(e.second); } else { _dynamic_fields.emplace(e.first, std::move(e.second)); } } return true; } void SerializableObject::write_to(Writer& writer) const { for (auto e: _dynamic_fields) { writer.write(e.first, e.second); } } bool SerializableObject::is_unknown_schema() const { return false; } std::string SerializableObject::to_json_string( ErrorStatus* error_status, const schema_version_map* schema_version_targets, int indent) const { return serialize_json_to_string( std::any(Retainer<>(this)), schema_version_targets, error_status, indent); } bool SerializableObject::to_json_file( std::string const& file_name, ErrorStatus* error_status, const schema_version_map* schema_version_targets, int indent) const { return serialize_json_to_file( std::any(Retainer<>(this)), file_name, schema_version_targets, error_status, indent); } SerializableObject* SerializableObject::from_json_string( std::string const& input, ErrorStatus* error_status) { std::any dest; if (!deserialize_json_from_string(input, &dest, error_status)) { return nullptr; } if (dest.type() != typeid(Retainer<>)) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::TYPE_MISMATCH, string_printf( "Expected a SerializableObject*, found object of type '%s' instead", type_name_for_error_message(dest.type()).c_str())); } return nullptr; } return std::any_cast&>(dest).take_value(); } SerializableObject* SerializableObject::from_json_file( std::string const& file_name, ErrorStatus* error_status) { std::any dest; if (!deserialize_json_from_file(file_name, &dest, error_status)) { return nullptr; } if (dest.type() != typeid(Retainer<>)) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::TYPE_MISMATCH, string_printf( "Expected a SerializableObject*, found object of type '%s' instead", type_name_for_error_message(dest.type()).c_str())); } return nullptr; } return std::any_cast&>(dest).take_value(); } std::string SerializableObject::_schema_name_for_reference() const { return schema_name(); } void SerializableObject::_managed_retain() { { std::lock_guard lock(_mutex); if (_managed_ref_count++ != 1 || !_external_keepalive_monitor) return; } // We just changed from unique (old ref count was 1) to non-unique // and we know we have a monitor. _external_keepalive_monitor(); } void SerializableObject::_managed_release() { _mutex.lock(); if (--_managed_ref_count == 0) { _mutex.unlock(); delete this; return; } if (_managed_ref_count != 1 || !_external_keepalive_monitor) { _mutex.unlock(); return; } // We just changed back to unique (new ref count is 1) // and we know we have a monitor. _mutex.unlock(); _external_keepalive_monitor(); } void SerializableObject::install_external_keepalive_monitor( std::function monitor, bool apply_now) { { std::lock_guard lock(_mutex); if (!_external_keepalive_monitor) { _external_keepalive_monitor = monitor; } } if (apply_now) { _external_keepalive_monitor(); } } int SerializableObject::current_ref_count() const { std::lock_guard lock(_mutex); return _managed_ref_count; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/CORE_VERSION_MAP.cpp0000664000175000017500000001507115110656316021364 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project // // This document is automatically generated by running the `make version-map` // make target. It is part of the unit tests suite and should be updated // whenever schema versions change. If it needs to be updated, run: `make // version-map-update` and this file should be regenerated. // // This maps a "Label" to a map of Schema name to Schema version. The intent is // that these sets of schemas can be used for compatibility with future // versions of OTIO, so that a newer version of OTIO can target a compatibility // version of an older library. #include "opentimelineio/typeRegistry.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { const label_to_schema_version_map CORE_VERSION_MAP{ { "0.14.0", { { "Adapter", 1 }, { "Clip", 1 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.15.0", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.16.0", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.17.0", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.18.0", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.18.1", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, // {next} }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/freezeFrame.h0000664000175000017500000000176715110656141020635 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/linearTimeWarp.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Hold the first frame of the clip for the duration of the clip. class OTIO_API_TYPE FreezeFrame : public LinearTimeWarp { public: /// @brief This struct provides the FreezeFrame schema. struct Schema { static auto constexpr name = "FreezeFrame"; static int constexpr version = 1; }; using Parent = LinearTimeWarp; /// @brief Create a new freeze frame time effect. /// /// @param name The name of the time effect. /// @param metadata The metadata for the time effect. FreezeFrame( std::string const& name = std::string(), AnyDictionary const& metadata = AnyDictionary()); protected: virtual ~FreezeFrame(); }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/serializableCollection.h0000664000175000017500000001447115110656141023060 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/composition.h" #include "opentimelineio/serializableObjectWithMetadata.h" #include "opentimelineio/timeline.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class Clip; /// @brief A container which can hold an ordered list of any serializable objects. /// /// Note that this is not a Composition nor is it Composable. /// /// This container approximates the concept of a bin - a collection of /// SerializableObjects that do not have any compositional meaning, but can /// serialize to/from OTIO correctly, with metadata and a named collection. /// /// A SerializableCollection is useful for serializing multiple timelines, /// clips, or media references to a single file. class OTIO_API_TYPE SerializableCollection : public SerializableObjectWithMetadata { public: /// @brief This struct provides the SerializableCollection schema. struct Schema { static auto constexpr name = "SerializableCollection"; static int constexpr version = 1; }; using Parent = SerializableObjectWithMetadata; /// @brief Create a new serializable collection. /// /// @param name The name of the collection. /// @param child The list of children in the collection. Note that the /// collection keeps a retainer to each child. /// @param metadata The metadata for the collection. OTIO_API SerializableCollection( std::string const& name = std::string(), std::vector children = std::vector(), AnyDictionary const& metadata = AnyDictionary()); /// @brief Return the list of children. std::vector> const& children() const noexcept { return _children; } /// @brief Modify the list of children. std::vector>& children() noexcept { return _children; } /// @brief Set the list of children. OTIO_API void set_children(std::vector const& children); /// @brief Clear the children. OTIO_API void clear_children(); /// @brief Insert a child at the given index. Note that the collection /// keeps a retainer to the child. OTIO_API void insert_child(int index, SerializableObject* child); /// @brief Set the child at the given index. Note that the collection /// keeps a retainer to the child. OTIO_API bool set_child( int index, SerializableObject* child, ErrorStatus* error_status = nullptr); /// @brief Remove the child at the given index. OTIO_API bool remove_child(int index, ErrorStatus* error_status = nullptr); /// @brief Find child clips. /// /// @param error_status The return status. /// @param search_range An optional range to limit the search. /// @param shallow_search The search is recursive unless shallow_search is /// set to true. OTIO_API std::vector> find_clips( ErrorStatus* error_status = nullptr, std::optional const& search_range = std::nullopt, bool shallow_search = false) const; /// @brief Find child objects that match the given template type. /// /// @param error_status The return status. /// @param search_range An optional range to limit the search. /// @param shallow_search The search is recursive unless shallow_search is /// set to true. template OTIO_API std::vector> find_children( ErrorStatus* error_status = nullptr, std::optional search_range = std::nullopt, bool shallow_search = false) const; protected: virtual ~SerializableCollection(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::vector> _children; }; template inline std::vector> SerializableCollection::find_children( ErrorStatus* error_status, std::optional search_range, bool shallow_search) const { std::vector> out; for (const auto& child: _children) { // filter out children who are not descended from the specified type if (auto valid_child = dynamic_cast(child.value)) { out.push_back(valid_child); } // if not a shallow_search, for children that are serializable collections, // compositions, or timelines, recurse into their children if (!shallow_search) { if (auto collection = dynamic_cast(child.value)) { const auto valid_children = collection->find_children(error_status, search_range); if (is_error(error_status)) { return out; } for (const auto& valid_child: valid_children) { out.push_back(valid_child); } } else if (auto composition = dynamic_cast(child.value)) { const auto valid_children = composition->find_children(error_status, search_range); if (is_error(error_status)) { return out; } for (const auto& valid_child: valid_children) { out.push_back(valid_child); } } else if (auto timeline = dynamic_cast(child.value)) { const auto valid_children = timeline->find_children(error_status, search_range); if (is_error(error_status)) { return out; } for (const auto& valid_child: valid_children) { out.push_back(valid_child); } } } } return out; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/mediaReference.cpp0000664000175000017500000000237515110656141021627 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/mediaReference.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { MediaReference::MediaReference( std::string const& name, std::optional const& available_range, AnyDictionary const& metadata, std::optional const& available_image_bounds) : Parent(name, metadata) , _available_range(available_range) , _available_image_bounds(available_image_bounds) {} MediaReference::~MediaReference() {} bool MediaReference::is_missing_reference() const { return false; } bool MediaReference::read_from(Reader& reader) { return reader.read_if_present("available_range", &_available_range) && reader.read_if_present( "available_image_bounds", &_available_image_bounds) && Parent::read_from(reader); } void MediaReference::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("available_range", _available_range); writer.write("available_image_bounds", _available_image_bounds); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/imageSequenceReference.h0000664000175000017500000001376115110656141022771 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/mediaReference.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief A reference to an image sequence. /// /// Image file names are composed from a target URL base, name prefix, name /// suffix, frame number, and zero padding. For example the image file name /// "file:///path/to/image.000100.exr": /// * Target URL base: file:///path/to/ /// * Name prefix: "image." /// * Name suffix: ".exr" /// * Frame number padded to six zeroes: "000100" class OTIO_API_TYPE ImageSequenceReference final : public MediaReference { public: /// @brief How to handle missing frames. enum MissingFramePolicy { error = 0, hold = 1, black = 2 }; /// @brief This struct provides the ImageSequenceReference schema. struct Schema { static auto constexpr name = "ImageSequenceReference"; static int constexpr version = 1; }; using Parent = MediaReference; /// @brief Create a new image sequence reference. /// /// @param target_url_base The base of the URL. /// @param name_prefix The prefix of the file name. /// @param name_suffix The suffix of the file name. /// @param start_frame The start frame of the image sequence. /// @param frame_step The frame step of the image sequence. /// @param rate The frame rate of the image sequence. /// @param frame_zero_padding The number of zeroes used to pad the frame numbers. /// @param missing_frame_policy How to handle missing frames. /// @param available_range The available range of the image sequence. /// @param metadata The metadata for the image sequence. /// @param available_image_bounds The spatial bounds of the image sequence. ImageSequenceReference( std::string const& target_url_base = std::string(), std::string const& name_prefix = std::string(), std::string const& name_suffix = std::string(), int start_frame = 1, int frame_step = 1, double rate = 1, int frame_zero_padding = 0, MissingFramePolicy const missing_frame_policy = MissingFramePolicy::error, std::optional const& available_range = std::nullopt, AnyDictionary const& metadata = AnyDictionary(), std::optional const& available_image_bounds = std::nullopt); /// @brief Return the URL base. std::string target_url_base() const noexcept { return _target_url_base; } /// @brief Set the URL base. void set_target_url_base(std::string const& target_url_base) { _target_url_base = target_url_base; } /// @brief Return the file name prefix. std::string name_prefix() const noexcept { return _name_prefix; } /// @brief Set the file name prefix. void set_name_prefix(std::string const& target_url_base) { _name_prefix = target_url_base; } /// @brief Return the file name suffix. std::string name_suffix() const noexcept { return _name_suffix; } /// @brief Set the file name suffix. void set_name_suffix(std::string const& target_url_base) { _name_suffix = target_url_base; } /// @brief Return the start frame. int start_frame() const noexcept { return _start_frame; } /// @brief Set the start frame. void set_start_frame(int start_frame) noexcept { _start_frame = start_frame; } /// @brief Return the frame step. int frame_step() const noexcept { return _frame_step; } /// @brief Set the frame step. void set_frame_step(int frame_step) noexcept { _frame_step = frame_step; } /// @brief Return the frame rate. double rate() const noexcept { return _rate; } /// @brief Set the frame rate. void set_rate(double rate) noexcept { _rate = rate; } /// @brief Return the frame number zero padding. int frame_zero_padding() const noexcept { return _frame_zero_padding; } /// @brief Set the frame number zero padding. void set_frame_zero_padding(int frame_zero_padding) noexcept { _frame_zero_padding = frame_zero_padding; } /// @brief Set the missing frame policy. void set_missing_frame_policy(MissingFramePolicy missing_frame_policy) noexcept { _missing_frame_policy = missing_frame_policy; } /// @brief Return the missing frame policy. MissingFramePolicy missing_frame_policy() const noexcept { return _missing_frame_policy; } /// @brief Return the end frame. int end_frame() const; /// @brief Return the number of images in the sequence. int number_of_images_in_sequence() const; /// @brief Return the frame for the given time. int frame_for_time( RationalTime const& time, ErrorStatus* error_status = nullptr) const; /// @brief Return the target URL for the given image number. std::string target_url_for_image_number( int image_number, ErrorStatus* error_status = nullptr) const; /// @brief Return the presentation time for the given image number. RationalTime presentation_time_for_image_number( int image_number, ErrorStatus* error_status = nullptr) const; protected: virtual ~ImageSequenceReference(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::string _target_url_base; std::string _name_prefix; std::string _name_suffix; int _start_frame; int _frame_step; double _rate; int _frame_zero_padding; MissingFramePolicy _missing_frame_policy; RationalTime frame_duration() const noexcept; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/deserialization.h0000664000175000017500000000137015110656141021556 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/serializableObject.h" #include "opentimelineio/version.h" #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Deserialize JSON data from a string. OTIO_API bool deserialize_json_from_string( std::string const& input, std::any* destination, ErrorStatus* error_status = nullptr); /// @brief Deserialize JSON data from a file. OTIO_API bool deserialize_json_from_file( std::string const& file_name, std::any* destination, ErrorStatus* error_status = nullptr); }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/anyVector.h0000664000175000017500000001060215110656141020340 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/export.h" #include "opentimelineio/version.h" #include #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief This class provides a replacement for "std::vector". /// /// This class has exactly the same API as "std::vector", except /// that it records a "time-stamp" that lets external observers know when /// the vector has been destroyed (which includes the case of the vector /// being relocated in memory). /// /// This allows us to hand out iterators that can be aware of moves /// and take steps to safe-guard themselves from causing a crash. class OTIO_API_TYPE AnyVector : private std::vector { public: using vector::vector; /// @brief Create an empty vector. AnyVector() : _mutation_stamp{} {} /// @brief Create a copy of a vector. /// /// To be safe, avoid brace-initialization so as to not trigger /// list initialization behavior in older compilers: AnyVector(const AnyVector& other) : vector(other) , _mutation_stamp{ nullptr } {} /// @brief Destructor. ~AnyVector() { if (_mutation_stamp) { _mutation_stamp->any_vector = nullptr; } } /// @brief Copy operator. AnyVector& operator=(const AnyVector& other) { vector::operator=(other); return *this; } /// @brief Move operator. AnyVector& operator=(AnyVector&& other) { vector::operator=(other); return *this; } /// @brief Copy operator. AnyVector& operator=(std::initializer_list ilist) { vector::operator=(ilist); return *this; } using vector::assign; using vector::get_allocator; using vector::at; using vector::operator[]; using vector::back; using vector::data; using vector::front; using vector::begin; using vector::cbegin; using vector::cend; using vector::crbegin; using vector::crend; using vector::end; using vector::rbegin; using vector::rend; using vector::capacity; using vector::empty; using vector::max_size; using vector::reserve; using vector::shrink_to_fit; using vector::size; using vector::clear; using vector::emplace; using vector::emplace_back; using vector::erase; using vector::insert; using vector::pop_back; using vector::push_back; using vector::resize; using vector::swap; using vector::allocator_type; using vector::const_iterator; using vector::const_pointer; using vector::const_reference; using vector::const_reverse_iterator; using vector::difference_type; using vector::iterator; using vector::pointer; using vector::reference; using vector::reverse_iterator; using vector::size_type; using vector::value_type; /// @brief Swap vectors. void swap(AnyVector& other) { vector::swap(other); } /// @brief This struct provides a mutation time stamp. struct MutationStamp { /// @brief Create a new time stamp. MutationStamp(AnyVector* v) : any_vector{ v } , owning{ false } { assert(v != nullptr); } MutationStamp(MutationStamp const&) = delete; MutationStamp& operator=(MutationStamp const&) = delete; /// @brief Destructor. ~MutationStamp() { if (any_vector) { any_vector->_mutation_stamp = nullptr; if (owning) { delete any_vector; } } } AnyVector* any_vector; bool owning; protected: MutationStamp() : any_vector{ new AnyVector } , owning{ true } { any_vector->_mutation_stamp = this; } }; /// @brief Get or create a mutation time stamp. MutationStamp* get_or_create_mutation_stamp() { if (!_mutation_stamp) { _mutation_stamp = new MutationStamp(this); } return _mutation_stamp; } friend struct MutationStamp; private: MutationStamp* _mutation_stamp = nullptr; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/externalReference.cpp0000664000175000017500000000173215110656141022366 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/externalReference.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { ExternalReference::ExternalReference( std::string const& target_url, std::optional const& available_range, AnyDictionary const& metadata, std::optional const& available_image_bounds) : Parent(std::string(), available_range, metadata, available_image_bounds) , _target_url(target_url) {} ExternalReference::~ExternalReference() {} bool ExternalReference::read_from(Reader& reader) { return reader.read("target_url", &_target_url) && Parent::read_from(reader); } void ExternalReference::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("target_url", _target_url); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/generatorReference.h0000664000175000017500000000443415110656141022201 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/mediaReference.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief A reference to dynamically generated media. class OTIO_API_TYPE GeneratorReference final : public MediaReference { public: /// @brief This struct provides the GeneratorReference schema. struct Schema { static auto constexpr name = "GeneratorReference"; static int constexpr version = 1; }; using Parent = MediaReference; /// @brief Create a new generator reference. /// /// @param name The name of the generator. /// @param generator_kind The kind of generator. /// @param available_range The available range of the generator. /// @param parameters The parameters used to configure the generator. /// @param metadata The metadata for the generator. /// @param available_image_bounds The spatial bounds of the generator. GeneratorReference( std::string const& name = std::string(), std::string const& generator_kind = std::string(), std::optional const& available_range = std::nullopt, AnyDictionary const& parameters = AnyDictionary(), AnyDictionary const& metadata = AnyDictionary(), std::optional const& available_image_bounds = std::nullopt); /// @brief Return the kind of generator. std::string generator_kind() const noexcept { return _generator_kind; } /// @brief Set the kind of generator. void set_generator_kind(std::string const& generator_kind) { _generator_kind = generator_kind; } /// @brief Modify the generator parameters. AnyDictionary& parameters() noexcept { return _parameters; } /// @brief Return the generator parameters. AnyDictionary parameters() const noexcept { return _parameters; } protected: virtual ~GeneratorReference(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::string _generator_kind; AnyDictionary _parameters; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/deserialization.cpp0000664000175000017500000006176115110656141022123 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentime/rationalTime.h" #include "opentime/timeRange.h" #include "opentime/timeTransform.h" #include "opentimelineio/color.h" #include "opentimelineio/serializableObject.h" #include "opentimelineio/serializableObjectWithMetadata.h" #include "stringUtils.h" #define RAPIDJSON_NAMESPACE OTIO_rapidjson #include #include #include #include #if defined(_WINDOWS) # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif // WIN32_LEAN_AND_MEAN # ifndef NOMINMAX # define NOMINMAX # endif // NOMINMAX # include #endif namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class JSONDecoder : public OTIO_rapidjson:: BaseReaderHandler, JSONDecoder> { public: JSONDecoder(std::function line_number_function) : _line_number_function{ line_number_function } { using namespace std::placeholders; _error_function = std::bind(&JSONDecoder::_error, this, _1); } bool has_errored(ErrorStatus* error_status) { if (error_status) { *error_status = _error_status; } return is_error(_error_status); } bool has_errored() { return is_error(_error_status); } void finalize() { if (!has_errored()) { _resolver.finalize(_error_function); } } bool Null() { return store(std::any()); } bool Bool(bool b) { return store(std::any(b)); } // coerce all integer types to int64_t... bool Int(int i) { return store(std::any(static_cast(i))); } bool Int64(int64_t i) { return store(std::any(static_cast(i))); } bool Uint(unsigned u) { return store(std::any(static_cast(u))); } bool Uint64(uint64_t u) { /// prevent an overflow return store(std::any(static_cast(u & 0x7FFFFFFFFFFFFFFF))); } // ...and all floating point types to double bool Double(double d) { return store(std::any(d)); } bool String(const char* str, OTIO_rapidjson::SizeType length, bool /* copy */) { return store(std::any(std::string(str, length))); } bool Key(const char* str, OTIO_rapidjson::SizeType length, bool /* copy */) { if (has_errored()) { return false; } if (_stack.empty() || !_stack.back().is_dict) { _internal_error( "RapidJSONDecoder:: _handle_key called while not decoding an object"); return false; } _stack.back().cur_key = std::string(str, length); return true; } bool StartArray() { if (has_errored()) { return false; } _stack.emplace_back(_DictOrArray{ false /* is_dict*/ }); return true; } bool StartObject() { if (has_errored()) { return false; } _stack.emplace_back(_DictOrArray{ true /* is_dict*/ }); return true; } bool EndArray(OTIO_rapidjson::SizeType) { if (has_errored()) { return false; } if (_stack.empty()) { _internal_error( "RapidJSONDecoder::_handle_end_array() called without matching _handle_start_array()"); } else { auto& top = _stack.back(); if (top.is_dict) { _internal_error( "RapidJSONDecoder::_handle_end_array() called without matching _handle_start_array()"); _stack.pop_back(); } else { AnyVector va; va.swap(top.array); _stack.pop_back(); store(std::any(std::move(va))); } } return true; } bool EndObject(OTIO_rapidjson::SizeType) { if (has_errored()) { return false; } if (_stack.empty()) { _internal_error( "JSONDecoder::_handle_end_object() called without matching _handle_start_object()"); } else { auto& top = _stack.back(); if (!top.is_dict) { _internal_error( "JSONDecoder::_handle_end_object() called without matching _handle_start_object"); _stack.pop_back(); } else { // when we end a dictionary, we immediately convert it // to the type it really represents, if it is a schema object. SerializableObject::Reader reader( top.dict, _error_function, nullptr, static_cast(_line_number_function())); _stack.pop_back(); store(reader._decode(_resolver)); } } return true; } bool store(std::any&& a) { if (has_errored()) { return false; } if (_stack.empty()) { _root.swap(a); } else { auto& top = _stack.back(); if (top.is_dict) { top.dict.emplace(_stack.back().cur_key, a); } else { top.array.emplace_back(a); } } return true; } template static T const* _lookup(AnyDictionary const& d, std::string const& key) { auto e = d.find(key); if (e != d.end() && typeid(T) == e->second.type()) { return &std::any_cast(e->second); } return nullptr; } std::any _root; void _internal_error(std::string const& err_msg) { _error_status = ErrorStatus( ErrorStatus::INTERNAL_ERROR, string_printf( "%s (near line %d)", err_msg.c_str(), _line_number_function())); } void _error(ErrorStatus const& error_status) { _error_status = error_status; } ErrorStatus _error_status; struct _DictOrArray { _DictOrArray(bool is_dict) { this->is_dict = is_dict; } bool is_dict; AnyDictionary dict; AnyVector array; std::string cur_key; }; std::vector<_DictOrArray> _stack; std::function _error_function; std::function _line_number_function; SerializableObject::Reader::_Resolver _resolver; }; SerializableObject::Reader::Reader( AnyDictionary& source, error_function_t const& error_function, SerializableObject* so, int line_number) : _error_function(error_function) , _source(so) , _line_number(line_number) { // destructively read from source. Decoding it will either return it back // anyway, or convert it to another type, in which case we want to destroy // the original so as to not keep extra data around. _dict.swap(source); } void SerializableObject::Reader::_error(ErrorStatus const& error_status) { if (!_source) { if (_line_number > 0) { _error_function(ErrorStatus( error_status.outcome, string_printf("near line %d", _line_number))); } else { _error_function(error_status); } return; } std::string line_description; if (_line_number > 0) { line_description = string_printf(" (near line %d)", _line_number); } std::string name = ""; auto e = _dict.find("name"); if (e != _dict.end() && e->second.type() == typeid(std::string)) { name = std::any_cast(e->second); } _error_function(ErrorStatus( error_status.outcome, string_printf( "While reading object named '%s' (of type '%s'): %s%s", name.c_str(), type_name_for_error_message(_source).c_str(), error_status.details.c_str(), line_description.c_str()))); } void SerializableObject::Reader::_fix_reference_ids( AnyDictionary& m, error_function_t const& error_function, _Resolver& resolver, int line_number) { for (auto& e: m) { _fix_reference_ids(e.second, error_function, resolver, line_number); } } void SerializableObject::Reader::_fix_reference_ids( std::any& a, error_function_t const& error_function, _Resolver& resolver, int line_number) { if (a.type() == typeid(AnyDictionary)) { _fix_reference_ids( std::any_cast(a), error_function, resolver, line_number); } else if (a.type() == typeid(AnyVector)) { AnyVector& child_array = std::any_cast(a); for (size_t i = 0; i < child_array.size(); i++) { _fix_reference_ids( child_array[i], error_function, resolver, line_number); } } else if (a.type() == typeid(SerializableObject::ReferenceId)) { std::string id = std::any_cast(a).id; auto e = resolver.object_for_id.find(id); if (e == resolver.object_for_id.end()) { error_function(ErrorStatus( ErrorStatus::UNRESOLVED_OBJECT_REFERENCE, string_printf("%s (near line %d)", id.c_str(), line_number))); } else { a = std::any(Retainer<>(e->second)); } } } template bool SerializableObject::Reader::_fetch( std::string const& key, T* dest, bool* had_null) { auto e = _dict.find(key); if (e == _dict.end()) { _error(ErrorStatus(ErrorStatus::KEY_NOT_FOUND, key)); return false; } else if (e->second.type() == typeid(void) && had_null) { _dict.erase(e); *had_null = true; return true; } else if (e->second.type() != typeid(T)) { _error(ErrorStatus( ErrorStatus::TYPE_MISMATCH, string_printf( "expected type %s under key '%s': found type %s instead", type_name_for_error_message(typeid(T)).c_str(), key.c_str(), type_name_for_error_message(e->second.type()).c_str()))); return false; } if (had_null) { *had_null = false; } std::swap(*dest, std::any_cast(e->second)); _dict.erase(e); return true; } bool SerializableObject::Reader::_fetch(std::string const& key, double* dest) { auto e = _dict.find(key); if (e == _dict.end()) { _error(ErrorStatus(ErrorStatus::KEY_NOT_FOUND, key)); return false; } if (e->second.type() == typeid(double)) { *dest = std::any_cast(e->second); _dict.erase(e); return true; } else if (e->second.type() == typeid(int)) { *dest = static_cast(std::any_cast(e->second)); _dict.erase(e); return true; } else if (e->second.type() == typeid(int64_t)) { *dest = static_cast(std::any_cast(e->second)); _dict.erase(e); return true; } _error(ErrorStatus( ErrorStatus::TYPE_MISMATCH, string_printf( "expected type %s under key '%s': found type %s instead", type_name_for_error_message(typeid(double)).c_str(), key.c_str(), type_name_for_error_message(e->second.type()).c_str()))); return false; } bool SerializableObject::Reader::_fetch(std::string const& key, int64_t* dest) { auto e = _dict.find(key); if (e == _dict.end()) { _error(ErrorStatus(ErrorStatus::KEY_NOT_FOUND, key)); return false; } if (e->second.type() == typeid(int64_t)) { *dest = std::any_cast(e->second); _dict.erase(e); return true; } else if (e->second.type() == typeid(int)) { *dest = std::any_cast(e->second); _dict.erase(e); return true; } _error(ErrorStatus( ErrorStatus::TYPE_MISMATCH, string_printf( "expected type %s under key '%s': found type %s instead", type_name_for_error_message(typeid(int64_t)).c_str(), key.c_str(), type_name_for_error_message(e->second.type()).c_str()))); return false; } bool SerializableObject::Reader::_fetch( std::string const& key, SerializableObject** dest) { auto e = _dict.find(key); if (e == _dict.end()) { _error(ErrorStatus(ErrorStatus::KEY_NOT_FOUND, key)); return false; } if (e->second.type() == typeid(void)) { *dest = nullptr; _dict.erase(e); return true; } else if (e->second.type() != typeid(SerializableObject::Retainer<>)) { _error(ErrorStatus( ErrorStatus::TYPE_MISMATCH, string_printf( "expected SerializableObject* under key '%s': found type %s instead", key.c_str(), type_name_for_error_message(e->second.type()).c_str()))); return false; } *dest = std::any_cast>(e->second); _dict.erase(e); return true; } bool SerializableObject::Reader::_type_check( std::type_info const& wanted, std::type_info const& found) { if (wanted != found) { _error(ErrorStatus( ErrorStatus::TYPE_MISMATCH, string_printf( "while decoding complex STL type, expected type '%s', found type '%s' instead", type_name_for_error_message(wanted).c_str(), type_name_for_error_message(found).c_str()))); return false; } return true; } bool SerializableObject::Reader::_type_check_so( std::type_info const& wanted, std::type_info const& found, std::type_info const& so_type) { if (wanted != found) { _error(ErrorStatus( ErrorStatus::TYPE_MISMATCH, string_printf( "expected to read a %s, found a %s instead", type_name_for_error_message(so_type).c_str(), type_name_for_error_message(found).c_str()))); return false; } return true; } std::any SerializableObject::Reader::_decode(_Resolver& resolver) { if (_dict.find("OTIO_SCHEMA") == _dict.end()) { return std::any(std::move(_dict)); } std::string schema_name_and_version; if (!_fetch("OTIO_SCHEMA", &schema_name_and_version)) { return std::any(); } if (schema_name_and_version == "RationalTime.1") { double rate, value; return _fetch("rate", &rate) && _fetch("value", &value) ? std::any(RationalTime(value, rate)) : std::any(); } else if (schema_name_and_version == "TimeRange.1") { RationalTime start_time, duration; return _fetch("start_time", &start_time) && _fetch("duration", &duration) ? std::any(TimeRange(start_time, duration)) : std::any(); } else if (schema_name_and_version == "Color.1") { double r, g, b, a; std::string name; return _fetch("name", &name) && _fetch("r", &r) && _fetch("g", &g) && _fetch("b", &b) && _fetch("a", &a) ? std::any(Color(r, g, b, a, name)) : std::any(); } else if (schema_name_and_version == "TimeTransform.1") { RationalTime offset; double rate, scale; return _fetch("offset", &offset) && _fetch("rate", &rate) && _fetch("scale", &scale) ? std::any(TimeTransform(offset, scale, rate)) : std::any(); } else if (schema_name_and_version == "SerializableObjectRef.1") { std::string ref_id; if (!_fetch("id", &ref_id)) { return std::any(); } return std::any(SerializableObject::ReferenceId{ ref_id }); } else if (schema_name_and_version == "V2d.1") { double x, y; return _fetch("x", &x) && _fetch("y", &y) ? std::any(IMATH_NAMESPACE::V2d(x, y)) : std::any(); } else if (schema_name_and_version == "Box2d.1") { IMATH_NAMESPACE::V2d min, max; return _fetch("min", &min) && _fetch("max", &max) ? std::any( IMATH_NAMESPACE::Box2d(std::move(min), std::move(max))) : std::any(); } else { std::string ref_id; if (_dict.find("OTIO_REF_ID") != _dict.end()) { if (!_fetch("OTIO_REF_ID", &ref_id)) { return std::any(); } auto e = resolver.object_for_id.find(ref_id); if (e != resolver.object_for_id.end()) { _error(ErrorStatus( ErrorStatus::DUPLICATE_OBJECT_REFERENCE, ref_id)); return std::any(); } } TypeRegistry& r = TypeRegistry::instance(); std::string schema_name; int schema_version; if (!split_schema_string( schema_name_and_version, &schema_name, &schema_version)) { _error(ErrorStatus( ErrorStatus::MALFORMED_SCHEMA, string_printf( "badly formed schema version string '%s'", schema_name_and_version.c_str()))); return std::any(); } ErrorStatus error_status; if (SerializableObject* so = r._instance_from_schema( schema_name, schema_version, _dict, true /* internal_read */, &error_status)) { if (!ref_id.empty()) { resolver.object_for_id[ref_id] = so; } resolver.data_for_object.emplace(so, std::move(_dict)); resolver.line_number_for_object[so] = _line_number; return std::any(SerializableObject::Retainer<>(so)); } _error(error_status); return std::any(); } } bool SerializableObject::Reader::read(std::string const& key, bool* value) { return _fetch(key, value); } bool SerializableObject::Reader::read(std::string const& key, int* value) { return _fetch(key, value); } bool SerializableObject::Reader::read(std::string const& key, double* value) { return _fetch(key, value); } bool SerializableObject::Reader::read(std::string const& key, std::string* value) { bool had_null; if (!_fetch(key, value, &had_null)) { return false; } if (had_null) { value->clear(); } return true; } bool SerializableObject::Reader::read(std::string const& key, RationalTime* value) { return _fetch(key, value); } bool SerializableObject::Reader::read(std::string const& key, TimeRange* value) { return _fetch(key, value); } bool SerializableObject::Reader::read(std::string const& key, Color* value) { return _fetch(key, value); } bool SerializableObject::Reader::read(std::string const& key, TimeTransform* value) { return _fetch(key, value); } bool SerializableObject::Reader::read(std::string const& key, AnyDictionary* value) { return _fetch(key, value); } bool SerializableObject::Reader::read(std::string const& key, AnyVector* value) { return _fetch(key, value); } bool SerializableObject::Reader::read( std::string const& key, IMATH_NAMESPACE::V2d* value) { return _fetch(key, value); } bool SerializableObject::Reader::read( std::string const& key, IMATH_NAMESPACE::Box2d* value) { return _fetch(key, value); } template bool SerializableObject::Reader::_read_optional( std::string const& key, std::optional* value) { bool had_null; T result; if (!SerializableObject::Reader::_fetch(key, &result, &had_null)) { return false; } *value = had_null ? std::optional() : std::optional(result); return true; } bool SerializableObject::Reader::read( std::string const& key, std::optional* value) { return _read_optional(key, value); } bool SerializableObject::Reader::read( std::string const& key, std::optional* value) { return _read_optional(key, value); } bool SerializableObject::Reader::read( std::string const& key, std::optional* value) { return _read_optional(key, value); } bool SerializableObject::Reader::read( std::string const& key, std::optional* value) { return _read_optional(key, value); } bool SerializableObject::Reader::read( std::string const& key, std::optional* value) { return _read_optional(key, value); } bool SerializableObject::Reader::read( std::string const& key, std::optional* value) { return _read_optional(key, value); } bool SerializableObject::Reader::read( std::string const& key, std::optional* value) { return _read_optional(key, value); } bool SerializableObject::Reader::read( std::string const& key, std::optional* value) { return _read_optional(key, value); } bool SerializableObject::Reader::read(std::string const& key, std::any* value) { auto e = _dict.find(key); if (e == _dict.end()) { _error(ErrorStatus(ErrorStatus::KEY_NOT_FOUND, key)); return false; } else { value->swap(e->second); _dict.erase(e); return true; } } bool deserialize_json_from_string( std::string const& input, std::any* destination, ErrorStatus* error_status) { OTIO_rapidjson::Reader reader; OTIO_rapidjson::StringStream ss(input.c_str()); OTIO_rapidjson::CursorStreamWrapper csw(ss); JSONDecoder handler(std::bind(&decltype(csw)::GetLine, &csw)); bool status = reader.Parse(csw, handler); handler.finalize(); if (handler.has_errored(error_status)) { return false; } if (!status) { if (error_status) { auto msg = GetParseError_En(reader.GetParseErrorCode()); *error_status = ErrorStatus( ErrorStatus::JSON_PARSE_ERROR, string_printf( "JSON parse error on input string: %s " "(line %d, column %d)", msg, csw.GetLine(), csw.GetColumn())); } return false; } destination->swap(handler._root); return true; } bool deserialize_json_from_file( std::string const& file_name, std::any* destination, ErrorStatus* error_status) { FILE* fp = nullptr; #if defined(_WINDOWS) const int wlen = MultiByteToWideChar(CP_UTF8, 0, file_name.c_str(), -1, NULL, 0); std::vector wchars(wlen); MultiByteToWideChar(CP_UTF8, 0, file_name.c_str(), -1, wchars.data(), wlen); if (_wfopen_s(&fp, wchars.data(), L"r") != 0) { fp = nullptr; } #else // _WINDOWS fp = fopen(file_name.c_str(), "r"); #endif // _WINDOWS if (!fp) { if (error_status) { *error_status = ErrorStatus(ErrorStatus::FILE_OPEN_FAILED, file_name); } return false; } OTIO_rapidjson::Reader reader; char readBuffer[65536]; OTIO_rapidjson::FileReadStream fs(fp, readBuffer, sizeof(readBuffer)); OTIO_rapidjson::CursorStreamWrapper csw(fs); JSONDecoder handler(std::bind(&decltype(csw)::GetLine, &csw)); bool status = reader.Parse(csw, handler); fclose(fp); handler.finalize(); if (handler.has_errored(error_status)) { return false; } if (!status) { auto msg = GetParseError_En(reader.GetParseErrorCode()); if (error_status) { *error_status = ErrorStatus( ErrorStatus::JSON_PARSE_ERROR, string_printf( "JSON parse error on input string: %s " "(line %d, column %d)", msg, csw.GetLine(), csw.GetColumn())); } return false; } destination->swap(handler._root); return true; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/stringUtils.cpp0000664000175000017500000000256115110656141021255 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/serializableObject.h" #include #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { std::string type_name_for_error_message(std::type_info const& t) { if (t == typeid(std::string)) { return "string"; } if (t == typeid(void)) { return "None"; } return t.name(); } std::string type_name_for_error_message(std::any const& a) { return type_name_for_error_message(a.type()); } std::string type_name_for_error_message(SerializableObject* so) { return type_name_for_error_message(typeid(*so)); } void fatal_error(std::string const& errMsg) { fprintf(stderr, "Fatal error: %s\n", errMsg.c_str()); exit(-1); } bool split_schema_string( std::string const& schema_and_version, std::string* schema_name, int* schema_version) { size_t index = schema_and_version.rfind('.'); if (index == std::string::npos) { return false; } *schema_name = schema_and_version.substr(0, index); try { *schema_version = std::stoi(schema_and_version.substr(index + 1)); return true; } catch (...) { return false; } } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/clip.cpp0000664000175000017500000001367715110656141017667 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/clip.h" #include "opentimelineio/missingReference.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { char constexpr Clip::default_media_key[]; Clip::Clip( std::string const& name, MediaReference* media_reference, std::optional const& source_range, AnyDictionary const& metadata, std::vector const& effects, std::vector const& markers, std::string const& active_media_reference_key, std::optional const& color) : Parent{ name, source_range, metadata, effects, markers, /*enabled*/ true, color } , _active_media_reference_key(active_media_reference_key) { set_media_reference(media_reference); } Clip::~Clip() {} MediaReference* Clip::media_reference() const noexcept { auto active = _media_references.find(_active_media_reference_key); return active == _media_references.end() || !active->second ? nullptr : active->second; } Clip::MediaReferences Clip::media_references() const noexcept { MediaReferences result; for (auto const& m: _media_references) { result.insert( { m.first, dynamic_retainer_cast(m.second) }); } return result; } template bool Clip::check_for_valid_media_reference_key( std::string const& caller, std::string const& key, MediaRefMap const& media_references, ErrorStatus* error_status) { auto empty_key = media_references.find(""); if (empty_key != media_references.end()) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::MEDIA_REFERENCES_CONTAIN_EMPTY_KEY, caller + " failed because the media references contain an empty string key", this); } return false; } auto found = media_references.find(key); if (found == media_references.end()) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY, caller + " failed because the media references do not contain the active key", this); } return false; } return true; } void Clip::set_media_references( MediaReferences const& media_references, std::string const& new_active_key, ErrorStatus* error_status) noexcept { if (!check_for_valid_media_reference_key( "set_media_references", new_active_key, media_references, error_status)) { return; } _media_references.clear(); for (auto const& m: media_references) { _media_references[m.first] = m.second ? m.second : new MissingReference; } _active_media_reference_key = new_active_key; } std::string Clip::active_media_reference_key() const noexcept { return _active_media_reference_key; } void Clip::set_active_media_reference_key( std::string const& new_active_key, ErrorStatus* error_status) noexcept { if (!check_for_valid_media_reference_key( "set_active_media_reference_key", new_active_key, _media_references, error_status)) { return; } _active_media_reference_key = new_active_key; } void Clip::set_media_reference(MediaReference* media_reference) { _media_references[_active_media_reference_key] = media_reference ? media_reference : new MissingReference; } bool Clip::read_from(Reader& reader) { return reader.read("media_references", &_media_references) && reader.read( "active_media_reference_key", &_active_media_reference_key) && Parent::read_from(reader); } void Clip::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("media_references", _media_references); writer.write("active_media_reference_key", _active_media_reference_key); } TimeRange Clip::available_range(ErrorStatus* error_status) const { auto active_media = media_reference(); if (!active_media) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::CANNOT_COMPUTE_AVAILABLE_RANGE, "No media reference set on clip", this); } return TimeRange(); } if (!active_media->available_range()) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::CANNOT_COMPUTE_AVAILABLE_RANGE, "No available_range set on media reference on clip", this); } return TimeRange(); } return active_media->available_range().value(); } std::optional Clip::available_image_bounds(ErrorStatus* error_status) const { auto active_media = media_reference(); //this code path most likely never runs since a null or empty media_reference gets //replaced with a placeholder value when instantiated if (!active_media) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::CANNOT_COMPUTE_BOUNDS, "No image bounds set on clip", this); } return std::optional(); } if (!active_media->available_image_bounds()) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::CANNOT_COMPUTE_BOUNDS, "No image bounds set on media reference on clip", this); } return std::optional(); } return active_media->available_image_bounds(); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/OpenTimelineIOConfig.cmake.in0000664000175000017500000000024515110656141023574 0ustar meme@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(OpenTime) find_dependency(Imath) include("${CMAKE_CURRENT_LIST_DIR}/OpenTimelineIOTargets.cmake") opentimelineio-0.18.1/src/opentimelineio/algo/0000775000175000017500000000000015110656141017140 5ustar memeopentimelineio-0.18.1/src/opentimelineio/algo/editAlgorithm.h0000664000175000017500000001611215110656141022106 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/composition.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { namespace algo { //! Enum used by 3/4 Point Edit (aka. as fill) enum class ReferencePoint { Source, Sequence, Fit }; // Overwrite an item or items. // // | A | B | -> |A| C |B| // ^ ^ // | C | // // item = item to overwrite (usually a clip) -- C in the diagram. // composition = usually a track item. // range = time range to overwrite. // remove_transitions = whether to remove transitions within range. // fill_template = item to fill in (usually a gap). // // If overwrite range starts after B's end, a gap hole is filled with // fill_template and then then C is appended. // // If overwrite range starts before B's end and extends after, B is partitioned // and C is appended at the end. // // If overwrite range starts before A and partially overlaps it, C is // added at the beginning and A is partitioned. // // If overwrite range starts and ends before A, a gap hole is filled with // fill_template. OTIO_API void overwrite( Item* item, Composition* composition, TimeRange const& range, bool remove_transitions = true, Item* fill_template = nullptr, ErrorStatus* error_status = nullptr); // Insert an item. // | A | B | -> | A | C | A | B | // ^ // | C | // // item = item to insert (usually a clip) // composition = usually a track item. // time = time to insert at. If < composition's start time, it will insert at 0 index. // If > composition's end_time_exclusive, it will append at end. // remove_transitions = whether to remove transitions that intersect time. // fill_template = item to fill in (usually a gap), // when time > composition's time. // // If A and B's length is L1 and C's length is L2, the end result is L1 + L2. // A is split. // OTIO_API void insert( Item* const item, Composition* composition, RationalTime const& time, bool const remove_transitions = true, Item* fill_template = nullptr, ErrorStatus* error_status = nullptr); // // Adjust a single item's start time or duration. // // | A | B | C | -> | A |FILL| B | C | // <--* // // item = Item to apply trim to (usually a clip) // delta_in = RationalTime that the item's source_range().start_time() // will be adjusted by // delta_out = RationalTime that the item's // source_range().end_time_exclusive() will be adjusted by // fill_template = item to fill in (usually a gap), // when time > composition's time. // // Do not affect other clips. // Fill now-"empty" time with gap or template // Unless item is meeting a Gap, then, existing Gap's duration will be augmented // OTIO_API void trim( Item* item, RationalTime const& delta_in, RationalTime const& delta_out, Item* fill_template = nullptr, ErrorStatus* error_status = nullptr); // Slice an item. // // | A | B | -> |A|A| B | // ^ // composition = usually a track item. // time = time to slice at. OTIO_API void slice( Composition* composition, RationalTime const& time, bool const remove_transitions = true, ErrorStatus* error_status = nullptr); // // Slip an item start_time by + or -, clamping to available_range if available. // // | A | // <-----> // // item = item to slip (usually a clip) // delta = +/- rational time to slip the item by. // // Do not affect item duration. // Do not affect surrounding items. // Clamp to available_range of media (if available) OTIO_API void slip(Item* item, RationalTime const& delta); // // Slide an item start_time by + or -, adjusting the previous item's duration. // Clamps previous item's duration to available_range if available. // // | A | B | C | -> | A | B | C | // *---> // // item = item to slip (usually a clip) // delta = +/- rational time to slide the item by. // // If item is the first clip, it does nothing. // OTIO_API void slide(Item* item, RationalTime const& delta); // // Adjust a source_range without affecting any other items. // // | A | B | -> | A | B |FILL| // <--* // // item = Item to apply ripple to (usually a clip) // delta_in = RationalTime that the item's source_range().start_time() // will be adjusted by // delta_out = RationalTime that the item's // source_range().end_time_exclusive() will be adjusted by OTIO_API void ripple( Item* item, RationalTime const& delta_in, RationalTime const& delta_out, ErrorStatus* error_status = nullptr); // // Any trim-like action results in adjacent items source_range being adjusted // to fit. // No new items are ever created. // Clamped to available media (if available) // Start time in parent of Item before input item will never change // End time in parent of Item after input item will never change // // | A | B | -> | A | B | // <--* // // item = Item to apply roll to (usually a clip) // delta_in = RationalTime that the item's source_range().start_time() // will be adjusted by // delta_out = RationalTime that the item's // source_range().end_time_exclusive() will be adjusted by OTIO_API void roll( Item* item, RationalTime const& delta_in, RationalTime const& delta_out, ErrorStatus* error_status = nullptr); // Create a 3/4 Point Edit or Fill. // // | A |GAP| B | -> | A | C | B | // ^ ^ // C--| C |--C // // item = item to place onto the track (usually a clip) // track = track that will now own this item. // track_time = RationalTime // reference_point = For 4 point editing, the reference point dictates what // transform to use when running the fill. // OTIO_API void fill( Item* item, Composition* track, RationalTime const& track_time, ReferencePoint const reference_point = ReferencePoint::Source, ErrorStatus* error_status = nullptr); // // Remove item(s) at a time and fill them, optionally with a gap. // // | A | C | B | -> | A |GAP| B | // ^ // | // // track = track to remove item from. // time = RationalTime // fill = whether to fill the hole with fill_template. // fill_template = if nullptr, use a gap to fill the hole. // // if fill is not set, A and B become concatenated, with no fill. // OTIO_API void remove( Composition* composition, RationalTime const& time, bool const fill = true, Item* fill_template = nullptr, ErrorStatus* error_status = nullptr); }}} // namespace opentimelineio::OPENTIMELINEIO_VERSION::algo opentimelineio-0.18.1/src/opentimelineio/algo/editAlgorithm.cpp0000664000175000017500000007325215110656141022451 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include #include "opentimelineio/algo/editAlgorithm.h" #include "opentimelineio/effect.h" #include "opentimelineio/gap.h" #include "opentimelineio/linearTimeWarp.h" #include "opentimelineio/track.h" #include "opentimelineio/transition.h" namespace otime = opentime::OPENTIME_VERSION; using otime::RationalTime; using otime::TimeRange; namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { namespace algo { #include inline std::ostream& operator<<(std::ostream& os, const RationalTime& value) { os << std::fixed << value.value() << "/" << value.rate(); return os; } inline std::ostream& operator<<(std::ostream& os, const TimeRange& value) { os << std::fixed << value.start_time().value() << "/" << value.duration().value() << "/" << value.duration().rate(); return os; } namespace { // We are not testing values outside of one million seconds. // At one million second, and double precision, the smallest // resolvable number that can be added to one million and return // a new value one million + epsilon is 5.82077e-11. // // This was calculated by searching iteratively for epsilon // around 1,000,000, with epsilon starting from 1 and halved // at every iteration, until epsilon when added to 1,000,000 // resulted in 1,000,000. constexpr double double_epsilon = 5.82077e-11; inline bool isEqual(double a, double b) { return (std::abs(a - b) <= double_epsilon); } } // namespace void overwrite( Item* item, Composition* composition, TimeRange const& range, bool const remove_transitions, Item* fill_template, ErrorStatus* error_status) { const TimeRange composition_range = composition->trimmed_range(); const RationalTime start_time = range.start_time(); if (start_time >= composition_range.end_time_exclusive()) { // Append the item and a possible fill (gap). const RationalTime fill_duration = range.start_time() - composition_range.end_time_exclusive(); if (!isEqual(fill_duration.value(), 0.0)) { const TimeRange fill_range = TimeRange( RationalTime(0.0, fill_duration.rate()), fill_duration); if (!fill_template) fill_template = new Gap(fill_range); composition->append_child(fill_template); } composition->append_child(item); } else if (start_time < composition_range.start_time() && range.end_time_exclusive() < composition_range.start_time()) { const RationalTime fill_duration = composition_range.start_time() - start_time - range.duration(); if (!isEqual(fill_duration.value(), 0.0)) { const TimeRange fill_range = TimeRange( RationalTime(0.0, fill_duration.rate()), fill_duration); if (!fill_template) fill_template = new Gap(fill_range); composition->insert_child(0, fill_template); } composition->insert_child(0, item); } else { // Check for transitions to remove first. if (remove_transitions) { auto transitions = composition->find_children( error_status, range, true); if (!transitions.empty()) { for (const auto& transition : transitions) { int index = composition->index_of_child(transition); if (index < 0 || static_cast(index) >= composition->children().size()) continue; composition->remove_child(transition); } } } // Find the items to overwrite. auto items = composition->find_children(error_status, range, true); if (items.empty()) { if (error_status) *error_status = ErrorStatus::NOT_AN_ITEM; return; } TimeRange item_range = composition->trimmed_range_of_child(items.front()).value(); if (1 == items.size() && item_range.contains(range, 0.0)) { auto first_item = items.front(); bool is_fill_fit = false; // We check if we are replacing a gap with a clip with timewarp, // which is the special case of fill() ReferencePoint::Fit. if (dynamic_retainer_cast(first_item)) { auto effects = item->effects(); for (auto& effect : effects) { if (dynamic_retainer_cast(effect)) { is_fill_fit = true; break; } } } // The item overwrites a portion inside an item. const RationalTime first_duration = range.start_time() - item_range.start_time(); const RationalTime second_duration = item_range.duration() - range.duration() - first_duration; int first_index = composition->index_of_child(first_item); int insert_index = first_index; TimeRange trimmed_range = first_item->trimmed_range(); TimeRange source_range(trimmed_range.start_time(), first_duration); if (isEqual(first_duration.value(), 0.0)) { composition->remove_child(first_index); } else { first_item->set_source_range(source_range); ++insert_index; } item_range = item->trimmed_range(); if (range.duration() < item_range.duration() && !is_fill_fit) item->set_source_range( TimeRange(trimmed_range.start_time(), range.duration())); composition->insert_child(insert_index, item); if (!isEqual(second_duration.value(), 0.0)) { auto second_item = dynamic_cast(items.front()->clone()); trimmed_range = second_item->trimmed_range(); source_range = TimeRange( trimmed_range.start_time() + first_duration + range.duration(), second_duration); ++insert_index; second_item->set_source_range(source_range); composition->insert_child(insert_index, second_item); } } else { // Determine if the first item is partially overwritten. int insert_index = composition->index_of_child(items.front()); bool first_partial = false; TimeRange first_source_range; if (item_range.start_time() < range.start_time()) { first_partial = true; const TimeRange trimmed_range = items.front()->trimmed_range(); first_source_range = TimeRange( trimmed_range.start_time(), range.start_time() - item_range.start_time()); ++insert_index; } // Determine if the last item is partially overwritten. bool last_partial = false; TimeRange last_source_range; if (items.size() >= 1) { item_range = composition->trimmed_range_of_child(items.back()).value(); if (item_range.end_time_inclusive() > range.end_time_inclusive()) { last_partial = true; const TimeRange trimmed_range = items.back()->trimmed_range(); RationalTime duration = item_range.end_time_inclusive() - range.end_time_inclusive(); if (items.size() == 1) { duration += range.start_time(); last_source_range = TimeRange( trimmed_range.start_time() + range.duration(), duration); } else { last_source_range = TimeRange( trimmed_range.start_time() + duration, trimmed_range.duration() - duration); } } } // Adjust the first and last items. if (first_partial) { items.front()->set_source_range(first_source_range); items.erase(items.begin()); } if (last_partial) { items.back()->set_source_range(last_source_range); items.erase(items.end() - 1); } // Remove the completely overwritten items. while (!items.empty()) { composition->remove_child(items.back()); items.pop_back(); } // Insert the item. const TimeRange trimmed_range = item->trimmed_range(); item->set_source_range( TimeRange(trimmed_range.start_time(), range.duration())); composition->insert_child(insert_index, item); } } } void insert( Item* const insert_item, Composition* composition, RationalTime const& time, bool const remove_transitions, Item* fill_template, ErrorStatus* error_status) { // Check for transitions to remove first. if (remove_transitions) { TimeRange range(time, RationalTime(1.0, time.rate())); auto transitions = composition->find_children( error_status, range, true); if (!transitions.empty()) { for (const auto& transition : transitions) { int index = composition->index_of_child(transition); if (index < 0 || static_cast(index) >= composition->children().size()) continue; composition->remove_child(transition); } } } const TimeRange composition_range = composition->trimmed_range(); // Find the item to insert into. auto item = dynamic_retainer_cast( composition->child_at_time(time, error_status)); if (!item) { if (time >= composition_range.end_time_exclusive()) { // Append the item and a possible fill (gap). const RationalTime fill_duration = time - composition_range.end_time_exclusive(); if (!isEqual(fill_duration.value(), 0.0)) { const TimeRange fill_range = TimeRange( RationalTime(0.0, fill_duration.rate()), fill_duration); if (!fill_template) fill_template = new Gap(fill_range); composition->append_child(fill_template); } composition->append_child(insert_item); } else if (time < composition_range.start_time()) { composition->insert_child(0, insert_item); } else { if (error_status) *error_status = ErrorStatus::INTERNAL_ERROR; } return; } const int index = composition->index_of_child(item); const TimeRange range = composition->trimmed_range_of_child_at_index(index); int insert_index = index; // Item is partially split bool split = false; const TimeRange first_source_range( item->trimmed_range().start_time(), time - range.start_time()); if (!isEqual(first_source_range.duration().value(), 0.0)) { split = true; item->set_source_range(first_source_range); ++insert_index; } // Insert the new item composition->insert_child(insert_index, insert_item); const TimeRange insert_range = composition->trimmed_range_of_child_at_index(insert_index); // Second item from splitting item if (split) { const TimeRange second_source_range( first_source_range.start_time() + insert_range.start_time() + insert_range.duration(), range.end_time_exclusive() - time); // Clone the item for the second partially overwritten item. if (!isEqual(second_source_range.duration().value(), 0.0)) { auto second_item = dynamic_cast(item->clone()); second_item->set_source_range(second_source_range); composition->insert_child(insert_index + 1, second_item); } } } void trim( Item* item, RationalTime const& delta_in, RationalTime const& delta_out, Item* fill_template, ErrorStatus* error_status) { Composition* composition = item->parent(); if (!composition) { if (error_status) *error_status = ErrorStatus::NOT_A_CHILD_OF; return; } auto children = composition->children(); const int index = composition->index_of_child(item); if (index < 0) { if (error_status) *error_status = ErrorStatus::NOT_AN_ITEM; return; } const TimeRange range = item->trimmed_range(); RationalTime start_time = range.start_time(); RationalTime end_time_exclusive = range.end_time_exclusive(); if (delta_in.value() != 0.0) { start_time += delta_in; if (index > 0) { auto previous = dynamic_retainer_cast(children[index - 1]); TimeRange previous_range = previous->trimmed_range(); previous_range = TimeRange(previous_range.start_time(), previous_range.duration() + delta_in); previous->set_source_range(previous_range); } } if (delta_out.value() != 0.0) { const int next_index = index + 1; if (static_cast(next_index) < children.size()) { auto next = dynamic_retainer_cast(children[next_index]); auto gap_next = dynamic_retainer_cast(children[next_index]); if (gap_next && delta_out.value() > 0.0) { end_time_exclusive += delta_out; } else if (delta_out.value() < 0.0) { if (gap_next) { end_time_exclusive += delta_out; const TimeRange gap_range = gap_next->trimmed_range(); const TimeRange gap_new_range( gap_range.start_time() - delta_out, gap_range.duration() + delta_out); gap_next->set_source_range(gap_new_range); } else { end_time_exclusive += delta_out; const RationalTime fill_duration = -delta_out; if (fill_duration.value() > 0.0) { const TimeRange fill_range = TimeRange( RationalTime(0.0, fill_duration.rate()), fill_duration); if (!fill_template) fill_template = new Gap(fill_range); composition->insert_child(next_index, fill_template); } } } } } const TimeRange new_range = TimeRange::range_from_start_end_time(start_time, end_time_exclusive); item->set_source_range(new_range); } void slice( Composition* composition, RationalTime const& time, bool const remove_transitions, ErrorStatus* error_status) { auto item = dynamic_retainer_cast( composition->child_at_time(time, error_status)); if (!item) { if (error_status) *error_status = ErrorStatus::NOT_AN_ITEM; return; } const int index = composition->index_of_child(item); const TimeRange range = composition->trimmed_range_of_child_at_index(index); // Check for slice at start of clip (invalid slice) const RationalTime duration = time - range.start_time(); if (isEqual(duration.value(), 0.0)) return; // Accumulate intersecting transitions std::vector transitions; if (auto track = dynamic_cast(composition)) { const auto neighbors = track->neighbors_of(item, error_status); if (auto transition = dynamic_cast(neighbors.second.value)) { const auto transition_range = track->trimmed_range_of_child(transition).value(); if (transition_range.contains(time)) { transitions.push_back(transition); } } if (auto transition = dynamic_cast(neighbors.first.value)) { const auto transition_range = track->trimmed_range_of_child(transition).value(); if (transition_range.contains(time)) { transitions.push_back(transition); } } } // Remove transitions if (!transitions.empty()) { if (remove_transitions) { for (auto transition : transitions) { const int child_index = composition->index_of_child(transition); composition->remove_child(child_index); } } else { if (error_status) *error_status = ErrorStatus::CANNOT_TRIM_TRANSITION; return; } } // Adjust the source range for the first slice. const TimeRange first_source_range( item->trimmed_range().start_time(), duration); item->set_source_range(first_source_range); // Clone the item for the second slice. auto second_item = dynamic_cast(item->clone()); const TimeRange second_source_range( first_source_range.start_time() + first_source_range.duration(), range.duration() - first_source_range.duration()); if (!(isEqual(second_source_range.duration().value(), 0.0))) { second_item->set_source_range(second_source_range); composition->insert_child(static_cast(index) + 1, second_item); } } void slip( Item* item, RationalTime const& delta) { const TimeRange range = item->trimmed_range(); RationalTime start_time = range.start_time(); start_time += delta; // Clamp to available range of media if present const TimeRange available_range = item->available_range(); if (!isEqual(available_range.duration().value(), 0.0)) { if (start_time < available_range.start_time()) { start_time = available_range.start_time(); } else if (start_time + range.duration() > available_range.end_time_exclusive()) { // S---E (move <- source start time so that E matches) // A-----E const RationalTime end_diff = start_time + range.duration() - available_range.end_time_exclusive(); start_time -= end_diff; } } const TimeRange new_range(start_time, range.duration()); item->set_source_range(new_range); } void slide( Item* item, RationalTime const& delta) { Composition* composition = item->parent(); if (!composition) { return; } const int index = composition->index_of_child(item); // Exit early if we are at the first clip or if the delta is 0. if (index <= 0 || delta.value() == 0.0) { return; } auto children = composition->children(); auto previous = dynamic_retainer_cast(children[index - 1]); const TimeRange range = previous->trimmed_range(); const TimeRange available_range = previous->available_range(); RationalTime offset = delta; if (delta.value() < 0.0) { // Check we don't move left beyond the previous clip's duration if (range.duration() <= -delta) { return; } } else { // Check we don't move right beyond the previous clip's // available duration if (!isEqual(available_range.duration().value(), 0.0) && range.duration() + delta > available_range.duration()) { offset = available_range.duration() - range.duration(); } } const otime::TimeRange new_range(range.start_time(), range.duration() + offset); previous->set_source_range(new_range); } void ripple( Item* item, RationalTime const& delta_in, RationalTime const& delta_out, ErrorStatus* error_status) { if (error_status) *error_status = ErrorStatus::OK; const TimeRange range = item->trimmed_range(); RationalTime start_time = range.start_time(); RationalTime end_time_exclusive = range.end_time_exclusive(); if (delta_in.value() != 0.0) { RationalTime in_offset = delta_in; if (delta_in < start_time) { in_offset = -start_time; } else if (start_time + delta_in > end_time_exclusive) { in_offset = delta_in - end_time_exclusive; } start_time += in_offset; } if (delta_out.value() != 0.0) { RationalTime out_offset = delta_out; if (delta_out.value() > 0.0) { const TimeRange available_range = item->available_range(); // Check we don't move right beyond the clip's // available duration if (!(isEqual(available_range.duration().value(), 0.0)) && range.duration() + delta_out > available_range.duration()) { out_offset = available_range.duration() - range.duration(); } } end_time_exclusive += out_offset; } const TimeRange new_range = TimeRange::range_from_start_end_time(start_time, end_time_exclusive); item->set_source_range(new_range); } void roll( Item* item, RationalTime const& delta_in, RationalTime const& delta_out, ErrorStatus* error_status) { Composition* composition = item->parent(); if (!composition) { if (error_status) *error_status = ErrorStatus::NOT_A_CHILD_OF; return; } auto children = composition->children(); const int index = composition->index_of_child(item); if (index < 0) { if (error_status) *error_status = ErrorStatus::NOT_AN_ITEM; return; } const TimeRange range = item->trimmed_range(); const TimeRange available_range = item->available_range(); RationalTime start_time = range.start_time(); RationalTime end_time_exclusive = range.end_time_exclusive(); if (delta_in.value() != 0.0) { const RationalTime available_start_time = available_range.start_time(); RationalTime in_offset = delta_in; if (-in_offset > start_time) in_offset = -start_time; if (index > 0) { auto previous = dynamic_retainer_cast(children[index - 1]); TimeRange previous_range = previous->trimmed_range(); // Clamp to previous clip's range first RationalTime duration = previous_range.duration(); if (duration < -in_offset) { duration -= RationalTime(1.0, duration.rate()); in_offset -= duration; } previous_range = TimeRange(previous_range.start_time(), previous_range.duration() + in_offset); previous->set_source_range(previous_range); } start_time += in_offset; // If available range present, clamp to its start_time. if (!(isEqual(available_range.duration().value(), 0.0))) { if (start_time < available_start_time) start_time = available_start_time; } } if (delta_out.value() != 0.0) { const size_t next_index = index + 1; if (next_index < children.size()) { auto next = dynamic_retainer_cast(children[next_index]); TimeRange next_range = next->trimmed_range(); const TimeRange next_available_range = next->available_range(); RationalTime next_start_time = next_range.start_time(); RationalTime out_offset = delta_out; // If available range, clamp to it. if (!(isEqual(available_range.duration().value(), 0.0))) { RationalTime available_start_time = next_available_range.start_time(); if (-out_offset > available_start_time) out_offset = -available_start_time; } else { if (-out_offset > next_start_time) out_offset = -next_start_time; } end_time_exclusive += out_offset; next_start_time += out_offset; next_range = TimeRange(next_start_time, next_range.duration()); next->set_source_range(next_range); } } const TimeRange new_range = TimeRange::range_from_start_end_time(start_time, end_time_exclusive); item->set_source_range(new_range); } void fill( Item* item, Composition* track, RationalTime const& track_time, ReferencePoint const reference_point, ErrorStatus* error_status) { // Find the gap to replace. auto gap = dynamic_retainer_cast( track->child_at_time(track_time, error_status, true)); if (!gap) { if (error_status) *error_status = ErrorStatus::NOT_A_GAP; return; } const TimeRange clip_range = item->trimmed_range(); const TimeRange gap_range = gap->trimmed_range(); TimeRange gap_track_range = track->trimmed_range_of_child(gap).value(); RationalTime duration = clip_range.duration(); switch (reference_point) { case ReferencePoint::Sequence: { RationalTime start_time = clip_range.start_time(); const RationalTime gap_start_time = gap_range.start_time(); auto track_item = dynamic_cast(item->clone()); // Check if start time is less than gap's start time (trim it if so) if (start_time < gap_start_time) { duration -= gap_start_time - start_time; start_time = gap_start_time; } // Check if end time is longer (trim it if it is) if (clip_range.end_time_exclusive() > gap_range.end_time_exclusive()) { duration = gap_range.end_time_exclusive() - start_time; } const TimeRange new_clip_range(start_time, duration); track_item->set_source_range(new_clip_range); if (duration > gap_track_range.end_time_exclusive() - track_time) { duration = gap_track_range.end_time_exclusive() - track_time; } const TimeRange time_range(track_time, duration); overwrite( track_item, track, time_range, true, nullptr, error_status); return; } case ReferencePoint::Fit: { const double pct = gap_range.duration().to_seconds() / duration.to_seconds(); const std::string& name = item->name(); LinearTimeWarp* timeWarp = new LinearTimeWarp(name, name + "_timeWarp", pct); auto& effects = item->effects(); std::vector effectList; for (auto& effect : effects) effectList.push_back(effect); effectList.push_back(timeWarp); item = new Item(name, clip_range, AnyDictionary(), effectList); const TimeRange time_range( track_time, gap_track_range.end_time_exclusive() - track_time); overwrite(item, track, time_range, true, nullptr, error_status); return; } case ReferencePoint::Source: default: { const TimeRange time_range(track_time, duration); overwrite(item, track, time_range, true, nullptr, error_status); return; } } } void remove( Composition* composition, RationalTime const& time, bool const fill, Item* fill_template, ErrorStatus* error_status) { auto item = dynamic_retainer_cast( composition->child_at_time(time, error_status)); if (!item) { if (error_status) *error_status = ErrorStatus::NOT_AN_ITEM; return; } const int index = composition->index_of_child(item); const TimeRange item_range = item->trimmed_range(); composition->remove_child(index); if (fill) { if (!fill_template) fill_template = new Gap(item_range); composition->insert_child(index, fill_template); } } }}} // namespace opentimelineio::OPENTIMELINEIO_VERSION::algo opentimelineio-0.18.1/src/opentimelineio/gap.cpp0000664000175000017500000000214115110656141017467 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/gap.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Gap::Gap( TimeRange const& source_range, std::string const& name, std::vector const& effects, std::vector const& markers, AnyDictionary const& metadata) : Parent(name, source_range, metadata, effects, markers) {} Gap::Gap( RationalTime duration, std::string const& name, std::vector const& effects, std::vector const& markers, AnyDictionary const& metadata) : Parent( name, TimeRange(RationalTime(0, duration.rate()), duration), metadata, effects, markers) {} Gap::~Gap() {} bool Gap::visible() const { return false; } bool Gap::read_from(Reader& reader) { return Parent::read_from(reader); } void Gap::write_to(Writer& writer) const { Parent::write_to(writer); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/version.h.in0000664000175000017500000000176215110656141020467 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #define OPENTIMELINEIO_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define OPENTIMELINEIO_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define OPENTIMELINEIO_VERSION_PATCH @PROJECT_VERSION_PATCH@ #define OPENTIMELINEIO_VERSION v@PROJECT_VERSION_MAJOR@_@PROJECT_VERSION_MINOR@_@PROJECT_VERSION_PATCH@ #include "opentime/rationalTime.h" #include "opentime/timeRange.h" #include "opentime/timeTransform.h" #include "opentime/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { using opentime::RationalTime; using opentime::TimeRange; using opentime::TimeTransform; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION /// @brief Convenience macro for the full namespace of OpenTimelineIO API. /// /// This can be used in place of the full namespace, e.g.: /// /// OTIO_NS::Track* track = new OTIO_NS::Track; /// /// #define OTIO_NS opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/errorStatus.cpp0000664000175000017500000000555515110656141021271 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/errorStatus.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { std::string ErrorStatus::outcome_to_string(Outcome o) { switch (o) { case OK: return std::string(); case NOT_IMPLEMENTED: return "method not implemented for this class"; case UNRESOLVED_OBJECT_REFERENCE: return "unresolved object reference encountered"; case DUPLICATE_OBJECT_REFERENCE: return "duplicate object reference encountered"; case MALFORMED_SCHEMA: return "schema specifier is malformed/illegal"; case JSON_PARSE_ERROR: return "JSON parse error"; case CHILD_ALREADY_PARENTED: return "child already has a parent"; case FILE_OPEN_FAILED: return "failed to open file for reading"; case FILE_WRITE_FAILED: return "failed to open file for writing"; case SCHEMA_ALREADY_REGISTERED: return "schema has already been registered"; case SCHEMA_NOT_REGISTERED: return "schema is not registered/known"; case KEY_NOT_FOUND: return "key not present reading from dictionary"; case ILLEGAL_INDEX: return "illegal index"; case TYPE_MISMATCH: return "type mismatch while decoding"; case INTERNAL_ERROR: return "internal error (aka \"this code has a bug\")"; case NOT_DESCENDED_FROM: return "item is not a descendent of specified object"; case NOT_A_CHILD_OF: return "item is not a child of specified object"; case NOT_AN_ITEM: return "object is not descendent of Item type"; case SCHEMA_VERSION_UNSUPPORTED: return "unsupported schema version"; case NOT_A_CHILD: return "item has no parent"; case CANNOT_COMPUTE_AVAILABLE_RANGE: return "Cannot compute available range"; case INVALID_TIME_RANGE: return "computed time range would be invalid"; case OBJECT_WITHOUT_DURATION: return "cannot compute duration on this type of object"; case CANNOT_TRIM_TRANSITION: return "cannot trim transition"; case CANNOT_COMPUTE_BOUNDS: return "cannot compute image bounds"; case MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY: return "active key not found in media references"; case MEDIA_REFERENCES_CONTAIN_EMPTY_KEY: return "the media references cannot contain an empty key"; case NOT_A_GAP: return "object is not descendent of Gap type"; default: return "unknown/illegal ErrorStatus::Outcome code"; }; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/unknownSchema.cpp0000664000175000017500000000167615110656141021554 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/unknownSchema.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { UnknownSchema::UnknownSchema( std::string const& original_schema_name, int original_schema_version) : _original_schema_name(original_schema_name) , _original_schema_version(original_schema_version) {} UnknownSchema::~UnknownSchema() {} bool UnknownSchema::read_from(Reader& reader) { _data.swap(reader._dict); _data.erase("OTIO_SCHEMA"); return true; } void UnknownSchema::write_to(Writer& writer) const { for (auto e: _data) { writer.write(e.first, e.second); } } std::string UnknownSchema::_schema_name_for_reference() const { return _original_schema_name; } bool UnknownSchema::is_unknown_schema() const { return true; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/typeRegistry.h0000664000175000017500000002017415110656141021105 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/errorStatus.h" #include "opentimelineio/version.h" #include #include #include #include #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class SerializableObject; class Encoder; class AnyDictionary; /// @name Schema Typedefs /// /// typedefs for the schema downgrading system. /// /// @todo Should we make version an int64_t? That would match what we can /// serialize natively, since we only serialize 64 bit signed ints. ///@{ using schema_version_map = std::unordered_map; using label_to_schema_version_map = std::unordered_map; ///@} extern const label_to_schema_version_map CORE_VERSION_MAP; /// @brief Type registry. class OTIO_API_TYPE TypeRegistry { public: /// @brief Get the type registry singleton. /// /// Access to functions are thread-safe. static TypeRegistry& instance(); /// @brief Register a new schema. /// /// This API call should only be needed by developers who are creating a bridge /// to another language (e.g. Python, Swift). In a C++ environment, prefer /// the templated form of this call. /// /// If the specified schema_name has already been registered, this function does nothing and returns false. bool register_type( std::string const& schema_name, int schema_version, std::type_info const* type, std::function create, std::string const& class_name = ""); /// @brief Register a new SerializableObject class /// /// If the specified schema_name has already been registered, this function does nothing and returns false. /// If you need to provide an alias for a schema name, se register_type_from_existing_type(). template bool register_type() { return register_type( CLASS::Schema::name, CLASS::Schema::version, &typeid(CLASS), []() -> SerializableObject* { return new CLASS; }, CLASS::Schema::name); } /// @brief Register a new schema. /// /// This API call can be used to register an alternate schema name for a class, in /// case a schema name is changed and the old name needs to be allowed as well. /// /// On success, returns true; otherwise, returns false and sets error_status if non-null. bool register_type_from_existing_type( std::string const& schema_name, int schema_version, std::string const& existing_schema_name, ErrorStatus* error_status = nullptr); /// This API call should only be needed by developers who are creating a bridge /// to another language (e.g. Python, Swift). In a C++ environment, prefer /// the templated form of this call. /// @brief Register a function that will upgrade the given schema to version_to_upgrade_to. /// /// Note that as a schema is upgraded, older upgrade functions should be kept around; /// the intent is that each upgrade function upgrades the schema from the version /// just before version_to_upgrade_to. (I.e. all registered upgrade functions are /// run in order, on the same data dictionary.) /// /// Returns false if an upgrade function has been registered for this (schema_name, version) /// pair, or if schema_name itself has not been registered, and true otherwise. bool register_upgrade_function( std::string const& schema_name, int version_to_upgrade_to, std::function upgrade_function); /// @brief Convenience API for C++ developers. /// /// See the documentation of the non-templated register_upgrade_function() for details. template bool register_upgrade_function( int version_to_upgrade_to, std::function upgrade_function) { return register_upgrade_function( CLASS::schema_name, version_to_upgrade_to, upgrade_function); } /// @brief Downgrade function from version_to_downgrade_from to /// version_to_downgrade_from - 1 bool register_downgrade_function( std::string const& schema_name, int version_to_downgrade_from, std::function downgrade_function); /// @brief Convenience API for C++ developers. /// /// See the documentation of the non-templated register_downgrade_function() for details. template bool register_downgrade_function( int version_to_upgrade_to, std::function upgrade_function) { return register_downgrade_function( CLASS::schema_name, version_to_upgrade_to, upgrade_function); } /// @brief Return the instance from the given schema. SerializableObject* instance_from_schema( std::string const& schema_name, int schema_version, AnyDictionary& dict, ErrorStatus* error_status = nullptr) { return _instance_from_schema( schema_name, schema_version, dict, false /* internal_read */, error_status); } /// @brief For use by external bridging systems. bool set_type_record( SerializableObject*, std::string const& schema_name, ErrorStatus* error_status = nullptr); /// @brief For inspecting the type registry, build a map of schema name to version. void type_version_map(schema_version_map& result); private: TypeRegistry(); TypeRegistry(TypeRegistry const&) = delete; TypeRegistry& operator=(TypeRegistry const&) = delete; class _TypeRecord { std::string schema_name; int schema_version; std::string class_name; std::function create; std::map> upgrade_functions; std::map> downgrade_functions; _TypeRecord( std::string _schema_name, int _schema_version, std::string _class_name, std::function _create) { this->schema_name = _schema_name; this->schema_version = _schema_version; this->class_name = _class_name; this->create = _create; } SerializableObject* create_object() const; friend class TypeRegistry; friend class SerializableObject; friend class CloningEncoder; }; // helper functions for lookup _TypeRecord* _find_type_record(std::string const& key) { auto it = _type_records.find(key); return it == _type_records.end() ? nullptr : it->second; } SerializableObject* _instance_from_schema( std::string schema_name, int schema_version, AnyDictionary& dict, bool internal_read, ErrorStatus* error_status = nullptr); static std::pair _schema_and_version_from_label(std::string const& label); _TypeRecord* _lookup_type_record(std::string const& schema_name); _TypeRecord* _lookup_type_record(std::type_info const& type); std::mutex _registry_mutex; std::map _type_records; std::map _type_records_by_type_name; friend class SerializableObject; friend class CloningEncoder; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/timeline.cpp0000664000175000017500000000407615110656141020537 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/timeline.h" #include "opentimelineio/clip.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Timeline::Timeline( std::string const& name, std::optional global_start_time, AnyDictionary const& metadata) : SerializableObjectWithMetadata(name, metadata) , _global_start_time(global_start_time) , _tracks(new Stack("tracks")) {} Timeline::~Timeline() {} void Timeline::set_tracks(Stack* stack) { _tracks = stack ? stack : new Stack("tracks"); } bool Timeline::read_from(Reader& reader) { return reader.read("tracks", &_tracks) && reader.read_if_present("global_start_time", &_global_start_time) && Parent::read_from(reader); } void Timeline::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("global_start_time", _global_start_time); writer.write("tracks", _tracks); } std::vector Timeline::video_tracks() const { std::vector result; for (auto c: _tracks->children()) { if (auto t = dynamic_retainer_cast(c)) { if (t->kind() == Track::Kind::video) { result.push_back(t); } } } return result; } std::vector Timeline::audio_tracks() const { std::vector result; for (auto c: _tracks->children()) { if (auto t = dynamic_retainer_cast(c)) { if (t->kind() == Track::Kind::audio) { result.push_back(t); } } } return result; } std::vector> Timeline::find_clips( ErrorStatus* error_status, std::optional const& search_range, bool shallow_search) const { return _tracks.value->find_clips( error_status, search_range, shallow_search); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/trackAlgorithm.cpp0000664000175000017500000000672415110656141021706 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/trackAlgorithm.h" #include "opentimelineio/transition.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Track* track_trimmed_to_range( Track* in_track, TimeRange trim_range, ErrorStatus* error_status) { Track* new_track = dynamic_cast(in_track->clone(error_status)); if (is_error(error_status) || !new_track) { return nullptr; } auto track_map = new_track->range_of_all_children(error_status); if (is_error(error_status)) { return nullptr; } std::vector children_copy( new_track->children().begin(), new_track->children().end()); for (size_t i = children_copy.size(); i--;) { Composable* child = children_copy[i]; auto child_range_it = track_map.find(child); if (child_range_it == track_map.end()) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::CANNOT_COMPUTE_AVAILABLE_RANGE, "failed to find child in track_map map"); } return nullptr; } auto child_range = child_range_it->second; if (!trim_range.intersects(child_range)) { new_track->remove_child(static_cast(i), error_status); if (is_error(error_status)) { return nullptr; } } else if (!trim_range.contains(child_range)) { if (dynamic_cast(child)) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::CANNOT_TRIM_TRANSITION, "Cannot trim in the middle of a transition"); } return nullptr; } Item* child_item = dynamic_cast(child); if (!child_item) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::TYPE_MISMATCH, "Expected child of type Item*", child); } return nullptr; } auto child_source_range = child_item->trimmed_range(error_status); if (is_error(error_status)) { return nullptr; } if (trim_range.start_time() > child_range.start_time()) { auto trim_amount = trim_range.start_time() - child_range.start_time(); child_source_range = TimeRange( child_source_range.start_time() + trim_amount, child_source_range.duration() - trim_amount); } auto trim_end = trim_range.end_time_exclusive(); auto child_end = child_range.end_time_exclusive(); if (trim_end < child_end) { auto trim_amount = child_end - trim_end; child_source_range = TimeRange( child_source_range.start_time(), child_source_range.duration() - trim_amount); } child_item->set_source_range(child_source_range); } } return new_track; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/composable.cpp0000664000175000017500000000310715110656141021047 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/composable.h" #include "opentimelineio/composition.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Composable::Composable(std::string const& name, AnyDictionary const& metadata) : Parent(name, metadata) , _parent(nullptr) {} Composable::~Composable() {} bool Composable::visible() const { return true; } bool Composable::overlapping() const { return false; } bool Composable::_set_parent(Composition* new_parent) noexcept { if (new_parent && _parent) { return false; } _parent = new_parent; return true; } Composable* Composable::_highest_ancestor() noexcept { Composable* c = this; for (; c->_parent; c = c->_parent) { /* empty */ } return c; } bool Composable::read_from(Reader& reader) { return Parent::read_from(reader); } void Composable::write_to(Writer& writer) const { Parent::write_to(writer); } RationalTime Composable::duration(ErrorStatus* error_status) const { if (error_status) { *error_status = ErrorStatus( ErrorStatus::OBJECT_WITHOUT_DURATION, "Cannot determine duration from this kind of object", this); } return RationalTime(); } std::optional Composable::available_image_bounds(ErrorStatus* error_status) const { *error_status = ErrorStatus::NOT_IMPLEMENTED; return std::optional(); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/safely_typed_any.h0000664000175000017500000000615415110656141021734 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once /// @file safely_typed_any.h /// /// This file/interface exists only so that we can package/unpackage /// types with code compiled in one specific library to avoid the type-aliasing /// problem that any's are subject to. /// /// Specifically, if you put the same type T in an any from two different /// libraries across a shared-library boundary, then the actual typeid the any /// records depends on the library that actually packaged the any. Ditto when /// trying to pull it out. /// /// The solution is to have all the unpacking/packing code for the types you /// care about be instantiated not in headers, but in source code, within one /// common library. That's why the seemingly silly code in safely_typed_any.cpp /// exists. #include "opentime/rationalTime.h" #include "opentime/timeRange.h" #include "opentime/timeTransform.h" #include "opentimelineio/color.h" #include "opentimelineio/serializableObject.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @name Any Create ///@{ std::any create_safely_typed_any(bool&&); std::any create_safely_typed_any(int&&); std::any create_safely_typed_any(int64_t&&); std::any create_safely_typed_any(uint64_t&&); std::any create_safely_typed_any(double&&); std::any create_safely_typed_any(std::string&&); std::any create_safely_typed_any(RationalTime&&); std::any create_safely_typed_any(TimeRange&&); std::any create_safely_typed_any(Color&&); std::any create_safely_typed_any(TimeTransform&&); std::any create_safely_typed_any(IMATH_NAMESPACE::V2d&&); std::any create_safely_typed_any(IMATH_NAMESPACE::Box2d&&); std::any create_safely_typed_any(AnyVector&&); std::any create_safely_typed_any(AnyDictionary&&); std::any create_safely_typed_any(SerializableObject*); ///@} /// @name Any Casting ///@{ bool safely_cast_bool_any(std::any const& a); int safely_cast_int_any(std::any const& a); int64_t safely_cast_int64_any(std::any const& a); uint64_t safely_cast_uint64_any(std::any const& a); double safely_cast_double_any(std::any const& a); std::string safely_cast_string_any(std::any const& a); RationalTime safely_cast_rational_time_any(std::any const& a); TimeRange safely_cast_time_range_any(std::any const& a); TimeTransform safely_cast_time_transform_any(std::any const& a); Color safely_cast_color_any(std::any const& a); IMATH_NAMESPACE::V2d safely_cast_point_any(std::any const& a); IMATH_NAMESPACE::Box2d safely_cast_box_any(std::any const& a); SerializableObject* safely_cast_retainer_any(std::any const& a); AnyDictionary safely_cast_any_dictionary_any(std::any const& a); AnyVector safely_cast_any_vector_any(std::any const& a); /// @bug Don't use these unless you know what you're doing... AnyDictionary& temp_safely_cast_any_dictionary_any(std::any const& a); AnyVector& temp_safely_cast_any_vector_any(std::any const& a); ///@} }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/mediaReference.h0000664000175000017500000000512415110656141021267 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/serializableObjectWithMetadata.h" #include "opentimelineio/version.h" #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { using namespace opentime; /// @brief A reference to a piece of media, for example a movie on a clip. class OTIO_API_TYPE MediaReference : public SerializableObjectWithMetadata { public: /// @brief This struct provides the MediaReference schema. struct Schema { static auto constexpr name = "MediaReference"; static int constexpr version = 1; }; using Parent = SerializableObjectWithMetadata; /// @brief Create a new media reference. /// /// @param name The name of the media reference. /// @param available_range The available range of the media reference. /// @param metadata The metadata for the media reference. /// @param available_image_bounds The spatial bounds of the media reference. OTIO_API MediaReference( std::string const& name = std::string(), std::optional const& available_range = std::nullopt, AnyDictionary const& metadata = AnyDictionary(), std::optional const& available_image_bounds = std::nullopt); /// @brief Return the available range of the media reference. std::optional available_range() const noexcept { return _available_range; } /// @brief Set the available range of the media reference. void set_available_range(std::optional const& available_range) { _available_range = available_range; } /// @brief Return whether the reference is missing. virtual bool is_missing_reference() const; /// @brief Return the spatial bounds of the media reference. std::optional available_image_bounds() const { return _available_image_bounds; } /// @brief Set the spatial bounds of the media reference. void set_available_image_bounds( std::optional const& available_image_bounds) { _available_image_bounds = available_image_bounds; } protected: virtual ~MediaReference(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::optional _available_range; std::optional _available_image_bounds; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/composition.cpp0000664000175000017500000004352415110656141021275 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/composition.h" #include "opentimelineio/clip.h" #include "opentimelineio/vectorIndexing.h" #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Composition::Composition( std::string const& name, std::optional const& source_range, AnyDictionary const& metadata, std::vector const& effects, std::vector const& markers, std::optional const& color) : Parent( name, source_range, metadata, effects, markers, /*enabled*/ true, color) {} Composition::~Composition() { clear_children(); } std::string Composition::composition_kind() const { static std::string kind = "Composition"; return kind; } void Composition::clear_children() { for (Composable* child: _children) { child->_set_parent(nullptr); } _children.clear(); _child_set.clear(); } bool Composition::set_children( std::vector const& children, ErrorStatus* error_status) { for (auto child: children) { if (child->parent()) { if (error_status) { *error_status = ErrorStatus::CHILD_ALREADY_PARENTED; } return false; } } for (auto child: children) { child->_set_parent(this); } _children = decltype(_children)(children.begin(), children.end()); _child_set = std::set(children.begin(), children.end()); return true; } bool Composition::insert_child( int index, Composable* child, ErrorStatus* error_status) { if (child->parent()) { if (error_status) { *error_status = ErrorStatus::CHILD_ALREADY_PARENTED; } return false; } child->_set_parent(this); index = adjusted_vector_index(index, _children); if (index >= int(_children.size())) { _children.emplace_back(child); } else { _children.insert(_children.begin() + std::max(index, 0), child); } _child_set.insert(child); return true; } bool Composition::set_child(int index, Composable* child, ErrorStatus* error_status) { index = adjusted_vector_index(index, _children); if (index < 0 || index >= int(_children.size())) { if (error_status) { *error_status = ErrorStatus::ILLEGAL_INDEX; } return false; } if (_children[index] != child) { if (child->parent()) { if (error_status) { *error_status = ErrorStatus::CHILD_ALREADY_PARENTED; } return false; } _children[index]->_set_parent(nullptr); _child_set.erase(_children[index]); child->_set_parent(this); _children[index] = child; _child_set.insert(child); } return true; } bool Composition::remove_child(int index, ErrorStatus* error_status) { if (_children.empty()) { if (error_status) { *error_status = ErrorStatus::ILLEGAL_INDEX; } return false; } index = adjusted_vector_index(index, _children); _child_set.erase(_children[index]); if (size_t(index) >= _children.size()) { _children.back()->_set_parent(nullptr); _children.pop_back(); } else { index = std::max(index, 0); _children[index]->_set_parent(nullptr); _children.erase(_children.begin() + index); } return true; } int Composition::index_of_child(Composable const* child, ErrorStatus* error_status) const { for (size_t i = 0; i < _children.size(); i++) { if (_children[i] == child) { return int(i); } } if (error_status) { *error_status = ErrorStatus::NOT_A_CHILD_OF; error_status->object_details = this; } return -1; } bool Composition::read_from(Reader& reader) { if (reader.read("children", &_children) && Parent::read_from(reader)) { for (Composable* child: _children) { if (!child->_set_parent(this)) { reader.error(ErrorStatus::CHILD_ALREADY_PARENTED); return false; } } } return true; } void Composition::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("children", _children); } bool Composition::is_parent_of(Composable const* other) const { Composition const* cur_parent = other->_parent; if (cur_parent == this) return true; std::set visited; while (cur_parent && visited.count(cur_parent) == 0) { if (cur_parent == this) return true; visited.insert(cur_parent); cur_parent = cur_parent->_parent; } return false; } std::pair, std::optional> Composition::handles_of_child( Composable const* /* child */, ErrorStatus* /* error_status */) const { return std::make_pair( std::optional(), std::optional()); } std::vector Composition::_path_from_child( Composable const* child, ErrorStatus* error_status) const { auto current = child->parent(); std::vector parents{ current }; while (current != this) { current = current->parent(); if (!current) { if (error_status) { *error_status = ErrorStatus::NOT_DESCENDED_FROM; error_status->object_details = this; } return parents; } parents.push_back(current); } return parents; } TimeRange Composition::range_of_child_at_index(int /* index */, ErrorStatus* error_status) const { if (error_status) { *error_status = ErrorStatus::NOT_IMPLEMENTED; } return TimeRange(); } TimeRange Composition::trimmed_range_of_child_at_index( int /* index */, ErrorStatus* error_status) const { if (error_status) { *error_status = ErrorStatus::NOT_IMPLEMENTED; } return TimeRange(); } std::map Composition::range_of_all_children(ErrorStatus* error_status) const { if (error_status) { *error_status = ErrorStatus::NOT_IMPLEMENTED; } return std::map(); } // XXX should have reference_space argument or something TimeRange Composition::range_of_child(Composable const* child, ErrorStatus* error_status) const { auto parents = _path_from_child(child, error_status); if (is_error(error_status)) { return TimeRange(); } Composition const* reference_space = this; // XXX std::optional result_range; auto current = child; assert(!parents.empty()); for (auto parent: parents) { const int index = parent->index_of_child(current, error_status); if (is_error(error_status)) { return TimeRange(); } auto parent_range = parent->range_of_child_at_index(index, error_status); if (is_error(error_status)) { return TimeRange(); } if (!result_range) { result_range = parent_range; current = parent; continue; } result_range = TimeRange( result_range->start_time() + parent_range.start_time(), result_range->duration()); current = parent; } return (reference_space != this) ? transformed_time_range( *result_range, reference_space, error_status) : *result_range; } // XXX should have reference_space argument or something std::optional Composition::trimmed_range_of_child( Composable const* child, ErrorStatus* error_status) const { auto parents = _path_from_child(child, error_status); if (is_error(error_status)) { return TimeRange(); } std::optional result_range; auto current = child; assert(!parents.empty()); for (auto parent: parents) { const int index = parent->index_of_child(current, error_status); if (is_error(error_status)) { return TimeRange(); } auto parent_range = parent->trimmed_range_of_child_at_index(index, error_status); if (is_error(error_status)) { return TimeRange(); } if (!result_range) { result_range = parent_range; current = parent; continue; } result_range = TimeRange( result_range->start_time() + parent_range.start_time(), result_range->duration()); } if (!source_range()) { return result_range; } auto new_start_time = std::max(source_range()->start_time(), result_range->start_time()); if (new_start_time > result_range->end_time_exclusive()) { return std::nullopt; } auto new_duration = std::min( result_range->end_time_exclusive(), source_range()->end_time_exclusive()) - new_start_time; if (new_duration.value() < 0) { return std::nullopt; } return TimeRange(new_start_time, new_duration); } std::vector Composition::_children_at_time(RationalTime t, ErrorStatus* error_status) const { std::vector result; // range_of_child_at_index is O(i), so this loop is quadratic: for (size_t i = 0; i < _children.size() && !is_error(error_status); i++) { if (range_of_child_at_index(int(i), error_status).contains(t)) { result.push_back(_children[i]); } } return result; } std::optional Composition::trim_child_range(TimeRange child_range) const { if (!source_range()) { return child_range; } const TimeRange sr = *source_range(); bool past_end_time = sr.start_time() >= child_range.end_time_exclusive(); bool before_start_time = sr.end_time_exclusive() <= child_range.start_time(); if (past_end_time || before_start_time) { return std::nullopt; } if (child_range.start_time() < sr.start_time()) { child_range = TimeRange::range_from_start_end_time( sr.start_time(), child_range.end_time_exclusive()); } if (child_range.end_time_exclusive() > sr.end_time_exclusive()) { child_range = TimeRange::range_from_start_end_time( child_range.start_time(), sr.end_time_exclusive()); } return child_range; } bool Composition::has_child(Composable* child) const { return _child_set.find(child) != _child_set.end(); } SerializableObject::Retainer Composition::child_at_time( RationalTime const& search_time, ErrorStatus* error_status, bool shallow_search) const { Retainer result; auto range_map = range_of_all_children(error_status); if (is_error(error_status)) { return result; } // find the first item whose end_time_exclusive is after the const auto first_inside_range = _bisect_left( search_time, [&range_map](Composable* child) { return range_map[child].end_time_exclusive(); }, error_status); if (is_error(error_status)) { return result; } // find the last item whose start_time is before the const auto last_in_range = _bisect_right( search_time, [&range_map](Composable* child) { return range_map[child].start_time(); }, error_status, first_inside_range); if (is_error(error_status)) { return result; } // limit the search to children who are in the search_range std::vector> possible_matches; for (auto child = _children.begin() + first_inside_range; child < _children.begin() + last_in_range; ++child) { possible_matches.push_back(child->value); } for (const auto& thing: possible_matches) { if (range_map[thing].overlaps(search_time)) { result = thing; break; } } // if the search cannot or should not continue auto composition = Retainer(dynamic_cast(result.value)); if (!result || shallow_search || !composition) { return result; } // before you recurse, you have to transform the time into the // space of the child const auto child_search_time = transformed_time(search_time, composition.value, error_status); if (is_error(error_status)) { return result; } result = composition.value->child_at_time( child_search_time, error_status, shallow_search); if (is_error(error_status)) { return result; } return result; } std::vector> Composition::children_in_range( TimeRange const& search_range, ErrorStatus* error_status) const { std::vector> children; auto range_map = range_of_all_children(error_status); if (is_error(error_status)) { return children; } // find the first item whose end_time_inclusive is after the // start_time of the search range const auto first_inside_range = _bisect_left( search_range.start_time(), [&range_map](Composable* child) { return range_map[child].end_time_inclusive(); }, error_status); if (is_error(error_status)) { return children; } // find the last item whose start_time is before the // end_time_inclusive of the search_range const auto last_in_range = _bisect_right( search_range.end_time_inclusive(), [&range_map](Composable* child) { return range_map[child].start_time(); }, error_status, first_inside_range); if (is_error(error_status)) { return children; } // limit the search to children who are in the search_range for (auto child = _children.begin() + first_inside_range; child < _children.begin() + last_in_range; ++child) { children.push_back(child->value); } return children; } int64_t Composition::_bisect_right( RationalTime const& tgt, std::function const& key_func, ErrorStatus* error_status, std::optional lower_search_bound, std::optional upper_search_bound) const { if (*lower_search_bound < 0) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::Outcome::INTERNAL_ERROR, "lower_search_bound must be non-negative"); } return 0; } if (!upper_search_bound) { upper_search_bound = std::optional(_children.size()); } int64_t midpoint_index = 0; while (*lower_search_bound < *upper_search_bound) { midpoint_index = static_cast( std::floor((*lower_search_bound + *upper_search_bound) / 2.0)); if (tgt < key_func(_children[midpoint_index])) { upper_search_bound = midpoint_index; } else { lower_search_bound = midpoint_index + 1; } } return *lower_search_bound; } int64_t Composition::_bisect_left( RationalTime const& tgt, std::function const& key_func, ErrorStatus* error_status, std::optional lower_search_bound, std::optional upper_search_bound) const { if (*lower_search_bound < 0) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::Outcome::INTERNAL_ERROR, "lower_search_bound must be non-negative"); } return 0; } if (!upper_search_bound) { upper_search_bound = std::optional(_children.size()); } int64_t midpoint_index = 0; while (*lower_search_bound < *upper_search_bound) { midpoint_index = static_cast( std::floor((*lower_search_bound + *upper_search_bound) / 2.0)); if (key_func(_children[midpoint_index]) < tgt) { lower_search_bound = midpoint_index + 1; } else { upper_search_bound = midpoint_index; } } return *lower_search_bound; } bool Composition::has_clips() const { for (auto child: children()) { if (dynamic_cast(child.value)) { return true; } else if (auto child_comp = dynamic_cast(child.value)) { if (child_comp->has_clips()) { return true; } } } return false; } std::vector> Composition::find_clips( ErrorStatus* error_status, std::optional const& search_range, bool shallow_search) const { return find_children(error_status, search_range, shallow_search); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/serialization.h0000664000175000017500000000205315110656141021244 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/errorStatus.h" #include "opentimelineio/typeRegistry.h" #include "opentimelineio/version.h" #include #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Serialize JSON data to a string. OTIO_API std::string serialize_json_to_string( const std::any& value, const schema_version_map* schema_version_targets = nullptr, ErrorStatus* error_status = nullptr, int indent = 4); /// @brief Serialize JSON data to a file. OTIO_API bool serialize_json_to_file( const std::any& value, std::string const& file_name, const schema_version_map* schema_version_targets = nullptr, ErrorStatus* error_status = nullptr, int indent = 4); }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/unknownSchema.h0000664000175000017500000000315615110656141021214 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/serializableObject.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief An unknown schema. class OTIO_API_TYPE UnknownSchema : public SerializableObject { public: /// @brief This struct provides the UnknownSchema schema. struct Schema { static auto constexpr name = "UnknownSchema"; static int constexpr version = 1; }; /// @brief Create a new unknown schema. /// /// @param original_schema_name The original schema name. /// @param original_schema_version The original schema version. UnknownSchema( std::string const& original_schema_name, int original_schema_version); /// @brief Return the original schema name. std::string original_schema_name() const noexcept { return _original_schema_name; } /// @brief Return the original schema version. int original_schema_version() const noexcept { return _original_schema_version; } bool read_from(Reader&) override; void write_to(Writer&) const override; bool is_unknown_schema() const override; protected: virtual ~UnknownSchema(); std::string _schema_name_for_reference() const override; private: std::string _original_schema_name; int _original_schema_version; AnyDictionary _data; friend class TypeRegistry; friend class SerializableObject::Writer; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/composable.h0000664000175000017500000000407015110656141020514 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/serializableObjectWithMetadata.h" #include "opentimelineio/version.h" #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class Composition; /// @brief An object that can be composed within a Composition (such as a Track or Stack). class OTIO_API_TYPE Composable : public SerializableObjectWithMetadata { public: /// @brief This struct provides the Composable schema. struct Schema { static auto constexpr name = "Composable"; static int constexpr version = 1; }; using Parent = SerializableObjectWithMetadata; /// @brief Create a new composable. /// /// @param name The name of the composable. /// @param metadata The metadata for the clip. OTIO_API Composable( std::string const& name = std::string(), AnyDictionary const& metadata = AnyDictionary()); /// @brief Return whether the composable is visible. virtual bool visible() const; /// @brief Return whether the composable is overlapping another item. virtual bool overlapping() const; /// @brief Return the parent composition. Composition* parent() const { return _parent; } /// @brief Return the duration of the composable. virtual RationalTime duration(ErrorStatus* error_status = nullptr) const; /// @brief Return the available image bounds. virtual std::optional available_image_bounds(ErrorStatus* error_status = nullptr) const; protected: bool _set_parent(Composition*) noexcept; Composable* _highest_ancestor() noexcept; Composable const* _highest_ancestor() const noexcept { return const_cast(this)->_highest_ancestor(); } virtual ~Composable(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: Composition* _parent; friend class Composition; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/CORE_VERSION_MAP.last.cpp0000664000175000017500000001315115110656316022323 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project // // This document is automatically generated by running the `make version-map` // make target. It is part of the unit tests suite and should be updated // whenever schema versions change. If it needs to be updated, run: `make // version-map-update` and this file should be regenerated. // // This maps a "Label" to a map of Schema name to Schema version. The intent is // that these sets of schemas can be used for compatibility with future // versions of OTIO, so that a newer version of OTIO can target a compatibility // version of an older library. #include "opentimelineio/typeRegistry.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { const label_to_schema_version_map CORE_VERSION_MAP{ { "0.14.0", { { "Adapter", 1 }, { "Clip", 1 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.15.0", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.16.0", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.17.0", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, { "0.18.0", { { "Adapter", 1 }, { "Clip", 2 }, { "Composable", 1 }, { "Composition", 1 }, { "Effect", 1 }, { "ExternalReference", 1 }, { "FreezeFrame", 1 }, { "Gap", 1 }, { "GeneratorReference", 1 }, { "HookScript", 1 }, { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, { "Marker", 2 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, { "PluginManifest", 1 }, { "SchemaDef", 1 }, { "SerializableCollection", 1 }, { "SerializableObject", 1 }, { "SerializableObjectWithMetadata", 1 }, { "Stack", 1 }, { "Test", 1 }, { "TimeEffect", 1 }, { "Timeline", 1 }, { "Track", 1 }, { "Transition", 1 }, { "UnknownSchema", 1 }, } }, // {next} }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/linearTimeWarp.h0000664000175000017500000000317415110656141021317 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/timeEffect.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief A time warp that applies a linear speed up or slow down across the entire clip. class OTIO_API_TYPE LinearTimeWarp : public TimeEffect { public: /// @brief This struct provides the LinearTimeWarp schema. struct Schema { static auto constexpr name = "LinearTimeWarp"; static int constexpr version = 1; }; using Parent = TimeEffect; /// @brief Create a new linear time warp effect. /// /// @param name The name of the time effect object. /// @param effect_name The name of the time effect. /// @param time_scalar The amount to scale the time. /// @param metadata The metadata for the time effect. OTIO_API LinearTimeWarp( std::string const& name = std::string(), std::string const& effect_name = std::string(), double time_scalar = 1, AnyDictionary const& metadata = AnyDictionary()); /// @brief Return the amount to scale the time. double time_scalar() const noexcept { return _time_scalar; } /// @brief Set the amount to scale the time. void set_time_scalar(double time_scalar) noexcept { _time_scalar = time_scalar; } protected: virtual ~LinearTimeWarp(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: double _time_scalar; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/generatorReference.cpp0000664000175000017500000000236415110656141022534 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/generatorReference.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { GeneratorReference::GeneratorReference( std::string const& name, std::string const& generator_kind, std::optional const& available_range, AnyDictionary const& parameters, AnyDictionary const& metadata, std::optional const& available_image_bounds) : Parent(name, available_range, metadata, available_image_bounds) , _generator_kind(generator_kind) , _parameters(parameters) {} GeneratorReference::~GeneratorReference() {} bool GeneratorReference::read_from(Reader& reader) { return reader.read("generator_kind", &_generator_kind) && reader.read("parameters", &_parameters) && Parent::read_from(reader); } void GeneratorReference::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("generator_kind", _generator_kind); writer.write("parameters", _parameters); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/export.h0000664000175000017500000000227515110656141017716 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/export.h" #if defined(OTIO_STATIC) # define OTIO_API # define OTIO_API_TYPE # define OTIO_API_TEMPLATE_CLASS(...) # define OTIO_API_TEMPLATE_STRUCT(...) # define OTIO_LOCAL #else # if defined(OTIO_EXPORTS) # define OTIO_API OPENTIMELINEIO_EXPORT # define OTIO_API_TYPE OPENTIMELINEIO_EXPORT_TYPE # define OTIO_API_TEMPLATE_CLASS(...) \ OPENTIMELINEIO_EXPORT_TEMPLATE(class, __VA_ARGS__) # define OTIO_API_TEMPLATE_STRUCT(...) \ OPENTIMELINEIO_EXPORT_TEMPLATE(struct, __VA_ARGS__) # else # define OTIO_API OPENTIMELINEIO_IMPORT # define OTIO_API_TYPE OPENTIMELINEIO_IMPORT_TYPE # define OTIO_API_TEMPLATE_CLASS(...) \ OPENTIMELINEIO_IMPORT_TEMPLATE(class, __VA_ARGS__) # define OTIO_API_TEMPLATE_STRUCT(...) \ OPENTIMELINEIO_IMPORT_TEMPLATE(struct, __VA_ARGS__) # endif # define OTIO_LOCAL OPENTIMELINEIO_HIDDEN #endif opentimelineio-0.18.1/src/opentimelineio/marker.h0000664000175000017500000000574715110656141017665 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/serializableObjectWithMetadata.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief A marker indicates a marked range of time on an item in a timeline, /// usually with a name, color or other metadata. /// /// The marked range may have a zero duration. The marked range is in the /// owning item's time coordinate system. class OTIO_API_TYPE Marker : public SerializableObjectWithMetadata { public: /// @brief This struct provides the base set of colors. struct Color { static auto constexpr pink = "PINK"; static auto constexpr red = "RED"; static auto constexpr orange = "ORANGE"; static auto constexpr yellow = "YELLOW"; static auto constexpr green = "GREEN"; static auto constexpr cyan = "CYAN"; static auto constexpr blue = "BLUE"; static auto constexpr purple = "PURPLE"; static auto constexpr magenta = "MAGENTA"; static auto constexpr black = "BLACK"; static auto constexpr white = "WHITE"; }; /// @brief This struct provides the Marker schema. struct Schema { static auto constexpr name = "Marker"; static int constexpr version = 2; }; using Parent = SerializableObjectWithMetadata; /// @brief Create a new marker. /// /// @param name The name of the marker. /// @param marked_range The time range of the marker. /// @param color The color associated with the marker. /// @param metadata The metadata for the marker. /// @param comment The text comment for the marker. OTIO_API Marker( std::string const& name = std::string(), TimeRange const& marked_range = TimeRange(), std::string const& color = Color::green, AnyDictionary const& metadata = AnyDictionary(), std::string const& comment = std::string()); /// @brief Return the marker color. std::string color() const noexcept { return _color; } /// @brief Set the marker color. void set_color(std::string const& color) { _color = color; } /// @brief Return the marker time range. TimeRange marked_range() const noexcept { return _marked_range; } /// @brief Set the marker time range. void set_marked_range(TimeRange const& marked_range) noexcept { _marked_range = marked_range; } /// @brief Return the marker comment. std::string comment() const noexcept { return _comment; } /// @brief Set the marker comment. void set_comment(std::string const& comment) { _comment = comment; } protected: virtual ~Marker(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::string _color; TimeRange _marked_range; std::string _comment; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/composition.h0000664000175000017500000002551215110656141020737 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/item.h" #include "opentimelineio/version.h" #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class Clip; /// @brief Base class for an Item that contains Composables. /// /// Should be subclassed (for example by Track Stack), not used directly. class OTIO_API_TYPE Composition : public Item { public: /// @brief This struct provides the Composition schema. struct Schema { static auto constexpr name = "Composition"; static int constexpr version = 1; }; using Parent = Item; /// @brief Create a new composition. /// /// @param name The name of the composition. /// @param source_range The source range of the composition. /// @param metadata The metadata for the composition. /// @param effects The list of effects for the composition. Note that the /// the composition keeps a retainer to each effect. /// @param markers The list of markers for the composition. Note that the /// the composition keeps a retainer to each marker. OTIO_API Composition( std::string const& name = std::string(), std::optional const& source_range = std::nullopt, AnyDictionary const& metadata = AnyDictionary(), std::vector const& effects = std::vector(), std::vector const& markers = std::vector(), std::optional const& color = std::nullopt); /// @brief Return the kind of composition. virtual std::string composition_kind() const; /// @brief Return the list of children. std::vector> const& children() const noexcept { return _children; } /// @brief Clear the children. OTIO_API void clear_children(); /// @brief Set the list of children. Note that the composition keeps a /// retainer to each child. OTIO_API bool set_children( std::vector const& children, ErrorStatus* error_status = nullptr); /// @brief Insert a child. Note that the composition keeps a retainer to the child. OTIO_API bool insert_child( int index, Composable* child, ErrorStatus* error_status = nullptr); /// @brief Set the child at the given index. Note that the composition keeps a /// retainer to the child. OTIO_API bool set_child( int index, Composable* child, ErrorStatus* error_status = nullptr); /// @brief Remove the child at the given index. OTIO_API bool remove_child(int index, ErrorStatus* error_status = nullptr); /// @brief Append the child. Note that the composition keeps a retainer to /// the child. bool append_child(Composable* child, ErrorStatus* error_status = nullptr) { return insert_child(int(_children.size()), child, error_status); } /// @brief Return the index of the given child. OTIO_API int index_of_child( Composable const* child, ErrorStatus* error_status = nullptr) const; /// @brief Return whether this is the parent of the given child. OTIO_API bool is_parent_of(Composable const* other) const; /// @brief Return the in and out handles of the given child. virtual std::pair, std::optional> handles_of_child( Composable const* child, ErrorStatus* error_status = nullptr) const; /// @brief Return the range of the given child. virtual TimeRange range_of_child_at_index( int index, ErrorStatus* error_status = nullptr) const; /// @brief Return the trimmed range of the given child. virtual TimeRange trimmed_range_of_child_at_index( int index, ErrorStatus* error_status = nullptr) const; /// @brief Return the range of the given child. /// /// @todo Leaving out reference_space argument for now. OTIO_API TimeRange range_of_child( Composable const* child, ErrorStatus* error_status = nullptr) const; /// @brief Return the trimmed range of the given child. OTIO_API std::optional trimmed_range_of_child( Composable const* child, ErrorStatus* error_status = nullptr) const; /// @brief Return the given range trimmed to this source range. OTIO_API std::optional trim_child_range(TimeRange child_range) const; /// @brief Return whether this contains the given child. OTIO_API bool has_child(Composable* child) const; /// @brief Return whether this contains any child clips. OTIO_API bool has_clips() const; /// @brief Return the range of all children. virtual std::map range_of_all_children(ErrorStatus* error_status = nullptr) const; /// @brief Return the child that overlaps with the given time. /// /// @param search_time The search time. /// @param error_status The return status. /// @param shallow_search The search is recursive unless shallow_search is /// set to true. OTIO_API Retainer child_at_time( RationalTime const& search_time, ErrorStatus* error_status = nullptr, bool shallow_search = false) const; /// @brief Return all objects within the given search_range. virtual std::vector> children_in_range( TimeRange const& search_range, ErrorStatus* error_status = nullptr) const; /// @brief Find child objects that match the given template type. /// /// @param error_status The return status. /// @param search_range An optional range to limit the search. /// @param shallow_search The search is recursive unless shallow_search is /// set to true. template OTIO_API std::vector> find_children( ErrorStatus* error_status = nullptr, std::optional search_range = std::nullopt, bool shallow_search = false) const; /// @brief Find child clips. /// /// @param error_status The return status. /// @param search_range An optional range to limit the search. /// @param shallow_search The search is recursive unless shallow_search is /// set to true. OTIO_API std::vector> find_clips( ErrorStatus* error_status = nullptr, std::optional const& search_range = std::nullopt, bool shallow_search = false) const; protected: virtual ~Composition(); bool read_from(Reader&) override; void write_to(Writer&) const override; std::vector _path_from_child( Composable const* child, ErrorStatus* error_status = nullptr) const; private: // XXX: python implementation is O(n^2) in number of children std::vector _children_at_time(RationalTime, ErrorStatus* error_status = nullptr) const; // Return the index of the last item in seq such that all e in seq[:index] // have key_func(e) <= tgt, and all e in seq[index:] have key_func(e) > tgt. // // Thus, seq.insert(index, value) will insert value after the rightmost item // such that meets the above condition. // // lower_search_bound and upper_search_bound bound the slice to be searched. // // Assumes that seq is already sorted. int64_t _bisect_right( RationalTime const& tgt, std::function const& key_func, ErrorStatus* error_status = nullptr, std::optional lower_search_bound = std::optional(0), std::optional upper_search_bound = std::nullopt) const; // Return the index of the last item in seq such that all e in seq[:index] // have key_func(e) < tgt, and all e in seq[index:] have key_func(e) >= tgt. // // Thus, seq.insert(index, value) will insert value before the leftmost item // such that meets the above condition. // // lower_search_bound and upper_search_bound bound the slice to be searched. // // Assumes that seq is already sorted. int64_t _bisect_left( RationalTime const& tgt, std::function const& key_func, ErrorStatus* error_status = nullptr, std::optional lower_search_bound = std::optional(0), std::optional upper_search_bound = std::nullopt) const; std::vector> _children; // This is for fast lookup only, and varies automatically // as _children is mutated. std::set _child_set; }; template inline std::vector> Composition::find_children( ErrorStatus* error_status, std::optional search_range, bool shallow_search) const { std::vector> out; std::vector> children; if (search_range) { // limit the search to children who are in the search_range children = children_in_range(*search_range, error_status); if (is_error(error_status)) { return out; } } else { // otherwise search all the children children = _children; } for (const auto& child: children) { if (auto valid_child = dynamic_cast(child.value)) { out.push_back(valid_child); } // if not a shallow_search, for children that are compositions, // recurse into their children if (!shallow_search) { if (auto composition = dynamic_cast(child.value)) { if (search_range) { search_range = transformed_time_range( *search_range, composition, error_status); if (is_error(error_status)) { return out; } } const auto valid_children = composition->find_children( error_status, search_range, shallow_search); if (is_error(error_status)) { return out; } for (const auto& valid_child: valid_children) { out.push_back(valid_child); } } } } return out; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/color.h0000664000175000017500000000537515110656141017517 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include #include #include "opentimelineio/export.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Color consists of red, green, blue, /// and alpha double floating point values, /// allowing conversion between different formats. /// To be considered interoperable, /// the sRGB transfer function encoded values, /// ranging between zero and one, are expected to be accurate /// to within 1/255 of the intended value. /// Round-trip conversions may not be guaranteed outside that. /// This class is meant for use in user interface elements, // like marker or clip coloring, NOT for image pixel content. class OTIO_API_TYPE Color { public: struct Schema { static auto constexpr name = "Color"; static int constexpr version = 1; }; OTIO_API Color( double const r = 1.f, double const g = 1.f, double const b = 1.f, double const a = 1.f, std::string const& name = ""); OTIO_API Color(Color const& other); static const Color pink; static const Color red; static const Color orange; static const Color yellow; static const Color green; static const Color cyan; static const Color blue; static const Color purple; static const Color magenta; static const Color black; static const Color white; static const Color transparent; static Color* from_hex(std::string const& color); static Color* from_int_list(std::vector const& color, int bit_depth); static Color* from_agbr_int(unsigned int agbr) noexcept; static Color* from_float_list(std::vector const& color); friend bool operator==(Color lhs, Color rhs) noexcept { return lhs.to_hex() == rhs.to_hex() && lhs.to_agbr_integer() == rhs.to_agbr_integer(); } OTIO_API std::string to_hex(); OTIO_API std::vector to_rgba_int_list(int base); OTIO_API unsigned int to_agbr_integer(); OTIO_API std::vector to_rgba_float_list(); double r() const { return _r; } double g() const { return _g; } double b() const { return _b; } double a() const { return _a; } std::string name() const { return _name; } void set_r(double r) { _r = r; } void set_g(double g) { _g = g; } void set_b(double b) { _b = b; } void set_a(double a) { _a = a; } void set_name(std::string const& name) { _name = name; } private: double _r; double _g; double _b; double _a; std::string _name; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSIONopentimelineio-0.18.1/src/opentimelineio/item.h0000664000175000017500000001132115110656141017323 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentime/timeRange.h" #include "opentimelineio/color.h" #include "opentimelineio/composable.h" #include "opentimelineio/errorStatus.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class Effect; class Marker; /// @brief An item in the timeline. class OTIO_API_TYPE Item : public Composable { public: /// @brief This struct provides the Item schema. struct Schema { static auto constexpr name = "Item"; static int constexpr version = 1; }; using Parent = Composable; /// @brief Create a new item. /// /// @param name The name of the item. /// @param source_range The source range of the item. /// @param metadata The metadata for the item. /// @param effects The list of effects for the item. Note that the /// the item keeps a retainer to each effect. /// @param markers The list of markers for the item. Note that the /// the item keeps a retainer to each marker. /// @param enabled Whether the item is enabled. OTIO_API Item( std::string const& name = std::string(), std::optional const& source_range = std::nullopt, AnyDictionary const& metadata = AnyDictionary(), std::vector const& effects = std::vector(), std::vector const& markers = std::vector(), bool enabled = true, std::optional const& color = std::nullopt); bool visible() const override; bool overlapping() const override; /// @brief Return whether the item is enabled. bool enabled() const { return _enabled; }; /// @brief Set whether the item is enabled. void set_enabled(bool enabled) { _enabled = enabled; } /// @brief Return the source range of the item. std::optional source_range() const noexcept { return _source_range; } /// @brief Set the source range of the item. void set_source_range(std::optional const& source_range) { _source_range = source_range; } /// @brief Modify the list of effects. std::vector>& effects() noexcept { return _effects; } /// @brief Return the list of effects. std::vector> const& effects() const noexcept { return _effects; } /// @brief Modify the list of markers. std::vector>& markers() noexcept { return _markers; } /// @brief Return the list of markers. std::vector> const& markers() const noexcept { return _markers; } RationalTime duration(ErrorStatus* error_status = nullptr) const override; /// @brief Return the available range of the item. virtual TimeRange available_range(ErrorStatus* error_status = nullptr) const; /// @brief Return the trimmed range of the item. TimeRange trimmed_range(ErrorStatus* error_status = nullptr) const { return _source_range ? *_source_range : available_range(error_status); } /// @brief Return the visible range of the item. OTIO_API TimeRange visible_range(ErrorStatus* error_status = nullptr) const; /// @brief Return the trimmed range of the item in the parent's time. OTIO_API std::optional trimmed_range_in_parent(ErrorStatus* error_status = nullptr) const; /// @brief Return the range of the item in the parent's time. OTIO_API TimeRange range_in_parent(ErrorStatus* error_status = nullptr) const; /// @brief Return the time transformed to another item in the hierarchy. OTIO_API RationalTime transformed_time( RationalTime time, Item const* to_item, ErrorStatus* error_status = nullptr) const; /// @brief Return the time range transformed to another item in the hierarchy. OTIO_API TimeRange transformed_time_range( TimeRange time_range, Item const* to_item, ErrorStatus* error_status = nullptr) const; std::optional color() const noexcept { return _color; } /// @brief Set the color of the item. void set_color(std::optional const& color) { _color = color; } protected: virtual ~Item(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::optional _source_range; std::vector> _effects; std::vector> _markers; std::optional _color; bool _enabled; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/imageSequenceReference.cpp0000664000175000017500000002033115110656141023313 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/imageSequenceReference.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { ImageSequenceReference::ImageSequenceReference( std::string const& target_url_base, std::string const& name_prefix, std::string const& name_suffix, int start_frame, int frame_step, double rate, int frame_zero_padding, MissingFramePolicy const missing_frame_policy, std::optional const& available_range, AnyDictionary const& metadata, std::optional const& available_image_bounds) : Parent(std::string(), available_range, metadata, available_image_bounds) , _target_url_base(target_url_base) , _name_prefix(name_prefix) , _name_suffix(name_suffix) , _start_frame{ start_frame } , _frame_step{ frame_step } , _rate{ rate } , _frame_zero_padding{ frame_zero_padding } , _missing_frame_policy{ missing_frame_policy } {} ImageSequenceReference::~ImageSequenceReference() {} RationalTime ImageSequenceReference::frame_duration() const noexcept { return RationalTime((double) _frame_step, _rate); } int ImageSequenceReference::end_frame() const { if (!this->available_range().has_value()) { return _start_frame; } int num_frames = this->available_range().value().duration().to_frames(_rate); // Subtract 1 for inclusive frame ranges return (_start_frame + num_frames - 1); } int ImageSequenceReference::number_of_images_in_sequence() const { if (!this->available_range().has_value()) { return 0; } double playback_rate = (_rate / (double) _frame_step); int num_frames = this->available_range().value().duration().to_frames(playback_rate); return num_frames; } int ImageSequenceReference::frame_for_time( RationalTime const& time, ErrorStatus* error_status) const { if (!this->available_range().has_value() || !this->available_range().value().contains(time)) { if (error_status) { *error_status = ErrorStatus(ErrorStatus::INVALID_TIME_RANGE); } return 0; } RationalTime start = this->available_range().value().start_time(); RationalTime duration_from_start = (time - start); int frame_offset = duration_from_start.to_frames(_rate); if (error_status) { *error_status = ErrorStatus(ErrorStatus::OK); } return (_start_frame + frame_offset); } std::string ImageSequenceReference::target_url_for_image_number( int image_number, ErrorStatus* error_status) const { if (_rate == 0) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::ILLEGAL_INDEX, "Zero rate sequence has no frames."); } return std::string(); } else if ( !this->available_range().has_value() || this->available_range().value().duration().value() == 0) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::ILLEGAL_INDEX, "Zero duration sequences has no frames."); } return std::string(); } else if (image_number >= this->number_of_images_in_sequence()) { if (error_status) { *error_status = ErrorStatus(ErrorStatus::ILLEGAL_INDEX); } return std::string(); } const int file_image_num = _start_frame + (image_number * _frame_step); const bool is_negative = (file_image_num < 0); std::string image_num_string = std::to_string(abs(file_image_num)); std::string zero_pad = std::string(); if (static_cast(image_num_string.length()) < _frame_zero_padding) { zero_pad = std::string(_frame_zero_padding - image_num_string.length(), '0'); } std::string sign = std::string(); if (is_negative) { sign = "-"; } // If the base does not include a trailing slash, add it std::string path_sep = std::string(); const auto target_url_base_len = _target_url_base.length(); if (target_url_base_len > 0 && _target_url_base.compare(target_url_base_len - 1, 1, "/") != 0) { path_sep = "/"; } std::string out_string = _target_url_base + path_sep + _name_prefix + sign + zero_pad + image_num_string + _name_suffix; if (error_status) { *error_status = ErrorStatus(ErrorStatus::OK); } return out_string; } RationalTime ImageSequenceReference::presentation_time_for_image_number( int image_number, ErrorStatus* error_status) const { if (image_number >= this->number_of_images_in_sequence()) { if (error_status) { *error_status = ErrorStatus(ErrorStatus::ILLEGAL_INDEX); } return RationalTime(); } auto first_frame_time = this->available_range().value().start_time(); auto time_multiplier = TimeTransform(first_frame_time, image_number, -1); return time_multiplier.applied_to(frame_duration()); } bool ImageSequenceReference::read_from(Reader& reader) { int64_t start_frame_value = 0; int64_t frame_step_value = 0; int64_t frame_zero_padding_value = 0; auto result = reader.read("target_url_base", &_target_url_base) && reader.read("name_prefix", &_name_prefix) && reader.read("name_suffix", &_name_suffix) && reader.read("start_frame", &start_frame_value) && reader.read("frame_step", &frame_step_value) && reader.read("rate", &_rate) && reader.read("frame_zero_padding", &frame_zero_padding_value); _start_frame = static_cast(start_frame_value); _frame_step = static_cast(frame_step_value); _frame_zero_padding = static_cast(frame_zero_padding_value); std::string missing_frame_policy_value; result&& reader.read("missing_frame_policy", &missing_frame_policy_value); if (!result) { return result; } if (missing_frame_policy_value == "error") { _missing_frame_policy = MissingFramePolicy::error; } else if (missing_frame_policy_value == "black") { _missing_frame_policy = MissingFramePolicy::black; } else if (missing_frame_policy_value == "hold") { _missing_frame_policy = MissingFramePolicy::hold; } else { // Unrecognized value ErrorStatus error_status = ErrorStatus( ErrorStatus::JSON_PARSE_ERROR, "Unknown missing_frame_policy: " + missing_frame_policy_value); reader.error(error_status); return false; } return result && Parent::read_from(reader); } void ImageSequenceReference::write_to(Writer& writer) const { int64_t start_frame_value = static_cast(_start_frame); int64_t frame_step_value = static_cast(_frame_step); int64_t frame_zero_padding_value = static_cast(_frame_zero_padding); Parent::write_to(writer); writer.write("target_url_base", _target_url_base); writer.write("name_prefix", _name_prefix); writer.write("name_suffix", _name_suffix); writer.write("start_frame", start_frame_value); writer.write("frame_step", frame_step_value); writer.write("rate", _rate); writer.write("frame_zero_padding", frame_zero_padding_value); std::string missing_frame_policy_value; switch (_missing_frame_policy) { case MissingFramePolicy::error: missing_frame_policy_value = "error"; break; case MissingFramePolicy::black: missing_frame_policy_value = "black"; break; case MissingFramePolicy::hold: missing_frame_policy_value = "hold"; break; } writer.write("missing_frame_policy", missing_frame_policy_value); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/linearTimeWarp.cpp0000664000175000017500000000147415110656141021653 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/linearTimeWarp.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { LinearTimeWarp::LinearTimeWarp( std::string const& name, std::string const& effect_name, double time_scalar, AnyDictionary const& metadata) : Parent(name, effect_name, metadata) , _time_scalar(time_scalar) {} LinearTimeWarp::~LinearTimeWarp() {} bool LinearTimeWarp::read_from(Reader& reader) { return reader.read("time_scalar", &_time_scalar) && Parent::read_from(reader); } void LinearTimeWarp::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("time_scalar", _time_scalar); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/track.cpp0000664000175000017500000001767215110656141020043 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/track.h" #include "opentimelineio/clip.h" #include "opentimelineio/gap.h" #include "opentimelineio/transition.h" #include "opentimelineio/vectorIndexing.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Track::Track( std::string const& name, std::optional const& source_range, std::string const& kind, AnyDictionary const& metadata, std::optional const& color) : Parent( name, source_range, metadata, std::vector(), std::vector(), color) , _kind(kind) {} Track::~Track() {} std::string Track::composition_kind() const { static std::string kind = "Track"; return kind; } bool Track::read_from(Reader& reader) { return reader.read("kind", &_kind) && Parent::read_from(reader); } void Track::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("kind", _kind); } TimeRange Track::range_of_child_at_index(int index, ErrorStatus* error_status) const { index = adjusted_vector_index(index, children()); if (index < 0 || index >= int(children().size())) { if (error_status) { *error_status = ErrorStatus::ILLEGAL_INDEX; } return TimeRange(); } Composable* child = children()[index]; RationalTime child_duration = child->duration(error_status); if (is_error(error_status)) { return TimeRange(); } RationalTime start_time(0, child_duration.rate()); for (int i = 0; i < index; i++) { Composable* child2 = children()[i]; if (!child2->overlapping()) { start_time += children()[i]->duration(error_status); } if (is_error(error_status)) { return TimeRange(); } } if (auto transition = dynamic_cast(child)) { start_time -= transition->in_offset(); } return TimeRange(start_time, child_duration); } TimeRange Track::trimmed_range_of_child_at_index(int index, ErrorStatus* error_status) const { auto child_range = range_of_child_at_index(index, error_status); if (is_error(error_status)) { return child_range; } auto trimmed_range = trim_child_range(child_range); if (!trimmed_range) { if (error_status) { *error_status = ErrorStatus::INVALID_TIME_RANGE; } return TimeRange(); } return *trimmed_range; } TimeRange Track::available_range(ErrorStatus* error_status) const { RationalTime duration; for (const auto& child: children()) { if (auto item = dynamic_retainer_cast(child)) { duration += item->duration(error_status); if (is_error(error_status)) { return TimeRange(); } } } if (!children().empty()) { if (auto transition = dynamic_retainer_cast(children().front())) { duration += transition->in_offset(); } if (auto transition = dynamic_retainer_cast(children().back())) { duration += transition->out_offset(); } } return TimeRange(RationalTime(0, duration.rate()), duration); } std::pair, std::optional> Track::handles_of_child(Composable const* child, ErrorStatus* error_status) const { std::optional head, tail; auto neighbors = neighbors_of(child, error_status); if (auto transition = dynamic_retainer_cast(neighbors.first)) { head = transition->in_offset(); } if (auto transition = dynamic_retainer_cast(neighbors.second)) { tail = transition->out_offset(); } return std::make_pair(head, tail); } std::pair, Composable::Retainer> Track::neighbors_of( Composable const* item, ErrorStatus* error_status, NeighborGapPolicy insert_gap) const { std::pair, Retainer> result{ nullptr, nullptr }; const int index = index_of_child(item, error_status); if (is_error(error_status)) { return result; } if (index == 0) { if (insert_gap == NeighborGapPolicy::around_transitions) { if (auto transition = dynamic_cast(item)) { result.first = new Gap(TimeRange( // fetch the rate from the offset on the transition RationalTime(0, transition->in_offset().rate()), transition->in_offset())); } } } else { result.first = children()[index - 1]; } if (index == int(children().size()) - 1) { if (insert_gap == NeighborGapPolicy::around_transitions) { if (auto transition = dynamic_cast(item)) { result.second = new Gap(TimeRange( // fetch the rate from the offset on the transition RationalTime(0, transition->out_offset().rate()), transition->out_offset())); } } } else { result.second = children()[index + 1]; } return result; } std::map Track::range_of_all_children(ErrorStatus* error_status) const { std::map result; if (children().empty()) { return result; } auto first_child = children().front(); double rate = 1; if (auto transition = dynamic_retainer_cast(first_child)) { rate = transition->in_offset().rate(); } else if (auto item = dynamic_retainer_cast(first_child)) { rate = item->trimmed_range(error_status).duration().rate(); if (is_error(error_status)) { return result; } } RationalTime last_end_time(0, rate); for (const auto& child: children()) { if (auto transition = dynamic_retainer_cast(child)) { result[child] = TimeRange( last_end_time - transition->in_offset(), transition->out_offset() + transition->in_offset()); } else if (auto item = dynamic_retainer_cast(child)) { auto last_range = TimeRange( last_end_time, item->trimmed_range(error_status).duration()); result[child] = last_range; last_end_time = last_range.end_time_exclusive(); } if (is_error(error_status)) { return result; } } return result; } std::optional Track::available_image_bounds(ErrorStatus* error_status) const { std::optional box; bool found_first_clip = false; for (const auto& child: children()) { if (auto clip = dynamic_cast(child.value)) { if (auto clip_box = clip->available_image_bounds(error_status)) { if (clip_box) { if (found_first_clip) { box->extendBy(*clip_box); } else { box = clip_box; found_first_clip = true; } } } if (is_error(error_status)) { return std::optional(); } } } return box; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/missingReference.h0000664000175000017500000000320415110656141021656 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/mediaReference.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Represents media for which a concrete reference is missing. /// /// Note that a missing reference may have useful metadata, even if the /// location of the media is not known. class OTIO_API_TYPE MissingReference final : public MediaReference { public: /// @brief This struct provides the MissingReference schema. struct Schema { static auto constexpr name = "MissingReference"; static int constexpr version = 1; }; using Parent = MediaReference; /// @brief Create a new missing reference. /// /// @param name The name of the missing reference. /// @param available_range The available range of the missing reference. /// @param metadata The metadata for the missing reference. /// @param available_image_bounds The spatial bounds for the missing reference. MissingReference( std::string const& name = std::string(), std::optional const& available_range = std::nullopt, AnyDictionary const& metadata = AnyDictionary(), std::optional const& available_image_bounds = std::nullopt); bool is_missing_reference() const override; protected: virtual ~MissingReference(); bool read_from(Reader&) override; void write_to(Writer&) const override; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/safely_typed_any.cpp0000664000175000017500000000657415110656141022275 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/safely_typed_any.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { std::any create_safely_typed_any(bool&& value) { return std::any(value); } std::any create_safely_typed_any(int&& value) { return std::any(value); } std::any create_safely_typed_any(int64_t&& value) { return std::any(value); } std::any create_safely_typed_any(uint64_t&& value) { return std::any(value); } std::any create_safely_typed_any(double&& value) { return std::any(value); } std::any create_safely_typed_any(std::string&& value) { return std::any(value); } std::any create_safely_typed_any(RationalTime&& value) { return std::any(value); } std::any create_safely_typed_any(TimeRange&& value) { return std::any(value); } std::any create_safely_typed_any(TimeTransform&& value) { return std::any(value); } std::any create_safely_typed_any(Color&& value) { return std::any(value); } std::any create_safely_typed_any(IMATH_NAMESPACE::V2d&& value) { return std::any(value); } std::any create_safely_typed_any(IMATH_NAMESPACE::Box2d&& value) { return std::any(value); } std::any create_safely_typed_any(AnyVector&& value) { return std::any(std::move(value)); } std::any create_safely_typed_any(AnyDictionary&& value) { return std::any(std::move(value)); } std::any create_safely_typed_any(SerializableObject* value) { return std::any(SerializableObject::Retainer<>(value)); } bool safely_cast_bool_any(std::any const& a) { return std::any_cast(a); } int safely_cast_int_any(std::any const& a) { return std::any_cast(a); } int64_t safely_cast_int64_any(std::any const& a) { return std::any_cast(a); } uint64_t safely_cast_uint64_any(std::any const& a) { return std::any_cast(a); } double safely_cast_double_any(std::any const& a) { return std::any_cast(a); } std::string safely_cast_string_any(std::any const& a) { return std::any_cast(a); } RationalTime safely_cast_rational_time_any(std::any const& a) { return std::any_cast(a); } TimeRange safely_cast_time_range_any(std::any const& a) { return std::any_cast(a); } TimeTransform safely_cast_time_transform_any(std::any const& a) { return std::any_cast(a); } Color safely_cast_color_any(std::any const& a) { return std::any_cast(a); } IMATH_NAMESPACE::V2d safely_cast_point_any(std::any const& a) { return std::any_cast(a); } IMATH_NAMESPACE::Box2d safely_cast_box_any(std::any const& a) { return std::any_cast(a); } AnyDictionary safely_cast_any_dictionary_any(std::any const& a) { return std::any_cast(a); } AnyVector safely_cast_any_vector_any(std::any const& a) { return std::any_cast(a); } SerializableObject* safely_cast_retainer_any(std::any const& a) { return std::any_cast const&>(a); } AnyVector& temp_safely_cast_any_vector_any(std::any const& a) { return const_cast(std::any_cast(a)); } AnyDictionary& temp_safely_cast_any_dictionary_any(std::any const& a) { return const_cast(std::any_cast(a)); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/stackAlgorithm.h0000664000175000017500000000122115110656141021337 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/stack.h" #include "opentimelineio/track.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Flatten a stack down to a single track. OTIO_API Track* flatten_stack(Stack* in_stack, ErrorStatus* error_status = nullptr); /// @brief Flatten a list of tracks down to a single track. OTIO_API Track* flatten_stack( std::vector const& tracks, ErrorStatus* error_status = nullptr); }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/serializableObjectWithMetadata.h0000664000175000017500000000314615110656141024465 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/serializableObject.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief A serializable object with metadata. class OTIO_API_TYPE SerializableObjectWithMetadata : public SerializableObject { public: /// @brief This struct provides the SerializableObjectWithMetadata schema. struct Schema { static auto constexpr name = "SerializableObjectWithMetadata"; static int constexpr version = 1; }; using Parent = SerializableObject; /// @brief Create a new serializable object. /// /// @param name The object name. /// @param metadata The metadata for the object. OTIO_API SerializableObjectWithMetadata( std::string const& name = std::string(), AnyDictionary const& metadata = AnyDictionary()); /// @brief Return the object name. std::string name() const noexcept { return _name; } /// @brief Set the object name. void set_name(std::string const& name) { _name = name; } /// @brief Modify the object metadata. AnyDictionary& metadata() noexcept { return _metadata; } /// @brief Return the object metadata. AnyDictionary metadata() const noexcept { return _metadata; } protected: virtual ~SerializableObjectWithMetadata(); bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::string _name; AnyDictionary _metadata; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/CMakeLists.txt0000664000175000017500000001122515110656316020763 0ustar meme#------------------------------------------------------------------------------ # opentimelineio/CMakeLists.txt set(OPENTIMELINEIO_HEADER_FILES anyDictionary.h anyVector.h color.h clip.h composable.h composition.h deserialization.h algo/editAlgorithm.h effect.h errorStatus.h export.h externalReference.h freezeFrame.h gap.h generatorReference.h imageSequenceReference.h item.h linearTimeWarp.h marker.h mediaReference.h missingReference.h safely_typed_any.h serializableCollection.h serializableObject.h serializableObjectWithMetadata.h serialization.h stack.h stackAlgorithm.h timeEffect.h timeline.h track.h trackAlgorithm.h transition.h typeRegistry.h unknownSchema.h vectorIndexing.h) add_library(opentimelineio ${OTIO_SHARED_OR_STATIC_LIB} color.cpp clip.cpp composable.cpp composition.cpp deserialization.cpp algo/editAlgorithm.cpp effect.cpp errorStatus.cpp externalReference.cpp freezeFrame.cpp gap.cpp generatorReference.cpp imageSequenceReference.cpp item.cpp linearTimeWarp.cpp marker.cpp mediaReference.cpp missingReference.cpp safely_typed_any.cpp serializableCollection.cpp serializableObject.cpp serializableObjectWithMetadata.cpp serialization.cpp stack.cpp stackAlgorithm.cpp stringUtils.cpp stringUtils.h # stringUtils.h is a private header timeEffect.cpp timeline.cpp track.cpp trackAlgorithm.cpp transition.cpp typeRegistry.cpp unknownSchema.cpp CORE_VERSION_MAP.cpp ${OPENTIMELINEIO_HEADER_FILES}) add_library(OTIO::opentimelineio ALIAS opentimelineio) target_include_directories( opentimelineio PRIVATE "${PROJECT_SOURCE_DIR}/src" "${CMAKE_CURRENT_BINARY_DIR}/.." ) if(OTIO_FIND_RAPIDJSON) target_include_directories(opentimelineio PRIVATE "${RapidJSON_INCLUDE_DIRS}") else() target_include_directories(opentimelineio PRIVATE "${PROJECT_SOURCE_DIR}/src/deps/rapidjson/include") endif() target_link_libraries(opentimelineio PUBLIC opentime Imath::Imath) set_target_properties(opentimelineio PROPERTIES DEBUG_POSTFIX "${OTIO_DEBUG_POSTFIX}" LIBRARY_OUTPUT_NAME "opentimelineio" POSITION_INDEPENDENT_CODE TRUE) if(OTIO_SHARED_LIBS) set_target_properties(opentimelineio PROPERTIES SOVERSION ${OTIO_SOVERSION} VERSION ${OTIO_VERSION}) target_compile_definitions( opentimelineio PUBLIC OTIO_EXPORTS) else() target_compile_definitions( opentimelineio PUBLIC OTIO_STATIC) endif() if(APPLE) set_target_properties(opentimelineio PROPERTIES INSTALL_NAME_DIR "@loader_path" MACOSX_RPATH ON) endif() # override any global CXX_FLAGS settings that came from dependencies target_compile_options(opentimelineio PRIVATE $<$,$,$>: -Wall> $<$: /W4> $<$: /EHsc> ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h ) if(OTIO_CXX_INSTALL) install(FILES ${OPENTIMELINEIO_HEADER_FILES} DESTINATION "${OTIO_RESOLVED_CXX_INSTALL_DIR}/include/opentimelineio") set(OPENTIMELINEIO_INCLUDES ${OTIO_RESOLVED_CXX_INSTALL_DIR}/include) install(TARGETS opentimelineio EXPORT OpenTimelineIOTargets INCLUDES DESTINATION "${OPENTIMELINEIO_INCLUDES}" ARCHIVE DESTINATION "${OTIO_RESOLVED_CXX_DYLIB_INSTALL_DIR}" LIBRARY DESTINATION "${OTIO_RESOLVED_CXX_DYLIB_INSTALL_DIR}" RUNTIME DESTINATION "${OTIO_RESOLVED_CXX_DYLIB_INSTALL_DIR}") install(EXPORT OpenTimelineIOTargets DESTINATION "${OTIO_RESOLVED_CXX_INSTALL_DIR}/share/opentimelineio" NAMESPACE OTIO:: ) include(CMakePackageConfigHelpers) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/OpenTimelineIOConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/OpenTimelineIOConfig.cmake INSTALL_DESTINATION ${OTIO_RESOLVED_CXX_INSTALL_DIR}/share/opentimelineio NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenTimelineIOConfig.cmake DESTINATION ${OTIO_RESOLVED_CXX_INSTALL_DIR}/share/opentimelineio ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/version.h DESTINATION "${OTIO_RESOLVED_CXX_INSTALL_DIR}/include/opentimelineio" ) endif() opentimelineio-0.18.1/src/opentimelineio/anyDictionary.h0000664000175000017500000001640115110656141021206 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/export.h" #include "opentimelineio/version.h" #include #include #include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief This class provides a replacement for "std::map". /// /// This class has exactly the same API as "std::map", /// except that it records a "time-stamp" that bumps monotonically every time an /// operation that would invalidate iterators is performed (this happens for /// operator =, clear, erase, insert, and swap). The stamp also lets external /// observers know when the map has been destroyed (which includes the case of /// the map being relocated in memory). /// /// This allows us to hand out iterators that can be aware of mutation and moves /// and take steps to safe-guard themselves from causing a crash. (Yes, I'm /// talking to you, Python...) class OTIO_API_TYPE AnyDictionary : private std::map { public: using map::map; /// @brief Create an empty dictionary. AnyDictionary() : map{} , _mutation_stamp{} {} /// @brief Create a copy of a dictionary. /// /// To be safe, avoid brace-initialization so as to not trigger /// list initialization behavior in older compilers: AnyDictionary(const AnyDictionary& other) : map(other) , _mutation_stamp{} {} /// @brief Destructor. ~AnyDictionary() { if (_mutation_stamp) { _mutation_stamp->stamp = -1; _mutation_stamp->any_dictionary = nullptr; } } /// @brief Copy operator. AnyDictionary& operator=(const AnyDictionary& other) { mutate(); map::operator=(other); return *this; } /// @brief Move operator. AnyDictionary& operator=(AnyDictionary&& other) { mutate(); other.mutate(); map::operator=(other); return *this; } /// @brief Copy operator. AnyDictionary& operator=(std::initializer_list ilist) { mutate(); map::operator=(ilist); return *this; } using map::get_allocator; using map::at; using map::operator[]; using map::begin; using map::cbegin; using map::cend; using map::crbegin; using map::crend; using map::end; using map::rbegin; using map::rend; /// @brief Clear the dictionary. void clear() noexcept { mutate(); map::clear(); } using map::emplace; using map::emplace_hint; using map::insert; /// @brief Erase an item. iterator erase(const_iterator pos) { mutate(); return map::erase(pos); } /// @brief Erase a range of items. iterator erase(const_iterator first, const_iterator last) { mutate(); return map::erase(first, last); } /// @brief Erase an item with the given key. size_type erase(const key_type& key) { mutate(); return map::erase(key); } /// @brief Swap dictionaries. void swap(AnyDictionary& other) { mutate(); other.mutate(); map::swap(other); } /// @brief Return whether the given key has been set. /// /// If key is in this, and the type of key matches the type of result, then /// set result to the value of std::any_cast(this[key]) and return true, /// otherwise return false. template bool get_if_set(const std::string& key, containedType* result) const { if (result == nullptr) { return false; } const auto it = this->find(key); if ((it != this->end()) && (it->second.type().hash_code() == typeid(containedType).hash_code())) { *result = std::any_cast(it->second); return true; } else { return false; } } /// @brief Return whether the dictionary contains the given key. inline bool has_key(const std::string& key) const { return (this->find(key) != this->end()); } /// @brief Set the default for the given key. /// /// If key is in this, place the value in result and return true, otherwise /// store the value in result at key and return false. template bool set_default(const std::string& key, containedType* result) { if (result == nullptr) { return false; } const auto d_it = this->find(key); if ((d_it != this->end()) && (d_it->second.type().hash_code() == typeid(containedType).hash_code())) { *result = std::any_cast(d_it->second); return true; } else { this->insert({ key, *result }); return false; } } using map::empty; using map::max_size; using map::size; using map::count; using map::equal_range; using map::find; using map::lower_bound; using map::upper_bound; using map::key_comp; using map::value_comp; using map::allocator_type; using map::const_iterator; using map::const_pointer; using map::const_reference; using map::const_reverse_iterator; using map::difference_type; using map::iterator; using map::key_compare; using map::key_type; using map::mapped_type; using map::pointer; using map::reference; using map::reverse_iterator; using map::size_type; using map::value_type; /// @brief This struct provides a mutation time stamp. struct MutationStamp { /// @brief Create a new time stamp. constexpr MutationStamp(AnyDictionary* d) noexcept : stamp{ 1 } , any_dictionary{ d } , owning{ false } { assert(d); } MutationStamp(MutationStamp const&) = delete; MutationStamp& operator=(MutationStamp const&) = delete; /// @brief Destructor. ~MutationStamp() { if (any_dictionary) { any_dictionary->_mutation_stamp = nullptr; if (owning) { delete any_dictionary; } } } int64_t stamp; AnyDictionary* any_dictionary; bool owning; protected: MutationStamp() : stamp{ 1 } , any_dictionary{ new AnyDictionary } , owning{ true } { any_dictionary->_mutation_stamp = this; } }; /// @brief Get or crate a mutation time stamp. MutationStamp* get_or_create_mutation_stamp() { if (!_mutation_stamp) { _mutation_stamp = new MutationStamp(this); } return _mutation_stamp; } friend struct MutationStamp; private: MutationStamp* _mutation_stamp = nullptr; void mutate() noexcept { if (_mutation_stamp) { _mutation_stamp->stamp++; } } }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/errorStatus.h0000664000175000017500000000627215110656141020733 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/export.h" #include "opentimelineio/version.h" #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class SerializableObject; /// @brief This struct represents the return status of a function. struct OTIO_API_TYPE ErrorStatus { /// @brief This enumeration represents the possible outcomes. enum Outcome { OK = 0, NOT_IMPLEMENTED, UNRESOLVED_OBJECT_REFERENCE, DUPLICATE_OBJECT_REFERENCE, MALFORMED_SCHEMA, JSON_PARSE_ERROR, CHILD_ALREADY_PARENTED, FILE_OPEN_FAILED, FILE_WRITE_FAILED, SCHEMA_ALREADY_REGISTERED, SCHEMA_NOT_REGISTERED, SCHEMA_VERSION_UNSUPPORTED, KEY_NOT_FOUND, ILLEGAL_INDEX, TYPE_MISMATCH, INTERNAL_ERROR, NOT_AN_ITEM, NOT_A_CHILD_OF, NOT_A_CHILD, NOT_DESCENDED_FROM, CANNOT_COMPUTE_AVAILABLE_RANGE, INVALID_TIME_RANGE, OBJECT_WITHOUT_DURATION, CANNOT_TRIM_TRANSITION, OBJECT_CYCLE, CANNOT_COMPUTE_BOUNDS, MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY, MEDIA_REFERENCES_CONTAIN_EMPTY_KEY, NOT_A_GAP }; /// @brief Construct a new status with no error. ErrorStatus() : outcome(OK) , object_details(nullptr) {} /// @brief Construct a new status with the given outcome. ErrorStatus(Outcome in_outcome) : outcome(in_outcome) , details(outcome_to_string(in_outcome)) , full_description(details) , object_details(nullptr) {} /// @brief Construct a new status with the given outcome, details, and object. ErrorStatus( Outcome in_outcome, std::string const& in_details, SerializableObject const* object = nullptr) : outcome(in_outcome) , details(in_details) , full_description(outcome_to_string(in_outcome) + ": " + in_details) , object_details(object) {} /// @brief Copy operator. ErrorStatus& operator=(Outcome in_outcome) { *this = ErrorStatus(in_outcome); return *this; } /// @brief The outcome of the function. Outcome outcome; /// @brief A human readable string that provides details about the outcome. std::string details; /// @brief A human readable string that provides the full description of the status. std::string full_description; /// @brief The object related to the status. SerializableObject const* object_details; //! @brief Return a human readable string for the given outcome. static OTIO_API std::string outcome_to_string(Outcome); }; /// @brief Check whether the given ErrorStatus is an error. OTIO_API constexpr bool is_error(const ErrorStatus& es) noexcept { return ErrorStatus::Outcome::OK != es.outcome; } /// @brief Check whether the given ErrorStatus* is non-null and an error. OTIO_API constexpr bool is_error(const ErrorStatus* es) noexcept { return es && ErrorStatus::Outcome::OK != es->outcome; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/effect.cpp0000664000175000017500000000163215110656141020160 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/effect.h" #include "opentimelineio/missingReference.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Effect::Effect( std::string const& name, std::string const& effect_name, AnyDictionary const& metadata, bool enabled) : Parent(name, metadata) , _effect_name(effect_name) , _enabled(enabled) {} Effect::~Effect() {} bool Effect::read_from(Reader& reader) { return reader.read("effect_name", &_effect_name) && reader.read_if_present("enabled", &_enabled) && Parent::read_from(reader); } void Effect::write_to(Writer& writer) const { Parent::write_to(writer); writer.write("effect_name", _effect_name); writer.write("enabled", _enabled); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/track.h0000664000175000017500000000602715110656141017500 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/composition.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { class Clip; /// @brief A track is a composition of a certain kind, like video or audio. class OTIO_API_TYPE Track : public Composition { public: /// @brief This struct provides the base set of kinds of tracks. struct Kind { static auto constexpr video = "Video"; static auto constexpr audio = "Audio"; }; /// @brief This enumeration provides the neighbor gap policy. enum NeighborGapPolicy { never = 0, around_transitions = 1 }; /// @brief This struct provides the Track schema. struct Schema { static auto constexpr name = "Track"; static int constexpr version = 1; }; using Parent = Composition; /// @brief Create a new track. /// /// @param name The track name. /// @param source_range The source range of the track. /// @param kind The kind of track. /// @param metadata The metadata for the track. OTIO_API Track( std::string const& name = std::string(), std::optional const& source_range = std::nullopt, std::string const& kind = Kind::video, AnyDictionary const& metadata = AnyDictionary(), std::optional const& color = std::nullopt); /// @brief Return this kind of track. std::string kind() const noexcept { return _kind; } /// @brief Set this kind of track. void set_kind(std::string const& kind) { _kind = kind; } TimeRange range_of_child_at_index( int index, ErrorStatus* error_status = nullptr) const override; TimeRange trimmed_range_of_child_at_index( int index, ErrorStatus* error_status = nullptr) const override; TimeRange available_range(ErrorStatus* error_status = nullptr) const override; std::pair, std::optional> handles_of_child( Composable const* child, ErrorStatus* error_status = nullptr) const override; /// @brief Return the neighbors of the given item. OTIO_API std::pair, Retainer> neighbors_of( Composable const* item, ErrorStatus* error_status = nullptr, NeighborGapPolicy insert_gap = NeighborGapPolicy::never) const; std::map range_of_all_children(ErrorStatus* error_status = nullptr) const override; std::optional available_image_bounds(ErrorStatus* error_status) const override; protected: virtual ~Track(); std::string composition_kind() const override; bool read_from(Reader&) override; void write_to(Writer&) const override; private: std::string _kind; }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/timeEffect.h0000664000175000017500000000206715110656141020447 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/effect.h" #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Base class for all effects that alter the timing of an item. class OTIO_API_TYPE TimeEffect : public Effect { public: /// @brief This struct provides the TimeEffect schema. struct Schema { static auto constexpr name = "TimeEffect"; static int constexpr version = 1; }; using Parent = Effect; /// @brief Create a new time effect. /// /// @param name The name of the object. /// @param effect_name The time effect name. /// @param metadata The metadata for the time effect. TimeEffect( std::string const& name = std::string(), std::string const& effect_name = std::string(), AnyDictionary const& metadata = AnyDictionary()); protected: virtual ~TimeEffect(); }; }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/serializableObjectWithMetadata.cpp0000664000175000017500000000165715110656141025025 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/serializableObjectWithMetadata.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { SerializableObjectWithMetadata::SerializableObjectWithMetadata( std::string const& name, AnyDictionary const& metadata) : _name(name) , _metadata(metadata) {} SerializableObjectWithMetadata::~SerializableObjectWithMetadata() {} bool SerializableObjectWithMetadata::read_from(Reader& reader) { return reader.read_if_present("metadata", &_metadata) && reader.read_if_present("name", &_name) && SerializableObject::read_from(reader); } void SerializableObjectWithMetadata::write_to(Writer& writer) const { SerializableObject::write_to(writer); writer.write("metadata", _metadata); writer.write("name", _name); } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/vectorIndexing.h0000664000175000017500000000071715110656141021364 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #pragma once #include "opentimelineio/version.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { /// @brief Return the adjusted vector index. template constexpr int adjusted_vector_index(int index, V const& vec) noexcept { return index < 0 ? int(vec.size()) + index : index; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/timeEffect.cpp0000664000175000017500000000071215110656141020775 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/timeEffect.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { TimeEffect::TimeEffect( std::string const& name, std::string const& effect_name, AnyDictionary const& metadata) : Parent(name, effect_name, metadata) {} TimeEffect::~TimeEffect() {} }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/stackAlgorithm.cpp0000664000175000017500000001621015110656141021676 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include "opentimelineio/stackAlgorithm.h" #include "opentimelineio/gap.h" #include "opentimelineio/track.h" #include "opentimelineio/trackAlgorithm.h" #include "opentimelineio/transition.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { typedef std::map> RangeTrackMap; typedef std::vector> TrackRetainerVector; static void _flatten_next_item( RangeTrackMap& range_track_map, Track* flat_track, std::vector const& tracks, int track_index, std::optional trim_range, ErrorStatus* error_status) { if (track_index < 0) { track_index = int(tracks.size()) - 1; } if (track_index < 0) { return; } Track* track = tracks[track_index]; SerializableObject::Retainer track_retainer; if (trim_range) { track = track_trimmed_to_range(track, *trim_range, error_status); if (track == nullptr || is_error(error_status)) { return; } track_retainer = SerializableObject::Retainer(track); } std::map* track_map; auto it = range_track_map.find(track); if (it != range_track_map.end()) { track_map = &it->second; } else { auto result = range_track_map.emplace( track, track->range_of_all_children(error_status)); if (is_error(error_status)) { return; } track_map = &result.first->second; } for (auto child: track->children()) { auto item = dynamic_retainer_cast(child); if (!item) { if (!dynamic_retainer_cast(child)) { if (error_status) { *error_status = ErrorStatus( ErrorStatus::TYPE_MISMATCH, "expected item of type Item* || Transition*", child); } return; } } if (!item || item->visible() || track_index == 0) { flat_track->insert_child( static_cast(flat_track->children().size()), static_cast(child->clone(error_status)), error_status); if (is_error(error_status)) { return; } } else { TimeRange trim = (*track_map)[item]; if (trim_range) { trim = TimeRange( trim.start_time() + trim_range->start_time(), trim.duration()); (*track_map)[item] = trim; } _flatten_next_item( range_track_map, flat_track, tracks, track_index - 1, trim, error_status); if (track == nullptr || is_error(error_status)) { return; } } } // range_track_map persists over the entire duration of flatten_stack // track_retainer.value is about to be deleted; it's entirely possible // that a new item will be created at the same pointer location, so we // have to clean this value out of the map now. if (track_retainer) { range_track_map.erase(track_retainer); } } // add a gap to end of a track if it is shorter then the longest track. // shorter tracks are clones and get added to the tracks_retainer. // a new track will replace the original pointer in the track vector. static void _normalize_tracks_lengths( std::vector& tracks, TrackRetainerVector& tracks_retainer, ErrorStatus* error_status) { RationalTime duration; for (auto track: tracks) { duration = std::max(duration, track->duration(error_status)); if (is_error(error_status)) { return; } } for (size_t i = 0; i < tracks.size(); i++) { Track* old_track = tracks[i]; RationalTime track_duration = old_track->duration(error_status); if (track_duration < duration) { Track* new_track = static_cast(old_track->clone(error_status)); if (is_error(error_status)) { return; } // add track to retainer so it can be freed later tracks_retainer.push_back( SerializableObject::Retainer(new_track)); new_track->append_child( new Gap(duration - track_duration), error_status); if (is_error(error_status)) { return; } tracks[i] = new_track; } } } Track* flatten_stack(Stack* in_stack, ErrorStatus* error_status) { std::vector tracks; // tracks are cloned if they need to be normalized // they get added to this retainer so they can be // freed when the algorithm is complete TrackRetainerVector tracks_retainer; tracks.reserve(in_stack->children().size()); for (auto c: in_stack->children()) { if (auto track = dynamic_retainer_cast(c)) { if (track->enabled()) { tracks.push_back(track); } } else { if (error_status) { *error_status = ErrorStatus( ErrorStatus::TYPE_MISMATCH, "expected item of type Track*", c); } return nullptr; } } _normalize_tracks_lengths(tracks, tracks_retainer, error_status); if (is_error(error_status)) { return nullptr; } Track* flat_track = new Track; flat_track->set_name("Flattened"); RangeTrackMap range_track_map; _flatten_next_item( range_track_map, flat_track, tracks, -1, std::nullopt, error_status); return flat_track; } Track* flatten_stack(std::vector const& tracks, ErrorStatus* error_status) { std::vector flat_tracks; // tracks are cloned if they need to be normalized // they get added to this retainer so they can be // freed when the algorithm is complete TrackRetainerVector tracks_retainer; flat_tracks.reserve(tracks.size()); for (auto track: tracks) { flat_tracks.push_back(track); } _normalize_tracks_lengths(flat_tracks, tracks_retainer, error_status); if (is_error(error_status)) { return nullptr; } Track* flat_track = new Track; flat_track->set_name("Flattened"); RangeTrackMap range_track_map; _flatten_next_item( range_track_map, flat_track, flat_tracks, -1, std::nullopt, error_status); return flat_track; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSION opentimelineio-0.18.1/src/opentimelineio/color.cpp0000664000175000017500000001206115110656141020040 0ustar meme// SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the OpenTimelineIO project #include #include #include "opentimelineio/color.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { Color::Color( double const r, double const g, double const b, double const a, std::string const& name) : _r(r) , _g(g) , _b(b) , _a(a) , _name(name) {} Color::Color(Color const& other) : _r(other.r()) , _g(other.g()) , _b(other.b()) , _a(other.a()) , _name(other.name()) {} const Color Color::pink(1.0, 0.0, 1.0, 1.0, "Pink"); const Color Color::red(1.0, 0.0, 0.0, 1.0, "Red"); const Color Color::orange(1.0, 0.5, 0.0, 1.0, "Orange"); const Color Color::yellow(1.0, 1.0, 0.0, 1.0, "Yellow"); const Color Color::green(0.0, 1.0, 0.0, 1.0, "Green"); const Color Color::cyan(0.0, 1.0, 1.0, 1.0, "Cyan"); const Color Color::blue(0.0, 0.0, 1.0, 1.0, "Blue"); const Color Color::purple(0.5, 0.0, 0.5, 1.0, "Purple"); const Color Color::magenta(1.0, 0.0, 1.0, 1.0, "Magenta"); const Color Color::black(0.0, 0.0, 0.0, 1.0, "Black"); const Color Color::white(1.0, 1.0, 1.0, 1.0, "White"); const Color Color::transparent(0.0, 0.0, 0.0, 0.0, "Transparent"); Color* Color::from_hex(std::string const& color) { std::string temp = color; if (temp[0] == '#') { temp = temp.substr(1); } else if (temp[0] == '0' && (temp[1] == 'x' || temp[1] == 'X')) { temp = temp.substr(2); } double _r, _g, _b, _a; // 0xFFFFFFFF (rgba, 255) int BASE_16 = 16; double BASE_16_DIV = 255.0; double BASE_8_DIV = 15.0; if (temp.length() == 8) { _r = std::stoi(temp.substr(0, 2), nullptr, BASE_16) / BASE_16_DIV; _g = std::stoi(temp.substr(2, 2), nullptr, BASE_16) / BASE_16_DIV; _b = std::stoi(temp.substr(4, 2), nullptr, BASE_16) / BASE_16_DIV; _a = std::stoi(temp.substr(6, 2), nullptr, BASE_16) / BASE_16_DIV; } // 0xFFFFFF (rgb, 255) else if (temp.length() == 6) { _r = std::stoi(temp.substr(0, 2), nullptr, BASE_16) / BASE_16_DIV; _g = std::stoi(temp.substr(2, 2), nullptr, BASE_16) / BASE_16_DIV; _b = std::stoi(temp.substr(4, 2), nullptr, BASE_16) / BASE_16_DIV; _a = 1.0; } // 0xFFF (rgb, 16) else if (temp.length() == 3) { _r = std::stoi(temp.substr(0, 1), nullptr, BASE_16) / BASE_8_DIV; _g = std::stoi(temp.substr(1, 1), nullptr, BASE_16) / BASE_8_DIV; _b = std::stoi(temp.substr(2, 1), nullptr, BASE_16) / BASE_8_DIV; _a = 1.0; } // 0xFFFF (rgba, 16) else if (temp.length() == 4) { _r = std::stoi(temp.substr(0, 1), nullptr, BASE_16) / BASE_8_DIV; _g = std::stoi(temp.substr(1, 1), nullptr, BASE_16) / BASE_8_DIV; _b = std::stoi(temp.substr(2, 1), nullptr, BASE_16) / BASE_8_DIV; _a = std::stoi(temp.substr(3, 1), nullptr, BASE_16) / BASE_8_DIV; } else { throw std::invalid_argument("Invalid hex format"); } return new Color(_r, _g, _b, _a); } Color* Color::from_int_list(std::vector const& color, int bit_depth) { double depth = pow(2, bit_depth) - 1.0; // e.g. 8 = 255.0 if (color.size() == 3) return new Color( color[0] / depth, color[1] / depth, color[2] / depth, 1.0); else if (color.size() == 4) return new Color( color[0] / depth, color[1] / depth, color[2] / depth, color[3] / depth); throw std::invalid_argument("List must have exactly 3 or 4 elements"); } Color* Color::from_agbr_int(unsigned int agbr) noexcept { auto conv_r = (agbr & 0xFF) / 255.0; auto conv_g = ((agbr >> 16) & 0xFF) / 255.0; auto conv_b = ((agbr >> 8) & 0xFF) / 255.0; auto conv_a = ((agbr >> 24) & 0xFF) / 255.0; return new Color(conv_r, conv_g, conv_b, conv_a); } Color* Color::from_float_list(std::vector const& color) { if (color.size() == 3) return new Color(color[0], color[1], color[2], 1.0); else if (color.size() == 4) return new Color(color[0], color[1], color[2], color[3]); throw std::invalid_argument("List must have exactly 3 or 4 elements"); } std::string Color::to_hex() { auto rgba = to_rgba_int_list(8); std::stringstream ss; ss << "#"; ss << std::hex << std::setfill('0'); ss << std::setw(2) << rgba[0]; ss << std::setw(2) << rgba[1]; ss << std::setw(2) << rgba[2]; ss << std::setw(2) << rgba[3]; return ss.str(); } std::vector Color::to_rgba_int_list(int base) { double math_base = pow(2, base) - 1.0; return { int(_r * math_base), int(_g * math_base), int(_b * math_base), int(_a * math_base) }; } unsigned int Color::to_agbr_integer() { auto rgba = to_rgba_int_list(8); return (rgba[3] << 24) + (rgba[2] << 16) + (rgba[1] << 8) + rgba[0]; } std::vector Color::to_rgba_float_list() { return { _r, _g, _b, _a }; } }} // namespace opentimelineio::OPENTIMELINEIO_VERSIONopentimelineio-0.18.1/src/deps/0000775000175000017500000000000015110656141014131 5ustar memeopentimelineio-0.18.1/src/deps/rapidjson/0000775000175000017500000000000015110656147016130 5ustar memeopentimelineio-0.18.1/src/deps/rapidjson/.travis.yml0000664000175000017500000001466415110656146020253 0ustar memesudo: required dist: xenial language: cpp cache: - ccache addons: apt: sources: - ubuntu-toolchain-r-test packages: - cmake - valgrind - clang-8 env: global: - USE_CCACHE=1 - CCACHE_SLOPPINESS=pch_defines,time_macros - CCACHE_COMPRESS=1 - CCACHE_MAXSIZE=100M - ARCH_FLAGS_x86='-m32' # #266: don't use SSE on 32-bit - ARCH_FLAGS_x86_64='-msse4.2' # use SSE4.2 on 64-bit - ARCH_FLAGS_aarch64='-march=armv8-a' - GITHUB_REPO='Tencent/rapidjson' - secure: "HrsaCb+N66EG1HR+LWH1u51SjaJyRwJEDzqJGYMB7LJ/bfqb9mWKF1fLvZGk46W5t7TVaXRDD5KHFx9DPWvKn4gRUVkwTHEy262ah5ORh8M6n/6VVVajeV/AYt2C0sswdkDBDO4Xq+xy5gdw3G8s1A4Inbm73pUh+6vx+7ltBbk=" matrix: include: # gcc - env: CONF=release ARCH=x86 CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=OFF compiler: gcc arch: amd64 - env: CONF=release ARCH=x86_64 CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=OFF compiler: gcc arch: amd64 - env: CONF=release ARCH=x86_64 CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=ON compiler: gcc arch: amd64 - env: CONF=debug ARCH=x86 CXX11=OFF CXX17=OFF CXX20=OFF MEMBERSMAP=OFF compiler: gcc arch: amd64 - env: CONF=debug ARCH=x86_64 CXX11=OFF CXX17=OFF CXX20=OFF MEMBERSMAP=OFF compiler: gcc arch: amd64 - env: CONF=debug ARCH=x86 CXX11=OFF CXX17=ON CXX20=OFF MEMBERSMAP=ON CXX_FLAGS='-D_GLIBCXX_DEBUG' compiler: gcc arch: amd64 - env: CONF=debug ARCH=x86_64 CXX11=OFF CXX17=ON CXX20=OFF MEMBERSMAP=ON CXX_FLAGS='-D_GLIBCXX_DEBUG' compiler: gcc arch: amd64 - env: CONF=release ARCH=aarch64 CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=OFF compiler: gcc arch: arm64 - env: CONF=release ARCH=aarch64 CXX11=OFF CXX17=OFF CXX20=OFF MEMBERSMAP=OFF compiler: gcc arch: arm64 - env: CONF=release ARCH=aarch64 CXX11=OFF CXX17=ON CXX20=OFF MEMBERSMAP=ON compiler: gcc arch: arm64 # clang - env: CONF=release ARCH=x86 CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=ON CCACHE_CPP2=yes compiler: clang arch: amd64 - env: CONF=release ARCH=x86_64 CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=ON CCACHE_CPP2=yes compiler: clang arch: amd64 - env: CONF=release ARCH=x86_64 CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=OFF CCACHE_CPP2=yes compiler: clang arch: amd64 - env: CONF=debug ARCH=x86 CXX11=OFF CXX17=OFF CXX20=OFF MEMBERSMAP=ON CCACHE_CPP2=yes compiler: clang arch: amd64 - env: CONF=debug ARCH=x86_64 CXX11=OFF CXX17=OFF CXX20=OFF MEMBERSMAP=ON CCACHE_CPP2=yes compiler: clang arch: amd64 - env: CONF=debug ARCH=x86 CXX11=OFF CXX17=ON CXX20=OFF MEMBERSMAP=OFF CCACHE_CPP2=yes compiler: clang arch: amd64 - env: CONF=debug ARCH=x86_64 CXX11=OFF CXX17=ON CXX20=OFF MEMBERSMAP=OFF CCACHE_CPP2=yes compiler: clang arch: amd64 - env: CONF=debug ARCH=aarch64 CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=ON CCACHE_CPP2=yes compiler: clang arch: arm64 - env: CONF=debug ARCH=aarch64 CXX11=OFF CXX17=OFF CXX20=OFF MEMBERSMAP=ON CCACHE_CPP2=yes compiler: clang arch: arm64 - env: CONF=debug ARCH=aarch64 CXX11=OFF CXX17=ON CXX20=OFF MEMBERSMAP=OFF CCACHE_CPP2=yes compiler: clang arch: arm64 # coverage report - env: CONF=debug ARCH=x86 GCOV_FLAGS='--coverage' CXX_FLAGS='-O0' CXX11=OFF CXX17=OFF CXX20=OFF compiler: gcc arch: amd64 cache: - ccache - pip after_success: - pip install --user cpp-coveralls - coveralls -r .. --gcov-options '\-lp' -e thirdparty -e example -e test -e build/CMakeFiles -e include/rapidjson/msinttypes -e include/rapidjson/internal/meta.h -e include/rapidjson/error/en.h - env: CONF=debug ARCH=x86_64 GCOV_FLAGS='--coverage' CXX_FLAGS='-O0' CXX11=ON CXX17=OFF CXX20=OFF MEMBERSMAP=ON compiler: gcc arch: amd64 cache: - ccache - pip after_success: - pip install --user cpp-coveralls - coveralls -r .. --gcov-options '\-lp' -e thirdparty -e example -e test -e build/CMakeFiles -e include/rapidjson/msinttypes -e include/rapidjson/internal/meta.h -e include/rapidjson/error/en.h - env: CONF=debug ARCH=aarch64 GCOV_FLAGS='--coverage' CXX_FLAGS='-O0' CXX11=OFF CXX17=ON CXX20=OFF compiler: gcc arch: arm64 cache: - ccache - pip after_success: - pip install --user cpp-coveralls - coveralls -r .. --gcov-options '\-lp' -e thirdparty -e example -e test -e build/CMakeFiles -e include/rapidjson/msinttypes -e include/rapidjson/internal/meta.h -e include/rapidjson/error/en.h - script: # Documentation task - cd build - cmake .. -DRAPIDJSON_HAS_STDSTRING=ON -DCMAKE_VERBOSE_MAKEFILE=ON - make travis_doc cache: false addons: apt: packages: - doxygen before_install: - if [ "x86_64" = "$(arch)" ]; then sudo apt-get install -y g++-multilib libc6-dbg:i386 --allow-unauthenticated; fi before_script: # travis provides clang-7 for amd64 and clang-3.8 for arm64 # here use clang-8 to all architectures as clang-7 is not available for arm64 - if [ -f /usr/bin/clang++-8 ]; then sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 1000; sudo update-alternatives --config clang++; export PATH=/usr/bin:$PATH; fi - if [ "$CXX" = "clang++" ]; then export CCACHE_CPP2=yes; fi - ccache -s # hack to avoid Valgrind bug (https://bugs.kde.org/show_bug.cgi?id=326469), # exposed by merging PR#163 (using -march=native) # TODO: Since this bug is already fixed. Remove this when valgrind can be upgraded. - sed -i "s/-march=native//" CMakeLists.txt - mkdir build script: - if [ "$CXX" = "clang++" ]; then export CXXFLAGS="-stdlib=libc++ ${CXXFLAGS}"; fi - > eval "ARCH_FLAGS=\${ARCH_FLAGS_${ARCH}}" ; (cd build && cmake -DRAPIDJSON_HAS_STDSTRING=ON -DRAPIDJSON_USE_MEMBERSMAP=$MEMBERSMAP -DRAPIDJSON_BUILD_CXX11=$CXX11 -DRAPIDJSON_BUILD_CXX17=$CXX17 -DRAPIDJSON_BUILD_CXX20=$CXX20 -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=$CONF -DCMAKE_CXX_FLAGS="$ARCH_FLAGS $GCOV_FLAGS $CXX_FLAGS" -DCMAKE_EXE_LINKER_FLAGS=$GCOV_FLAGS ..) - cd build - make tests -j 2 - make examples -j 2 - ctest -j 2 -V `[ "$CONF" = "release" ] || echo "-E perftest"` opentimelineio-0.18.1/src/deps/rapidjson/.gitmodules0000664000175000017500000000015015110656146020300 0ustar meme[submodule "thirdparty/gtest"] path = thirdparty/gtest url = https://github.com/google/googletest.git opentimelineio-0.18.1/src/deps/rapidjson/RapidJSONConfigVersion.cmake.in0000664000175000017500000000072515110656146023767 0ustar memeSET(PACKAGE_VERSION "@LIB_VERSION_STRING@") IF (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) SET(PACKAGE_VERSION_EXACT "true") ENDIF (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) IF (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) SET(PACKAGE_VERSION_COMPATIBLE "true") ELSE (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) SET(PACKAGE_VERSION_UNSUITABLE "true") ENDIF (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) opentimelineio-0.18.1/src/deps/rapidjson/RapidJSON.pc.in0000664000175000017500000000034515110656146020613 0ustar memeincludedir=@INCLUDE_INSTALL_DIR@ Name: @PROJECT_NAME@ Description: A fast JSON parser/generator for C++ with both SAX/DOM style API Version: @LIB_VERSION_STRING@ URL: https://github.com/Tencent/rapidjson Cflags: -I${includedir} opentimelineio-0.18.1/src/deps/rapidjson/rapidjson.autopkg0000664000175000017500000000651715110656147021526 0ustar memenuget { //Usage: Write-NuGetPackage rapidjson.autopkg -defines:MYVERSION=1.1.0 //Be sure you are running Powershell 3.0 and have the CoApp powershell extensions installed properly. nuspec { id = rapidjson; version : ${MYVERSION}; title: "rapidjson"; authors: {"https://github.com/Tencent/rapidjson/releases/tag/v1.1.0"}; owners: {"@lsantos (github)"}; licenseUrl: "https://github.com/Tencent/rapidjson/blob/master/license.txt"; projectUrl: "https://github.com/Tencent/rapidjson/"; iconUrl: "https://cdn1.iconfinder.com/data/icons/fatcow/32x32/json.png"; requireLicenseAcceptance:false; summary: @"A fast JSON parser/generator for C++ with both SAX/DOM style API"; // if you need to span several lines you can prefix a string with an @ symbol (exactly like c# does). description: @"Rapidjson is an attempt to create the fastest JSON parser and generator. - Small but complete. Supports both SAX and DOM style API. SAX parser only a few hundred lines of code. - Fast. In the order of magnitude of strlen(). Optionally supports SSE2/SSE4.2 for acceleration. - Self-contained. Minimal dependency on standard libraries. No BOOST, not even STL. - Compact. Each JSON value is 16 or 20 bytes for 32 or 64-bit machines respectively (excluding text string storage). With the custom memory allocator, parser allocates memory compactly during parsing. - Full RFC4627 compliance. Supports UTF-8, UTF-16 and UTF-32. - Support both in-situ parsing (directly decode strings into the source JSON text) and non-destructive parsing (decode strings into new buffers). - Parse number to int/unsigned/int64_t/uint64_t/double depending on input - Support custom memory allocation. Also, the default memory pool allocator can also be supplied with a user buffer (such as a buffer allocated on user's heap or - programme stack) to minimize allocation. As the name implies, rapidjson is inspired by rapidxml."; releaseNotes: @" Added Add Value::XXXMember(...) overloads for std::string (#335) Fixed Include rapidjson.h for all internal/error headers. Parsing some numbers incorrectly in full-precision mode (kFullPrecisionParseFlag) (#342) Fix alignment of 64bit platforms (#328) Fix MemoryPoolAllocator::Clear() to clear user-buffer (0691502) Changed CMakeLists for include as a thirdparty in projects (#334, #337) Change Document::ParseStream() to use stack allocator for Reader (ffbe386)"; copyright: "Copyright 2015"; tags: { native, coapp, JSON, nativepackage }; language: en-US; }; dependencies { packages : { //TODO: Add dependencies here in [pkg.name]/[version] form per newline //zlib/[1.2.8], }; } // the files that go into the content folders files { #defines { SDK_ROOT = .\; } // grab all the files in the include folder // the folder that contains all the .h files will // automatically get added to the Includes path. nestedinclude += { #destination = ${d_include}rapidjson; "${SDK_ROOT}include\rapidjson\**\*.h" }; }; targets { // We're trying to be standard about these sorts of thing. (Will help with config.h later :D) //Defines += HAS_EQCORE; // Fix creating the package with Raggles' fork of CoApp Includes += "$(MSBuildThisFileDirectory)../..${d_include}"; }; }opentimelineio-0.18.1/src/deps/rapidjson/appveyor.yml0000664000175000017500000000551115110656146020521 0ustar memeversion: 1.1.0.{build} configuration: - Debug - Release environment: matrix: # - VS_VERSION: 9 2008 # VS_PLATFORM: win32 # - VS_VERSION: 9 2008 # VS_PLATFORM: x64 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 VS_VERSION: 10 2010 VS_PLATFORM: win32 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: OFF - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 VS_VERSION: 10 2010 VS_PLATFORM: x64 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: ON - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 VS_VERSION: 11 2012 VS_PLATFORM: win32 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: ON - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 VS_VERSION: 11 2012 VS_PLATFORM: x64 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: OFF - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 VS_VERSION: 12 2013 VS_PLATFORM: win32 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: OFF - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 VS_VERSION: 12 2013 VS_PLATFORM: x64 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: ON - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 VS_VERSION: 14 2015 VS_PLATFORM: win32 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: ON - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 VS_VERSION: 14 2015 VS_PLATFORM: x64 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: OFF - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 VS_VERSION: 15 2017 VS_PLATFORM: win32 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: OFF - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 VS_VERSION: 15 2017 VS_PLATFORM: x64 CXX11: OFF CXX17: OFF CXX20: OFF MEMBERSMAP: ON - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 VS_VERSION: 15 2017 VS_PLATFORM: x64 CXX11: ON CXX17: OFF CXX20: OFF MEMBERSMAP: OFF - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 VS_VERSION: 15 2017 VS_PLATFORM: x64 CXX11: OFF CXX17: ON CXX20: OFF MEMBERSMAP: OFF - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 VS_VERSION: 16 2019 VS_PLATFORM: x64 CXX11: OFF CXX17: ON CXX20: OFF MEMBERSMAP: ON before_build: - git submodule update --init --recursive - cmake -H. -BBuild/VS -G "Visual Studio %VS_VERSION%" -DCMAKE_GENERATOR_PLATFORM=%VS_PLATFORM% -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_SHARED_LIBS=true -DRAPIDJSON_BUILD_CXX11=%CXX11% -DRAPIDJSON_BUILD_CXX17=%CXX17% -DRAPIDJSON_BUILD_CXX20=%CXX20% -DRAPIDJSON_USE_MEMBERSMAP=%MEMBERSMAP% -Wno-dev build: project: Build\VS\RapidJSON.sln parallel: true verbosity: minimal test_script: - cd Build\VS && if %CONFIGURATION%==Debug (ctest --verbose -E perftest --build-config %CONFIGURATION%) else (ctest --verbose --build-config %CONFIGURATION%) opentimelineio-0.18.1/src/deps/rapidjson/CHANGELOG.md0000664000175000017500000001524215110656146017744 0ustar meme# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ## 1.1.0 - 2016-08-25 ### Added * Add GenericDocument ctor overload to specify JSON type (#369) * Add FAQ (#372, #373, #374, #376) * Add forward declaration header `fwd.h` * Add @PlatformIO Library Registry manifest file (#400) * Implement assignment operator for BigInteger (#404) * Add comments support (#443) * Adding coapp definition (#460) * documenttest.cpp: EXPECT_THROW when checking empty allocator (470) * GenericDocument: add implicit conversion to ParseResult (#480) * Use with C++ linkage on Windows ARM (#485) * Detect little endian for Microsoft ARM targets * Check Nan/Inf when writing a double (#510) * Add JSON Schema Implementation (#522) * Add iostream wrapper (#530) * Add Jsonx example for converting JSON into JSONx (a XML format) (#531) * Add optional unresolvedTokenIndex parameter to Pointer::Get() (#532) * Add encoding validation option for Writer/PrettyWriter (#534) * Add Writer::SetMaxDecimalPlaces() (#536) * Support {0, } and {0, m} in Regex (#539) * Add Value::Get/SetFloat(), Value::IsLossLessFloat/Double() (#540) * Add stream position check to reader unit tests (#541) * Add Templated accessors and range-based for (#542) * Add (Pretty)Writer::RawValue() (#543) * Add Document::Parse(std::string), Document::Parse(const char*, size_t length) and related APIs. (#553) * Add move constructor for GenericSchemaDocument (#554) * Add VS2010 and VS2015 to AppVeyor CI (#555) * Add parse-by-parts example (#556, #562) * Support parse number as string (#564, #589) * Add kFormatSingleLineArray for PrettyWriter (#577) * Added optional support for trailing commas (#584) * Added filterkey and filterkeydom examples (#615) * Added npm docs (#639) * Allow options for writing and parsing NaN/Infinity (#641) * Add std::string overload to PrettyWriter::Key() when RAPIDJSON_HAS_STDSTRING is defined (#698) ### Fixed * Fix gcc/clang/vc warnings (#350, #394, #397, #444, #447, #473, #515, #582, #589, #595, #667) * Fix documentation (#482, #511, #550, #557, #614, #635, #660) * Fix emscripten alignment issue (#535) * Fix missing allocator to uses of AddMember in document (#365) * CMake will no longer complain that the minimum CMake version is not specified (#501) * Make it usable with old VC8 (VS2005) (#383) * Prohibit C++11 move from Document to Value (#391) * Try to fix incorrect 64-bit alignment (#419) * Check return of fwrite to avoid warn_unused_result build failures (#421) * Fix UB in GenericDocument::ParseStream (#426) * Keep Document value unchanged on parse error (#439) * Add missing return statement (#450) * Fix Document::Parse(const Ch*) for transcoding (#478) * encodings.h: fix typo in preprocessor condition (#495) * Custom Microsoft headers are necessary only for Visual Studio 2012 and lower (#559) * Fix memory leak for invalid regex (26e69ffde95ba4773ab06db6457b78f308716f4b) * Fix a bug in schema minimum/maximum keywords for 64-bit integer (e7149d665941068ccf8c565e77495521331cf390) * Fix a crash bug in regex (#605) * Fix schema "required" keyword cannot handle duplicated keys (#609) * Fix cmake CMP0054 warning (#612) * Added missing include guards in istreamwrapper.h and ostreamwrapper.h (#634) * Fix undefined behaviour (#646) * Fix buffer overrun using PutN (#673) * Fix rapidjson::value::Get() may returns wrong data (#681) * Add Flush() for all value types (#689) * Handle malloc() fail in PoolAllocator (#691) * Fix builds on x32 platform. #703 ### Changed * Clarify problematic JSON license (#392) * Move Travis to container based infrastructure (#504, #558) * Make whitespace array more compact (#513) * Optimize Writer::WriteString() with SIMD (#544) * x86-64 48-bit pointer optimization for GenericValue (#546) * Define RAPIDJSON_HAS_CXX11_RVALUE_REFS directly in clang (#617) * Make GenericSchemaDocument constructor explicit (#674) * Optimize FindMember when use std::string (#690) ## [1.0.2] - 2015-05-14 ### Added * Add Value::XXXMember(...) overloads for std::string (#335) ### Fixed * Include rapidjson.h for all internal/error headers. * Parsing some numbers incorrectly in full-precision mode (`kFullPrecisionParseFlag`) (#342) * Fix some numbers parsed incorrectly (#336) * Fix alignment of 64bit platforms (#328) * Fix MemoryPoolAllocator::Clear() to clear user-buffer (0691502573f1afd3341073dd24b12c3db20fbde4) ### Changed * CMakeLists for include as a thirdparty in projects (#334, #337) * Change Document::ParseStream() to use stack allocator for Reader (ffbe38614732af8e0b3abdc8b50071f386a4a685) ## [1.0.1] - 2015-04-25 ### Added * Changelog following [Keep a CHANGELOG](https://github.com/olivierlacan/keep-a-changelog) suggestions. ### Fixed * Parsing of some numbers (e.g. "1e-00011111111111") causing assertion (#314). * Visual C++ 32-bit compilation error in `diyfp.h` (#317). ## [1.0.0] - 2015-04-22 ### Added * 100% [Coverall](https://coveralls.io/r/Tencent/rapidjson?branch=master) coverage. * Version macros (#311) ### Fixed * A bug in trimming long number sequence (4824f12efbf01af72b8cb6fc96fae7b097b73015). * Double quote in unicode escape (#288). * Negative zero roundtrip (double only) (#289). * Standardize behavior of `memcpy()` and `malloc()` (0c5c1538dcfc7f160e5a4aa208ddf092c787be5a, #305, 0e8bbe5e3ef375e7f052f556878be0bd79e9062d). ### Removed * Remove an invalid `Document::ParseInsitu()` API (e7f1c6dd08b522cfcf9aed58a333bd9a0c0ccbeb). ## 1.0-beta - 2015-04-8 ### Added * RFC 7159 (#101) * Optional Iterative Parser (#76) * Deep-copy values (#20) * Error code and message (#27) * ASCII Encoding (#70) * `kParseStopWhenDoneFlag` (#83) * `kParseFullPrecisionFlag` (881c91d696f06b7f302af6d04ec14dd08db66ceb) * Add `Key()` to handler concept (#134) * C++11 compatibility and support (#128) * Optimized number-to-string and vice versa conversions (#137, #80) * Short-String Optimization (#131) * Local stream optimization by traits (#32) * Travis & Appveyor Continuous Integration, with Valgrind verification (#24, #242) * Redo all documentation (English, Simplified Chinese) ### Changed * Copyright ownership transferred to THL A29 Limited (a Tencent company). * Migrating from Premake to CMAKE (#192) * Resolve all warning reports ### Removed * Remove other JSON libraries for performance comparison (#180) ## 0.11 - 2012-11-16 ## 0.1 - 2011-11-18 [Unreleased]: https://github.com/Tencent/rapidjson/compare/v1.1.0...HEAD [1.1.0]: https://github.com/Tencent/rapidjson/compare/v1.0.2...v1.1.0 [1.0.2]: https://github.com/Tencent/rapidjson/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/Tencent/rapidjson/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/Tencent/rapidjson/compare/v1.0-beta...v1.0.0 opentimelineio-0.18.1/src/deps/rapidjson/include_dirs.js0000664000175000017500000000013615110656147021132 0ustar memevar path = require('path'); console.log(path.join(path.relative('.', __dirname), 'include')); opentimelineio-0.18.1/src/deps/rapidjson/readme.md0000664000175000017500000002561215110656147017715 0ustar meme![RapidJSON logo](doc/logo/rapidjson.png) ![Release version](https://img.shields.io/badge/release-v1.1.0-blue.svg) ## A fast JSON parser/generator for C++ with both SAX/DOM style API Tencent is pleased to support the open source community by making RapidJSON available. Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. * [RapidJSON GitHub](https://github.com/Tencent/rapidjson/) * RapidJSON Documentation * [English](http://rapidjson.org/) * [简体中文](http://rapidjson.org/zh-cn/) * [GitBook](https://www.gitbook.com/book/miloyip/rapidjson/) with downloadable PDF/EPUB/MOBI, without API reference. ## Build status | [Linux][lin-link] | [Windows][win-link] | [Coveralls][cov-link] | | :---------------: | :-----------------: | :-------------------: | | ![lin-badge] | ![win-badge] | ![cov-badge] | [lin-badge]: https://travis-ci.org/Tencent/rapidjson.svg?branch=master "Travis build status" [lin-link]: https://travis-ci.org/Tencent/rapidjson "Travis build status" [win-badge]: https://ci.appveyor.com/api/projects/status/l6qulgqahcayidrf/branch/master?svg=true "AppVeyor build status" [win-link]: https://ci.appveyor.com/project/miloyip/rapidjson-0fdqj/branch/master "AppVeyor build status" [cov-badge]: https://coveralls.io/repos/Tencent/rapidjson/badge.svg?branch=master "Coveralls coverage" [cov-link]: https://coveralls.io/r/Tencent/rapidjson?branch=master "Coveralls coverage" ## Introduction RapidJSON is a JSON parser and generator for C++. It was inspired by [RapidXml](http://rapidxml.sourceforge.net/). * RapidJSON is **small** but **complete**. It supports both SAX and DOM style API. The SAX parser is only a half thousand lines of code. * RapidJSON is **fast**. Its performance can be comparable to `strlen()`. It also optionally supports SSE2/SSE4.2 for acceleration. * RapidJSON is **self-contained** and **header-only**. It does not depend on external libraries such as BOOST. It even does not depend on STL. * RapidJSON is **memory-friendly**. Each JSON value occupies exactly 16 bytes for most 32/64-bit machines (excluding text string). By default it uses a fast memory allocator, and the parser allocates memory compactly during parsing. * RapidJSON is **Unicode-friendly**. It supports UTF-8, UTF-16, UTF-32 (LE & BE), and their detection, validation and transcoding internally. For example, you can read a UTF-8 file and let RapidJSON transcode the JSON strings into UTF-16 in the DOM. It also supports surrogates and "\u0000" (null character). More features can be read [here](doc/features.md). JSON(JavaScript Object Notation) is a light-weight data exchange format. RapidJSON should be in full compliance with RFC7159/ECMA-404, with optional support of relaxed syntax. More information about JSON can be obtained at * [Introducing JSON](http://json.org/) * [RFC7159: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc7159) * [Standard ECMA-404: The JSON Data Interchange Format](https://www.ecma-international.org/publications/standards/Ecma-404.htm) ## Highlights in v1.1 (2016-8-25) * Added [JSON Pointer](doc/pointer.md) * Added [JSON Schema](doc/schema.md) * Added [relaxed JSON syntax](doc/dom.md) (comment, trailing comma, NaN/Infinity) * Iterating array/object with [C++11 Range-based for loop](doc/tutorial.md) * Reduce memory overhead of each `Value` from 24 bytes to 16 bytes in x86-64 architecture. For other changes please refer to [change log](CHANGELOG.md). ## Compatibility RapidJSON is cross-platform. Some platform/compiler combinations which have been tested are shown as follows. * Visual C++ 2008/2010/2013 on Windows (32/64-bit) * GNU C++ 3.8.x on Cygwin * Clang 3.4 on Mac OS X (32/64-bit) and iOS * Clang 3.4 on Android NDK Users can build and run the unit tests on their platform/compiler. ## Installation RapidJSON is a header-only C++ library. Just copy the `include/rapidjson` folder to system or project's include path. Alternatively, if you are using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager you can download and install rapidjson with CMake integration in a single command: * vcpkg install rapidjson RapidJSON uses following software as its dependencies: * [CMake](https://cmake.org/) as a general build tool * (optional) [Doxygen](http://www.doxygen.org) to build documentation * (optional) [googletest](https://github.com/google/googletest) for unit and performance testing To generate user documentation and run tests please proceed with the steps below: 1. Execute `git submodule update --init` to get the files of thirdparty submodules (google test). 2. Create directory called `build` in rapidjson source directory. 3. Change to `build` directory and run `cmake ..` command to configure your build. Windows users can do the same with cmake-gui application. 4. On Windows, build the solution found in the build directory. On Linux, run `make` from the build directory. On successful build you will find compiled test and example binaries in `bin` directory. The generated documentation will be available in `doc/html` directory of the build tree. To run tests after finished build please run `make test` or `ctest` from your build tree. You can get detailed output using `ctest -V` command. It is possible to install library system-wide by running `make install` command from the build tree with administrative privileges. This will install all files according to system preferences. Once RapidJSON is installed, it is possible to use it from other CMake projects by adding `find_package(RapidJSON)` line to your CMakeLists.txt. ## Usage at a glance This simple example parses a JSON string into a document (DOM), make a simple modification of the DOM, and finally stringify the DOM to a JSON string. ~~~~~~~~~~cpp // rapidjson/example/simpledom/simpledom.cpp` #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" #include using namespace rapidjson; int main() { // 1. Parse a JSON string into DOM. const char* json = "{\"project\":\"rapidjson\",\"stars\":10}"; Document d; d.Parse(json); // 2. Modify it by DOM. Value& s = d["stars"]; s.SetInt(s.GetInt() + 1); // 3. Stringify the DOM StringBuffer buffer; Writer writer(buffer); d.Accept(writer); // Output {"project":"rapidjson","stars":11} std::cout << buffer.GetString() << std::endl; return 0; } ~~~~~~~~~~ Note that this example did not handle potential errors. The following diagram shows the process. ![simpledom](doc/diagram/simpledom.png) More [examples](https://github.com/Tencent/rapidjson/tree/master/example) are available: * DOM API * [tutorial](https://github.com/Tencent/rapidjson/blob/master/example/tutorial/tutorial.cpp): Basic usage of DOM API. * SAX API * [simplereader](https://github.com/Tencent/rapidjson/blob/master/example/simplereader/simplereader.cpp): Dumps all SAX events while parsing a JSON by `Reader`. * [condense](https://github.com/Tencent/rapidjson/blob/master/example/condense/condense.cpp): A command line tool to rewrite a JSON, with all whitespaces removed. * [pretty](https://github.com/Tencent/rapidjson/blob/master/example/pretty/pretty.cpp): A command line tool to rewrite a JSON with indents and newlines by `PrettyWriter`. * [capitalize](https://github.com/Tencent/rapidjson/blob/master/example/capitalize/capitalize.cpp): A command line tool to capitalize strings in JSON. * [messagereader](https://github.com/Tencent/rapidjson/blob/master/example/messagereader/messagereader.cpp): Parse a JSON message with SAX API. * [serialize](https://github.com/Tencent/rapidjson/blob/master/example/serialize/serialize.cpp): Serialize a C++ object into JSON with SAX API. * [jsonx](https://github.com/Tencent/rapidjson/blob/master/example/jsonx/jsonx.cpp): Implements a `JsonxWriter` which stringify SAX events into [JSONx](https://www-01.ibm.com/support/knowledgecenter/SS9H2Y_7.1.0/com.ibm.dp.doc/json_jsonx.html) (a kind of XML) format. The example is a command line tool which converts input JSON into JSONx format. * Schema * [schemavalidator](https://github.com/Tencent/rapidjson/blob/master/example/schemavalidator/schemavalidator.cpp) : A command line tool to validate a JSON with a JSON schema. * Advanced * [prettyauto](https://github.com/Tencent/rapidjson/blob/master/example/prettyauto/prettyauto.cpp): A modified version of [pretty](https://github.com/Tencent/rapidjson/blob/master/example/pretty/pretty.cpp) to automatically handle JSON with any UTF encodings. * [parsebyparts](https://github.com/Tencent/rapidjson/blob/master/example/parsebyparts/parsebyparts.cpp): Implements an `AsyncDocumentParser` which can parse JSON in parts, using C++11 thread. * [filterkey](https://github.com/Tencent/rapidjson/blob/master/example/filterkey/filterkey.cpp): A command line tool to remove all values with user-specified key. * [filterkeydom](https://github.com/Tencent/rapidjson/blob/master/example/filterkeydom/filterkeydom.cpp): Same tool as above, but it demonstrates how to use a generator to populate a `Document`. ## Contributing RapidJSON welcomes contributions. When contributing, please follow the code below. ### Issues Feel free to submit issues and enhancement requests. Please help us by providing **minimal reproducible examples**, because source code is easier to let other people understand what happens. For crash problems on certain platforms, please bring stack dump content with the detail of the OS, compiler, etc. Please try breakpoint debugging first, tell us what you found, see if we can start exploring based on more information been prepared. ### Workflow In general, we follow the "fork-and-pull" Git workflow. 1. **Fork** the repo on GitHub 2. **Clone** the project to your own machine 3. **Checkout** a new branch on your fork, start developing on the branch 4. **Test** the change before commit, Make sure the changes pass all the tests, including `unittest` and `preftest`, please add test case for each new feature or bug-fix if needed. 5. **Commit** changes to your own branch 6. **Push** your work back up to your fork 7. Submit a **Pull request** so that we can review your changes NOTE: Be sure to merge the latest from "upstream" before making a pull request! ### Copyright and Licensing You can copy and paste the license summary from below. ``` Tencent is pleased to support the open source community by making RapidJSON available. Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://opensource.org/licenses/MIT Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` opentimelineio-0.18.1/src/deps/rapidjson/travis-doxygen.sh0000775000175000017500000000633615110656147021462 0ustar meme#!/bin/bash # Update Doxygen documentation after push to 'master'. # Author: @pah set -e DOXYGEN_VER=1_8_16 DOXYGEN_URL="https://codeload.github.com/doxygen/doxygen/tar.gz/Release_${DOXYGEN_VER}" : ${GITHUB_REPO:="Tencent/rapidjson"} GITHUB_HOST="github.com" GITHUB_CLONE="git://${GITHUB_HOST}/${GITHUB_REPO}" GITHUB_URL="https://${GITHUB_HOST}/${GITHUB_PUSH-${GITHUB_REPO}}" # if not set, ignore password #GIT_ASKPASS="${TRAVIS_BUILD_DIR}/gh_ignore_askpass.sh" skip() { echo "$@" 1>&2 echo "Exiting..." 1>&2 exit 0 } abort() { echo "Error: $@" 1>&2 echo "Exiting..." 1>&2 exit 1 } # TRAVIS_BUILD_DIR not set, exiting [ -d "${TRAVIS_BUILD_DIR-/nonexistent}" ] || \ abort '${TRAVIS_BUILD_DIR} not set or nonexistent.' # check for pull-requests [ "${TRAVIS_PULL_REQUEST}" = "false" ] || \ skip "Not running Doxygen for pull-requests." # check for branch name [ "${TRAVIS_BRANCH}" = "master" ] || \ skip "Running Doxygen only for updates on 'master' branch (current: ${TRAVIS_BRANCH})." # check for job number # [ "${TRAVIS_JOB_NUMBER}" = "${TRAVIS_BUILD_NUMBER}.1" ] || \ # skip "Running Doxygen only on first job of build ${TRAVIS_BUILD_NUMBER} (current: ${TRAVIS_JOB_NUMBER})." # install doxygen binary distribution doxygen_install() { cd ${TMPDIR-/tmp} curl ${DOXYGEN_URL} -o doxygen.tar.gz tar zxvf doxygen.tar.gz mkdir doxygen_build cd doxygen_build cmake ../doxygen-Release_${DOXYGEN_VER}/ make export PATH="${TMPDIR-/tmp}/doxygen_build/bin:$PATH" cd ../../ } doxygen_run() { cd "${TRAVIS_BUILD_DIR}"; doxygen ${TRAVIS_BUILD_DIR}/build/doc/Doxyfile; doxygen ${TRAVIS_BUILD_DIR}/build/doc/Doxyfile.zh-cn; } gh_pages_prepare() { cd "${TRAVIS_BUILD_DIR}/build/doc"; [ ! -d "html" ] || \ abort "Doxygen target directory already exists." git --version git clone --single-branch -b gh-pages "${GITHUB_CLONE}" html cd html # setup git config (with defaults) git config user.name "${GIT_NAME-travis}" git config user.email "${GIT_EMAIL-"travis@localhost"}" # clean working dir rm -f .git/index git clean -df } gh_pages_commit() { cd "${TRAVIS_BUILD_DIR}/build/doc/html"; echo "rapidjson.org" > CNAME git add --all; git diff-index --quiet HEAD || git commit -m "Automatic doxygen build"; } gh_setup_askpass() { cat > ${GIT_ASKPASS} < ${HOME}/.git-credentials ; \ chmod go-rw ${HOME}/.git-credentials ) ) # push to GitHub git push origin gh-pages } doxygen_install gh_pages_prepare doxygen_run gh_pages_commit gh_pages_push opentimelineio-0.18.1/src/deps/rapidjson/doc/0000775000175000017500000000000015110656147016675 5ustar memeopentimelineio-0.18.1/src/deps/rapidjson/doc/faq.zh-cn.md0000664000175000017500000003526615110656147021020 0ustar meme# 常见问题 [TOC] ## 一般问题 1. RapidJSON 是什么? RapidJSON 是一个 C++ 库,用于解析及生成 JSON。读者可参考它的所有 [特点](doc/features.zh-cn.md)。 2. 为什么称作 RapidJSON? 它的灵感来自于 [RapidXML](http://rapidxml.sourceforge.net/),RapidXML 是一个高速的 XML DOM 解析器。 3. RapidJSON 与 RapidXML 相似么? RapidJSON 借镜了 RapidXML 的一些设计, 包括原位(*in situ*)解析、只有头文件的库。但两者的 API 是完全不同的。此外 RapidJSON 也提供许多 RapidXML 没有的特点。 4. RapidJSON 是免费的么? 是的,它在 MIT 协议下免费。它可用于商业软件。详情请参看 [license.txt](https://github.com/Tencent/rapidjson/blob/master/license.txt)。 5. RapidJSON 很小么?它有何依赖? 是的。在 Windows 上,一个解析 JSON 并打印出统计的可执行文件少于 30KB。 RapidJSON 仅依赖于 C++ 标准库。 6. 怎样安装 RapidJSON? 见 [安装一节](../readme.zh-cn.md#安装)。 7. RapidJSON 能否运行于我的平台? 社区已在多个操作系统/编译器/CPU 架构的组合上测试 RapidJSON。但我们无法确保它能运行于你特定的平台上。只需要生成及执行单元测试便能获取答案。 8. RapidJSON 支持 C++03 么?C++11 呢? RapidJSON 开始时在 C++03 上实现。后来加入了可选的 C++11 特性支持(如转移构造函数、`noexcept`)。RapidJSON 应该兼容所有遵从 C++03 或 C++11 的编译器。 9. RapidJSON 是否真的用于实际应用? 是的。它被配置于前台及后台的真实应用中。一个社区成员说 RapidJSON 在他们的系统中每日解析 5 千万个 JSON。 10. RapidJSON 是如何被测试的? RapidJSON 包含一组单元测试去执行自动测试。[Travis](https://travis-ci.org/Tencent/rapidjson/)(供 Linux 平台)及 [AppVeyor](https://ci.appveyor.com/project/Tencent/rapidjson/)(供 Windows 平台)会对所有修改进行编译及执行单元测试。在 Linux 下还会使用 Valgrind 去检测内存泄漏。 11. RapidJSON 是否有完整的文档? RapidJSON 提供了使用手册及 API 说明文档。 12. 有没有其他替代品? 有许多替代品。例如 [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) 列出了一些开源的 C/C++ JSON 库。[json.org](http://www.json.org/) 也有一个列表。 ## JSON 1. 什么是 JSON? JSON (JavaScript Object Notation) 是一个轻量的数据交换格式。它使用人类可读的文本格式。更多关于 JSON 的细节可考 [RFC7159](http://www.ietf.org/rfc/rfc7159.txt) 及 [ECMA-404](http://www.ecma-international.org/publications/standards/Ecma-404.htm)。 2. JSON 有什么应用场合? JSON 常用于网页应用程序,以传送结构化数据。它也可作为文件格式用于数据持久化。 3. RapidJSON 是否符合 JSON 标准? 是。RapidJSON 完全符合 [RFC7159](http://www.ietf.org/rfc/rfc7159.txt) 及 [ECMA-404](http://www.ecma-international.org/publications/standards/Ecma-404.htm)。它能处理一些特殊情况,例如支持 JSON 字符串中含有空字符及代理对(surrogate pair)。 4. RapidJSON 是否支持宽松的语法? 目前不支持。RapidJSON 只支持严格的标准格式。宽松语法可以在这个 [issue](https://github.com/Tencent/rapidjson/issues/36) 中进行讨论。 ## DOM 与 SAX 1. 什么是 DOM 风格 API? Document Object Model(DOM)是一个储存于内存的 JSON 表示方式,用于查询及修改 JSON。 2. 什么是 SAX 风格 API? SAX 是一个事件驱动的 API,用于解析及生成 JSON。 3. 我应用 DOM 还是 SAX? DOM 易于查询及修改。SAX 则是非常快及省内存的,但通常较难使用。 4. 什么是原位(*in situ*)解析? 原位解析会把 JSON 字符串直接解码至输入的 JSON 中。这是一个优化,可减少内存消耗及提升性能,但输入的 JSON 会被更改。进一步细节请参考 [原位解析](doc/dom.zh-cn.md) 。 5. 什么时候会产生解析错误? 当输入的 JSON 包含非法语法,或不能表示一个值(如 Number 太大),或解析器的处理器中断解析过程,解析器都会产生一个错误。详情请参考 [解析错误](doc/dom.zh-cn.md)。 6. 有什么错误信息? 错误信息存储在 `ParseResult`,它包含错误代号及偏移值(从 JSON 开始至错误处的字符数目)。可以把错误代号翻译为人类可读的错误讯息。 7. 为何不只使用 `double` 去表示 JSON number? 一些应用需要使用 64 位无号/有号整数。这些整数不能无损地转换成 `double`。因此解析器会检测一个 JSON number 是否能转换至各种整数类型及 `double`。 8. 如何清空并最小化 `document` 或 `value` 的容量? 调用 `SetXXX()` 方法 - 这些方法会调用析构函数,并重建空的 Object 或 Array: ~~~~~~~~~~cpp Document d; ... d.SetObject(); // clear and minimize ~~~~~~~~~~ 另外,也可以参考在 [C++ swap with temporary idiom](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Clear-and-minimize) 中的一种等价的方法: ~~~~~~~~~~cpp Value(kObjectType).Swap(d); ~~~~~~~~~~ 或者,使用这个稍微长一点的代码也能完成同样的事情: ~~~~~~~~~~cpp d.Swap(Value(kObjectType).Move()); ~~~~~~~~~~ 9. 如何将一个 `document` 节点插入到另一个 `document` 中? 比如有以下两个 document(DOM): ~~~~~~~~~~cpp Document person; person.Parse("{\"person\":{\"name\":{\"first\":\"Adam\",\"last\":\"Thomas\"}}}"); Document address; address.Parse("{\"address\":{\"city\":\"Moscow\",\"street\":\"Quiet\"}}"); ~~~~~~~~~~ 假设我们希望将整个 `address` 插入到 `person` 中,作为其的一个子节点: ~~~~~~~~~~js { "person": { "name": { "first": "Adam", "last": "Thomas" }, "address": { "city": "Moscow", "street": "Quiet" } } } ~~~~~~~~~~ 在插入节点的过程中需要注意 `document` 和 `value` 的生命周期并且正确地使用 allocator 进行内存分配和管理。 一个简单有效的方法就是修改上述 `address` 变量的定义,让其使用 `person` 的 allocator 初始化,然后将其添加到根节点。 ~~~~~~~~~~cpp Documnet address(&person.GetAllocator()); ... person["person"].AddMember("address", address["address"], person.GetAllocator()); ~~~~~~~~~~ 当然,如果你不想通过显式地写出 `address` 的 key 来得到其值,可以使用迭代器来实现: ~~~~~~~~~~cpp auto addressRoot = address.MemberBegin(); person["person"].AddMember(addressRoot->name, addressRoot->value, person.GetAllocator()); ~~~~~~~~~~ 此外,还可以通过深拷贝 address document 来实现: ~~~~~~~~~~cpp Value addressValue = Value(address["address"], person.GetAllocator()); person["person"].AddMember("address", addressValue, person.GetAllocator()); ~~~~~~~~~~ ## Document/Value (DOM) 1. 什么是转移语义?为什么? `Value` 不用复制语义,而使用了转移语义。这是指,当把来源值赋值于目标值时,来源值的所有权会转移至目标值。 由于转移快于复制,此设计决定强迫使用者注意到复制的消耗。 2. 怎样去复制一个值? 有两个 API 可用:含 allocator 的构造函数,以及 `CopyFrom()`。可参考 [深复制 Value](doc/tutorial.zh-cn.md) 里的用例。 3. 为什么我需要提供字符串的长度? 由于 C 字符串是空字符结尾的,需要使用 `strlen()` 去计算其长度,这是线性复杂度的操作。若使用者已知字符串的长度,对很多操作来说会造成不必要的消耗。 此外,RapidJSON 可处理含有 `\u0000`(空字符)的字符串。若一个字符串含有空字符,`strlen()` 便不能返回真正的字符串长度。在这种情况下使用者必须明确地提供字符串长度。 4. 为什么在许多 DOM 操作 API 中要提供分配器作为参数? 由于这些 API 是 `Value` 的成员函数,我们不希望为每个 `Value` 储存一个分配器指针。 5. 它会转换各种数值类型么? 当使用 `GetInt()`、`GetUint()` 等 API 时,可能会发生转换。对于整数至整数转换,仅当保证转换安全才会转换(否则会断言失败)。然而,当把一个 64 位有号/无号整数转换至 double 时,它会转换,但有可能会损失精度。含有小数的数字、或大于 64 位的整数,都只能使用 `GetDouble()` 获取其值。 ## Reader/Writer (SAX) 1. 为什么不仅仅用 `printf` 输出一个 JSON?为什么需要 `Writer`? 最重要的是,`Writer` 能确保输出的 JSON 是格式正确的。错误地调用 SAX 事件(如 `StartObject()` 错配 `EndArray()`)会造成断言失败。此外,`Writer` 会把字符串进行转义(如 `\n`)。最后,`printf()` 的数值输出可能并不是一个合法的 JSON number,特别是某些 locale 会有数字分隔符。而且 `Writer` 的数值字符串转换是使用非常快的算法来实现的,胜过 `printf()` 及 `iostream`。 2. 我能否暂停解析过程,并在稍后继续? 基于性能考虑,目前版本并不直接支持此功能。然而,若执行环境支持多线程,使用者可以在另一线程解析 JSON,并通过阻塞输入流去暂停。 ## Unicode 1. 它是否支持 UTF-8、UTF-16 及其他格式? 是。它完全支持 UTF-8、UTF-16(大端/小端)、UTF-32(大端/小端)及 ASCII。 2. 它能否检测编码的合法性? 能。只需把 `kParseValidateEncodingFlag` 参考传给 `Parse()`。若发现在输入流中有非法的编码,它就会产生 `kParseErrorStringInvalidEncoding` 错误。 3. 什么是代理对(surrogate pair)?RapidJSON 是否支持? JSON 使用 UTF-16 编码去转义 Unicode 字符,例如 `\u5927` 表示中文字“大”。要处理基本多文种平面(basic multilingual plane,BMP)以外的字符时,UTF-16 会把那些字符编码成两个 16 位值,这称为 UTF-16 代理对。例如,绘文字字符 U+1F602 在 JSON 中可被编码成 `\uD83D\uDE02`。 RapidJSON 完全支持解析及生成 UTF-16 代理对。 4. 它能否处理 JSON 字符串中的 `\u0000`(空字符)? 能。RapidJSON 完全支持 JSON 字符串中的空字符。然而,使用者需要注意到这件事,并使用 `GetStringLength()` 及相关 API 去取得字符串真正长度。 5. 能否对所有非 ASCII 字符输出成 `\uxxxx` 形式? 可以。只要在 `Writer` 中使用 `ASCII<>` 作为输出编码参数,就可以强逼转义那些字符。 ## 流 1. 我有一个很大的 JSON 文件。我应否把它整个载入内存中? 使用者可使用 `FileReadStream` 去逐块读入文件。但若使用于原位解析,必须载入整个文件。 2. 我能否解析一个从网络上串流进来的 JSON? 可以。使用者可根据 `FileReadStream` 的实现,去实现一个自定义的流。 3. 我不知道一些 JSON 将会使用哪种编码。怎样处理它们? 你可以使用 `AutoUTFInputStream`,它能自动检测输入流的编码。然而,它会带来一些性能开销。 4. 什么是 BOM?RapidJSON 怎样处理它? [字节顺序标记(byte order mark, BOM)](http://en.wikipedia.org/wiki/Byte_order_mark) 有时会出现于文件/流的开始,以表示其 UTF 编码类型。 RapidJSON 的 `EncodedInputStream` 可检测/跳过 BOM。`EncodedOutputStream` 可选择是否写入 BOM。可参考 [编码流](doc/stream.zh-cn.md) 中的例子。 5. 为什么会涉及大端/小端? 流的大端/小端是 UTF-16 及 UTF-32 流要处理的问题,而 UTF-8 不需要处理。 ## 性能 1. RapidJSON 是否真的快? 是。它可能是最快的开源 JSON 库。有一个 [评测](https://github.com/miloyip/nativejson-benchmark) 评估 C/C++ JSON 库的性能。 2. 为什么它会快? RapidJSON 的许多设计是针对时间/空间性能来设计的,这些决定可能会影响 API 的易用性。此外,它也使用了许多底层优化(内部函数/intrinsic、SIMD)及特别的算法(自定义的 double 至字符串转换、字符串至 double 的转换)。 3. 什是是 SIMD?它如何用于 RapidJSON? [SIMD](http://en.wikipedia.org/wiki/SIMD) 指令可以在现代 CPU 中执行并行运算。RapidJSON 支持使用 Intel 的 SSE2/SSE4.2 和 ARM 的 Neon 来加速对空白符、制表符、回车符和换行符的过滤处理。在解析含缩进的 JSON 时,这能提升性能。只要定义名为 `RAPIDJSON_SSE2` ,`RAPIDJSON_SSE42` 或 `RAPIDJSON_NEON` 的宏,就能启动这个功能。然而,若在不支持这些指令集的机器上执行这些可执行文件,会导致崩溃。 4. 它会消耗许多内存么? RapidJSON 的设计目标是减低内存占用。 在 SAX API 中,`Reader` 消耗的内存与 JSON 树深度加上最长 JSON 字符成正比。 在 DOM API 中,每个 `Value` 在 32/64 位架构下分别消耗 16/24 字节。RapidJSON 也使用一个特殊的内存分配器去减少分配的额外开销。 5. 高性能的意义何在? 有些应用程序需要处理非常大的 JSON 文件。而有些后台应用程序需要处理大量的 JSON。达到高性能同时改善延时及吞吐量。更广义来说,这也可以节省能源。 ## 八卦 1. 谁是 RapidJSON 的开发者? 叶劲峰(Milo Yip,[miloyip](https://github.com/miloyip))是 RapidJSON 的原作者。全世界许多贡献者一直在改善 RapidJSON。Philipp A. Hartmann([pah](https://github.com/pah))实现了许多改进,也设置了自动化测试,而且还参与许多社区讨论。丁欧南(Don Ding,[thebusytypist](https://github.com/thebusytypist))实现了迭代式解析器。Andrii Senkovych([jollyroger](https://github.com/jollyroger))完成了向 CMake 的迁移。Kosta([Kosta-Github](https://github.com/Kosta-Github))提供了一个非常灵巧的短字符串优化。也需要感谢其他献者及社区成员。 2. 为何你要开发 RapidJSON? 在 2011 年开始这项目时,它只是一个兴趣项目。Milo Yip 是一个游戏程序员,他在那时候认识到 JSON 并希望在未来的项目中使用。由于 JSON 好像很简单,他希望写一个快速的仅有头文件的程序库。 3. 为什么开发中段有一段长期空档? 主要是个人因素,例如加入新家庭成员。另外,Milo Yip 也花了许多业余时间去翻译 Jason Gregory 的《Game Engine Architecture》至中文版《游戏引擎架构》。 4. 为什么这个项目从 Google Code 搬到 GitHub? 这是大势所趋,而且 GitHub 更为强大及方便。 opentimelineio-0.18.1/src/deps/rapidjson/doc/npm.md0000664000175000017500000000055315110656147020014 0ustar meme## NPM # package.json {#package} ~~~~~~~~~~js { ... "dependencies": { ... "rapidjson": "git@github.com:Tencent/rapidjson.git" }, ... "gypfile": true } ~~~~~~~~~~ # binding.gyp {#binding} ~~~~~~~~~~js { ... 'targets': [ { ... 'include_dirs': [ ' $projectname: $title $title $treeview $search $mathjax $extrastylesheet
$searchbox opentimelineio-0.18.1/src/deps/rapidjson/doc/misc/footer.html0000664000175000017500000000040015110656147022006 0ustar meme opentimelineio-0.18.1/src/deps/rapidjson/doc/misc/DoxygenLayout.xml0000664000175000017500000001371215110656147023171 0ustar meme opentimelineio-0.18.1/src/deps/rapidjson/doc/misc/doxygenextra.css0000664000175000017500000001465415110656147023075 0ustar memebody code { margin: 0; border: 1px solid #ddd; background-color: #f8f8f8; border-radius: 3px; padding: 0; } a { color: #4183c4; } a.el { font-weight: normal; } body, table, div, p, dl { color: #333333; font-family: Helvetica, arial, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; line-height: 25.5px; } body { background-color: #eee; } div.header { background-image: none; background-color: white; margin: 0px; border: 0px; } div.headertitle { width: 858px; margin: 30px; padding: 0px; } div.toc { background-color: #f8f8f8; border-color: #ddd; margin-right: 10px; margin-left: 20px; } div.toc h3 { color: #333333; font-family: Helvetica, arial, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; font-style: normal; font-variant: normal; font-weight: normal; } div.toc li { color: #333333; font-family: Helvetica, arial, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; } .title { font-size: 2.5em; line-height: 63.75px; border-bottom: 1px solid #ddd; margin-bottom: 15px; margin-left: 0px; margin-right: 0px; margin-top: 0px; } .summary { float: none !important; width: auto !important; padding-top: 10px; padding-right: 10px !important; } .summary + .headertitle .title { font-size: 1.5em; line-height: 2.0em; } body h1 { font-size: 2em; line-height: 1.7; border-bottom: 1px solid #eee; margin: 1em 0 15px; padding: 0; overflow: hidden; } body h2 { font-size: 1.5em; line-height: 1.7; margin: 1em 0 15px; padding: 0; } pre.fragment { font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; line-height: 19px; } table.doxtable th { background-color: #f8f8f8; color: #333333; font-size: 15px; } table.doxtable td, table.doxtable th { border: 1px solid #ddd; } #doc-content { background-color: #fff; width: 918px; height: auto !important; margin-left: 270px !important; } div.contents { width: 858px; margin: 30px; } div.line { font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; line-height: 19px; } tt, code, pre { font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; } div.fragment { background-color: #f8f8f8; border: 1px solid #ddd; font-size: 13px; line-height: 19px; overflow: auto; padding: 6px 10px; border-radius: 3px; } #topbanner { position: fixed; margin: 15px; z-index: 101; } #projectname { font-family: Helvetica, arial, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 38px; font-weight: bold; line-height: 63.75px; margin: 0px; padding: 2px 0px; } #projectbrief { font-family: Helvetica, arial, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; line-height: 22.4px; margin: 0px 0px 13px 0px; padding: 2px; } /* side bar and search */ #side-nav { padding: 10px 0px 20px 20px; border-top: 60px solid #2980b9; background-color: #343131; width: 250px !important; height: 100% !important; position: fixed; } #nav-tree { background-color: transparent; background-image: none; height: 100% !important; } #nav-tree .label { font-family: Helvetica, arial, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 25.5px; font-size: 15px; } #nav-tree { color: #b3b3b3; } #nav-tree .selected { background-image: none; } #nav-tree a { color: #b3b3b3; } #github { position: fixed; left: auto; right: auto; width: 250px; } #MSearchBox { margin: 20px; left: 40px; right: auto; position: fixed; width: 180px; } #MSearchField { width: 121px; } #MSearchResultsWindow { left: 45px !important; } #nav-sync { display: none; } .ui-resizable .ui-resizable-handle { width: 0px; } #nav-path { display: none; } /* external link icon */ div.contents a[href ^= "http"]:after { content: " " url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAVklEQVR4Xn3PgQkAMQhDUXfqTu7kTtkpd5RA8AInfArtQ2iRXFWT2QedAfttj2FsPIOE1eCOlEuoWWjgzYaB/IkeGOrxXhqB+uA9Bfcm0lAZuh+YIeAD+cAqSz4kCMUAAAAASUVORK5CYII=); } .githublogo { content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RERCMUIwOUY4NkNFMTFFM0FBNTJFRTMzNTJEMUJDNDYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RERCMUIwOUU4NkNFMTFFM0FBNTJFRTMzNTJEMUJDNDYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkU1MTc4QTJBOTlBMDExRTI5QTE1QkMxMDQ2QTg5MDREIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkU1MTc4QTJCOTlBMDExRTI5QTE1QkMxMDQ2QTg5MDREIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+jUqS1wAAApVJREFUeNq0l89rE1EQx3e3gVJoSPzZeNEWPKgHoa0HBak0iHiy/4C3WvDmoZ56qJ7txVsPQu8qlqqHIhRKJZceesmhioQEfxTEtsoSpdJg1u/ABJ7Pmc1m8zLwgWTmzcw3L+/te+tHUeQltONgCkyCi2AEDHLsJ6iBMlgHL8FeoqokoA2j4CloRMmtwTmj7erHBXPgCWhG6a3JNXKdCiDl1cidVbXZkJoXQRi5t5BrxwoY71FzU8S4JuAIqFkJ2+BFSlEh525b/hr3+k/AklDkNsf6wTT4yv46KIMNpsy+iMdMc47HNWxbsgVcUn7FmLAzzoFAWDsBx+wVP6bUpp5ewI+DOeUx0Wd9D8F70BTGNjkWtqnhmT1JQAHcUgZd8Lo3rQb1LAT8eJVUfgGvHQigGp+V2Z0iAUUl8QH47kAA1XioxIo+bRN8OG8F/oBjwv+Z1nJgX5jpdzQDw0LCjsPmrcW7I/iHScCAEDj03FtD8A0EyuChHgg4KTlJQF3wZ7WELppnBX+dBFSVpJsOBWi1qiRgSwnOgoyD5hmuJdkWCVhTgnTvW3AgYIFrSbZGh0UW/Io5Vp+DQoK7o80pztWMemZbgxeNwCNwDbw1fIfgGZjhU6xPaJgBV8BdsMw5cbZoHsenwYFxkZzl83xTSKTiviCAfCsJLysH3POfC8m8NegyGAGfLP/VmGmfSChgXroR0RSWjEFv2J/nG84cuKFMf4sTCZqXuJd4KaXFVjEG3+tw4eXbNK/YC9oXXs3O8NY8y99L4BXY5cvLY/Bb2VZ58EOJVcB18DHJq9lRsKr8inyKGVjlmh29mtHs3AHfuhCwy1vXT/Nu2GKQt+UHsGdctyX6eQyNvc+5sfX9Dl7Pe2J/BRgAl2CpwmrsHR0AAAAASUVORK5CYII=); }opentimelineio-0.18.1/src/deps/rapidjson/doc/performance.md0000664000175000017500000000236415110656147021525 0ustar meme# Performance There is a [native JSON benchmark collection] [1] which evaluates speed, memory usage and code size of various operations among 37 JSON libraries. [1]: https://github.com/miloyip/nativejson-benchmark The old performance article for RapidJSON 0.1 is provided [here](https://code.google.com/p/rapidjson/wiki/Performance). Additionally, you may refer to the following third-party benchmarks. ## Third-party benchmarks * [Basic benchmarks for miscellaneous C++ JSON parsers and generators](https://github.com/mloskot/json_benchmark) by Mateusz Loskot (Jun 2013) * [casablanca](https://casablanca.codeplex.com/) * [json_spirit](https://github.com/cierelabs/json_spirit) * [jsoncpp](http://jsoncpp.sourceforge.net/) * [libjson](http://sourceforge.net/projects/libjson/) * [rapidjson](https://github.com/Tencent/rapidjson/) * [QJsonDocument](http://qt-project.org/doc/qt-5.0/qtcore/qjsondocument.html) * [JSON Parser Benchmarking](http://chadaustin.me/2013/01/json-parser-benchmarking/) by Chad Austin (Jan 2013) * [sajson](https://github.com/chadaustin/sajson) * [rapidjson](https://github.com/Tencent/rapidjson/) * [vjson](https://code.google.com/p/vjson/) * [YAJL](http://lloyd.github.com/yajl/) * [Jansson](http://www.digip.org/jansson/) opentimelineio-0.18.1/src/deps/rapidjson/doc/stream.zh-cn.md0000664000175000017500000003376515110656147021546 0ustar meme# 流 在 RapidJSON 中,`rapidjson::Stream` 是用於读写 JSON 的概念(概念是指 C++ 的 concept)。在这里我们先介绍如何使用 RapidJSON 提供的各种流。然后再看看如何自行定义流。 [TOC] # 内存流 {#MemoryStreams} 内存流把 JSON 存储在内存之中。 ## StringStream(输入){#StringStream} `StringStream` 是最基本的输入流,它表示一个完整的、只读的、存储于内存的 JSON。它在 `rapidjson/rapidjson.h` 中定义。 ~~~~~~~~~~cpp #include "rapidjson/document.h" // 会包含 "rapidjson/rapidjson.h" using namespace rapidjson; // ... const char json[] = "[1, 2, 3, 4]"; StringStream s(json); Document d; d.ParseStream(s); ~~~~~~~~~~ 由于这是非常常用的用法,RapidJSON 提供 `Document::Parse(const char*)` 去做完全相同的事情: ~~~~~~~~~~cpp // ... const char json[] = "[1, 2, 3, 4]"; Document d; d.Parse(json); ~~~~~~~~~~ 需要注意,`StringStream` 是 `GenericStringStream >` 的 typedef,使用者可用其他编码类去代表流所使用的字符集。 ## StringBuffer(输出){#StringBuffer} `StringBuffer` 是一个简单的输出流。它分配一个内存缓冲区,供写入整个 JSON。可使用 `GetString()` 来获取该缓冲区。 ~~~~~~~~~~cpp #include "rapidjson/stringbuffer.h" #include StringBuffer buffer; Writer writer(buffer); d.Accept(writer); const char* output = buffer.GetString(); ~~~~~~~~~~ 当缓冲区满溢,它将自动增加容量。缺省容量是 256 个字符(UTF8 是 256 字节,UTF16 是 512 字节等)。使用者能自行提供分配器及初始容量。 ~~~~~~~~~~cpp StringBuffer buffer1(0, 1024); // 使用它的分配器,初始大小 = 1024 StringBuffer buffer2(allocator, 1024); ~~~~~~~~~~ 如无设置分配器,`StringBuffer` 会自行实例化一个内部分配器。 相似地,`StringBuffer` 是 `GenericStringBuffer >` 的 typedef。 # 文件流 {#FileStreams} 当要从文件解析一个 JSON,你可以把整个 JSON 读入内存并使用上述的 `StringStream`。 然而,若 JSON 很大,或是内存有限,你可以改用 `FileReadStream`。它只会从文件读取一部分至缓冲区,然后让那部分被解析。若缓冲区的字符都被读完,它会再从文件读取下一部分。 ## FileReadStream(输入) {#FileReadStream} `FileReadStream` 通过 `FILE` 指针读取文件。使用者需要提供一个缓冲区。 ~~~~~~~~~~cpp #include "rapidjson/filereadstream.h" #include using namespace rapidjson; FILE* fp = fopen("big.json", "rb"); // 非 Windows 平台使用 "r" char readBuffer[65536]; FileReadStream is(fp, readBuffer, sizeof(readBuffer)); Document d; d.ParseStream(is); fclose(fp); ~~~~~~~~~~ 与 `StringStreams` 不一样,`FileReadStream` 是一个字节流。它不处理编码。若文件并非 UTF-8 编码,可以把字节流用 `EncodedInputStream` 包装。我们很快会讨论这个问题。 除了读取文件,使用者也可以使用 `FileReadStream` 来读取 `stdin`。 ## FileWriteStream(输出){#FileWriteStream} `FileWriteStream` 是一个含缓冲功能的输出流。它的用法与 `FileReadStream` 非常相似。 ~~~~~~~~~~cpp #include "rapidjson/filewritestream.h" #include #include using namespace rapidjson; Document d; d.Parse(json); // ... FILE* fp = fopen("output.json", "wb"); // 非 Windows 平台使用 "w" char writeBuffer[65536]; FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer)); Writer writer(os); d.Accept(writer); fclose(fp); ~~~~~~~~~~ 它也可以把输出导向 `stdout`。 # iostream 包装类 {#iostreamWrapper} 基于用户的要求,RapidJSON 提供了正式的 `std::basic_istream` 和 `std::basic_ostream` 包装类。然而,请注意其性能会大大低于以上的其他流。 ## IStreamWrapper {#IStreamWrapper} `IStreamWrapper` 把任何继承自 `std::istream` 的类(如 `std::istringstream`、`std::stringstream`、`std::ifstream`、`std::fstream`)包装成 RapidJSON 的输入流。 ~~~cpp #include #include #include using namespace rapidjson; using namespace std; ifstream ifs("test.json"); IStreamWrapper isw(ifs); Document d; d.ParseStream(isw); ~~~ 对于继承自 `std::wistream` 的类,则使用 `WIStreamWrapper`。 ## OStreamWrapper {#OStreamWrapper} 相似地,`OStreamWrapper` 把任何继承自 `std::ostream` 的类(如 `std::ostringstream`、`std::stringstream`、`std::ofstream`、`std::fstream`)包装成 RapidJSON 的输出流。 ~~~cpp #include #include #include #include using namespace rapidjson; using namespace std; Document d; d.Parse(json); // ... ofstream ofs("output.json"); OStreamWrapper osw(ofs); Writer writer(osw); d.Accept(writer); ~~~ 对于继承自 `std::wistream` 的类,则使用 `WIStreamWrapper`。 # 编码流 {#EncodedStreams} 编码流(encoded streams)本身不存储 JSON,它们是通过包装字节流来提供基本的编码/解码功能。 如上所述,我们可以直接读入 UTF-8 字节流。然而,UTF-16 及 UTF-32 有字节序(endian)问题。要正确地处理字节序,需要在读取时把字节转换成字符(如对 UTF-16 使用 `wchar_t`),以及在写入时把字符转换为字节。 除此以外,我们也需要处理 [字节顺序标记(byte order mark, BOM)](http://en.wikipedia.org/wiki/Byte_order_mark)。当从一个字节流读取时,需要检测 BOM,或者仅仅是把存在的 BOM 消去。当把 JSON 写入字节流时,也可选择写入 BOM。 若一个流的编码在编译期已知,你可使用 `EncodedInputStream` 及 `EncodedOutputStream`。若一个流可能存储 UTF-8、UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE 的 JSON,并且编码只能在运行时得知,你便可以使用 `AutoUTFInputStream` 及 `AutoUTFOutputStream`。这些流定义在 `rapidjson/encodedstream.h`。 注意到,这些编码流可以施于文件以外的流。例如,你可以用编码流包装内存中的文件或自定义的字节流。 ## EncodedInputStream {#EncodedInputStream} `EncodedInputStream` 含两个模板参数。第一个是 `Encoding` 类型,例如定义于 `rapidjson/encodings.h` 的 `UTF8`、`UTF16LE`。第二个参数是被包装的流的类型。 ~~~~~~~~~~cpp #include "rapidjson/document.h" #include "rapidjson/filereadstream.h" // FileReadStream #include "rapidjson/encodedstream.h" // EncodedInputStream #include using namespace rapidjson; FILE* fp = fopen("utf16le.json", "rb"); // 非 Windows 平台使用 "r" char readBuffer[256]; FileReadStream bis(fp, readBuffer, sizeof(readBuffer)); EncodedInputStream, FileReadStream> eis(bis); // 用 eis 包装 bis Document d; // Document 为 GenericDocument > d.ParseStream<0, UTF16LE<> >(eis); // 把 UTF-16LE 文件解析至内存中的 UTF-8 fclose(fp); ~~~~~~~~~~ ## EncodedOutputStream {#EncodedOutputStream} `EncodedOutputStream` 也是相似的,但它的构造函数有一个 `bool putBOM` 参数,用于控制是否在输出字节流写入 BOM。 ~~~~~~~~~~cpp #include "rapidjson/filewritestream.h" // FileWriteStream #include "rapidjson/encodedstream.h" // EncodedOutputStream #include #include Document d; // Document 为 GenericDocument > // ... FILE* fp = fopen("output_utf32le.json", "wb"); // 非 Windows 平台使用 "w" char writeBuffer[256]; FileWriteStream bos(fp, writeBuffer, sizeof(writeBuffer)); typedef EncodedOutputStream, FileWriteStream> OutputStream; OutputStream eos(bos, true); // 写入 BOM Writer, UTF32LE<>> writer(eos); d.Accept(writer); // 这里从内存的 UTF-8 生成 UTF32-LE 文件 fclose(fp); ~~~~~~~~~~ ## AutoUTFInputStream {#AutoUTFInputStream} 有时候,应用软件可能需要㲃理所有可支持的 JSON 编码。`AutoUTFInputStream` 会先使用 BOM 来检测编码。若 BOM 不存在,它便会使用合法 JSON 的特性来检测。若两种方法都失败,它就会倒退至构造函数提供的 UTF 类型。 由于字符(编码单元/code unit)可能是 8 位、16 位或 32 位,`AutoUTFInputStream` 需要一个能至少储存 32 位的字符类型。我们可以使用 `unsigned` 作为模板参数: ~~~~~~~~~~cpp #include "rapidjson/document.h" #include "rapidjson/filereadstream.h" // FileReadStream #include "rapidjson/encodedstream.h" // AutoUTFInputStream #include using namespace rapidjson; FILE* fp = fopen("any.json", "rb"); // 非 Windows 平台使用 "r" char readBuffer[256]; FileReadStream bis(fp, readBuffer, sizeof(readBuffer)); AutoUTFInputStream eis(bis); // 用 eis 包装 bis Document d; // Document 为 GenericDocument > d.ParseStream<0, AutoUTF >(eis); // 把任何 UTF 编码的文件解析至内存中的 UTF-8 fclose(fp); ~~~~~~~~~~ 当要指定流的编码,可使用上面例子中 `ParseStream()` 的参数 `AutoUTF`。 你可以使用 `UTFType GetType()` 去获取 UTF 类型,并且用 `HasBOM()` 检测输入流是否含有 BOM。 ## AutoUTFOutputStream {#AutoUTFOutputStream} 相似地,要在运行时选择输出的编码,我们可使用 `AutoUTFOutputStream`。这个类本身并非「自动」。你需要在运行时指定 UTF 类型,以及是否写入 BOM。 ~~~~~~~~~~cpp using namespace rapidjson; void WriteJSONFile(FILE* fp, UTFType type, bool putBOM, const Document& d) { char writeBuffer[256]; FileWriteStream bos(fp, writeBuffer, sizeof(writeBuffer)); typedef AutoUTFOutputStream OutputStream; OutputStream eos(bos, type, putBOM); Writer, AutoUTF<> > writer; d.Accept(writer); } ~~~~~~~~~~ `AutoUTFInputStream`/`AutoUTFOutputStream` 是比 `EncodedInputStream`/`EncodedOutputStream` 方便。但前者会产生一点运行期额外开销。 # 自定义流 {#CustomStream} 除了内存/文件流,使用者可创建自行定义适配 RapidJSON API 的流类。例如,你可以创建网络流、从压缩文件读取的流等等。 RapidJSON 利用模板结合不同的类型。只要一个类包含所有所需的接口,就可以作为一个流。流的接合定义在 `rapidjson/rapidjson.h` 的注释里: ~~~~~~~~~~cpp concept Stream { typename Ch; //!< 流的字符类型 //! 从流读取当前字符,不移动读取指针(read cursor) Ch Peek() const; //! 从流读取当前字符,移动读取指针至下一字符。 Ch Take(); //! 获取读取指针。 //! \return 从开始以来所读过的字符数量。 size_t Tell(); //! 从当前读取指针开始写入操作。 //! \return 返回开始写入的指针。 Ch* PutBegin(); //! 写入一个字符。 void Put(Ch c); //! 清空缓冲区。 void Flush(); //! 完成写作操作。 //! \param begin PutBegin() 返回的开始写入指针。 //! \return 已写入的字符数量。 size_t PutEnd(Ch* begin); } ~~~~~~~~~~ 输入流必须实现 `Peek()`、`Take()` 及 `Tell()`。 输出流必须实现 `Put()` 及 `Flush()`。 `PutBegin()` 及 `PutEnd()` 是特殊的接口,仅用于原位(*in situ*)解析。一般的流不需实现它们。然而,即使接口不需用于某些流,仍然需要提供空实现,否则会产生编译错误。 ## 例子:istream 的包装类 {#ExampleIStreamWrapper} 以下的简单例子是 `std::istream` 的包装类,它只需现 3 个函数。 ~~~~~~~~~~cpp class MyIStreamWrapper { public: typedef char Ch; MyIStreamWrapper(std::istream& is) : is_(is) { } Ch Peek() const { // 1 int c = is_.peek(); return c == std::char_traits::eof() ? '\0' : (Ch)c; } Ch Take() { // 2 int c = is_.get(); return c == std::char_traits::eof() ? '\0' : (Ch)c; } size_t Tell() const { return (size_t)is_.tellg(); } // 3 Ch* PutBegin() { assert(false); return 0; } void Put(Ch) { assert(false); } void Flush() { assert(false); } size_t PutEnd(Ch*) { assert(false); return 0; } private: MyIStreamWrapper(const MyIStreamWrapper&); MyIStreamWrapper& operator=(const MyIStreamWrapper&); std::istream& is_; }; ~~~~~~~~~~ 使用者能用它来包装 `std::stringstream`、`std::ifstream` 的实例。 ~~~~~~~~~~cpp const char* json = "[1,2,3,4]"; std::stringstream ss(json); MyIStreamWrapper is(ss); Document d; d.ParseStream(is); ~~~~~~~~~~ 但要注意,由于标准库的内部开销问,此实现的性能可能不如 RapidJSON 的内存/文件流。 ## 例子:ostream 的包装类 {#ExampleOStreamWrapper} 以下的例子是 `std::istream` 的包装类,它只需实现 2 个函数。 ~~~~~~~~~~cpp class MyOStreamWrapper { public: typedef char Ch; OStreamWrapper(std::ostream& os) : os_(os) { } Ch Peek() const { assert(false); return '\0'; } Ch Take() { assert(false); return '\0'; } size_t Tell() const { } Ch* PutBegin() { assert(false); return 0; } void Put(Ch c) { os_.put(c); } // 1 void Flush() { os_.flush(); } // 2 size_t PutEnd(Ch*) { assert(false); return 0; } private: MyOStreamWrapper(const MyOStreamWrapper&); MyOStreamWrapper& operator=(const MyOStreamWrapper&); std::ostream& os_; }; ~~~~~~~~~~ 使用者能用它来包装 `std::stringstream`、`std::ofstream` 的实例。 ~~~~~~~~~~cpp Document d; // ... std::stringstream ss; MyOStreamWrapper os(ss); Writer writer(os); d.Accept(writer); ~~~~~~~~~~ 但要注意,由于标准库的内部开销问,此实现的性能可能不如 RapidJSON 的内存/文件流。 # 总结 {#Summary} 本节描述了 RapidJSON 提供的各种流的类。内存流很简单。若 JSON 存储在文件中,文件流可减少 JSON 解析及生成所需的内存量。编码流在字节流和字符流之间作转换。最后,使用者可使用一个简单接口创建自定义的流。 opentimelineio-0.18.1/src/deps/rapidjson/doc/sax.zh-cn.md0000664000175000017500000004677715110656147021055 0ustar meme# SAX "SAX" 此术语源于 [Simple API for XML](http://en.wikipedia.org/wiki/Simple_API_for_XML)。我们借了此术语去套用在 JSON 的解析及生成。 在 RapidJSON 中,`Reader`(`GenericReader<...>` 的 typedef)是 JSON 的 SAX 风格解析器,而 `Writer`(`GenericWriter<...>` 的 typedef)则是 JSON 的 SAX 风格生成器。 [TOC] # Reader {#Reader} `Reader` 从输入流解析一个 JSON。当它从流中读取字符时,它会基于 JSON 的语法去分析字符,并向处理器发送事件。 例如,以下是一个 JSON。 ~~~~~~~~~~js { "hello": "world", "t": true , "f": false, "n": null, "i": 123, "pi": 3.1416, "a": [1, 2, 3, 4] } ~~~~~~~~~~ 当一个 `Reader` 解析此 JSON 时,它会顺序地向处理器发送以下的事件: ~~~~~~~~~~ StartObject() Key("hello", 5, true) String("world", 5, true) Key("t", 1, true) Bool(true) Key("f", 1, true) Bool(false) Key("n", 1, true) Null() Key("i") Uint(123) Key("pi") Double(3.1416) Key("a") StartArray() Uint(1) Uint(2) Uint(3) Uint(4) EndArray(4) EndObject(7) ~~~~~~~~~~ 除了一些事件参数需要再作解释,这些事件可以轻松地与 JSON 对上。我们可以看看 `simplereader` 例子怎样产生和以上完全相同的结果: ~~~~~~~~~~cpp #include "rapidjson/reader.h" #include using namespace rapidjson; using namespace std; struct MyHandler : public BaseReaderHandler, MyHandler> { bool Null() { cout << "Null()" << endl; return true; } bool Bool(bool b) { cout << "Bool(" << boolalpha << b << ")" << endl; return true; } bool Int(int i) { cout << "Int(" << i << ")" << endl; return true; } bool Uint(unsigned u) { cout << "Uint(" << u << ")" << endl; return true; } bool Int64(int64_t i) { cout << "Int64(" << i << ")" << endl; return true; } bool Uint64(uint64_t u) { cout << "Uint64(" << u << ")" << endl; return true; } bool Double(double d) { cout << "Double(" << d << ")" << endl; return true; } bool String(const char* str, SizeType length, bool copy) { cout << "String(" << str << ", " << length << ", " << boolalpha << copy << ")" << endl; return true; } bool StartObject() { cout << "StartObject()" << endl; return true; } bool Key(const char* str, SizeType length, bool copy) { cout << "Key(" << str << ", " << length << ", " << boolalpha << copy << ")" << endl; return true; } bool EndObject(SizeType memberCount) { cout << "EndObject(" << memberCount << ")" << endl; return true; } bool StartArray() { cout << "StartArray()" << endl; return true; } bool EndArray(SizeType elementCount) { cout << "EndArray(" << elementCount << ")" << endl; return true; } }; void main() { const char json[] = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "; MyHandler handler; Reader reader; StringStream ss(json); reader.Parse(ss, handler); } ~~~~~~~~~~ 注意 RapidJSON 使用模板去静态挷定 `Reader` 类型及处理器的类型,而不是使用含虚函数的类。这个范式可以通过把函数内联而改善性能。 ## 处理器 {#Handler} 如前例所示,使用者需要实现一个处理器(handler),用于处理来自 `Reader` 的事件(函数调用)。处理器必须包含以下的成员函数。 ~~~~~~~~~~cpp class Handler { bool Null(); bool Bool(bool b); bool Int(int i); bool Uint(unsigned i); bool Int64(int64_t i); bool Uint64(uint64_t i); bool Double(double d); bool RawNumber(const Ch* str, SizeType length, bool copy); bool String(const Ch* str, SizeType length, bool copy); bool StartObject(); bool Key(const Ch* str, SizeType length, bool copy); bool EndObject(SizeType memberCount); bool StartArray(); bool EndArray(SizeType elementCount); }; ~~~~~~~~~~ 当 `Reader` 遇到 JSON null 值时会调用 `Null()`。 当 `Reader` 遇到 JSON true 或 false 值时会调用 `Bool(bool)`。 当 `Reader` 遇到 JSON number,它会选择一个合适的 C++ 类型映射,然后调用 `Int(int)`、`Uint(unsigned)`、`Int64(int64_t)`、`Uint64(uint64_t)` 及 `Double(double)` 的 * 其中之一个 *。 若开启了 `kParseNumbersAsStrings` 选项,`Reader` 便会改为调用 `RawNumber()`。 当 `Reader` 遇到 JSON string,它会调用 `String(const char* str, SizeType length, bool copy)`。第一个参数是字符串的指针。第二个参数是字符串的长度(不包含空终止符号)。注意 RapidJSON 支持字串中含有空字符 `\0`。若出现这种情况,便会有 `strlen(str) < length`。最后的 `copy` 参数表示处理器是否需要复制该字符串。在正常解析时,`copy = true`。仅当使用原位解析时,`copy = false`。此外,还要注意字符的类型与目标编码相关,我们稍后会再谈这一点。 当 `Reader` 遇到 JSON object 的开始之时,它会调用 `StartObject()`。JSON 的 object 是一个键值对(成员)的集合。若 object 包含成员,它会先为成员的名字调用 `Key()`,然后再按值的类型调用函数。它不断调用这些键值对,直至最终调用 `EndObject(SizeType memberCount)`。注意 `memberCount` 参数对处理器来说只是协助性质,使用者可能不需要此参数。 JSON array 与 object 相似,但更简单。在 array 开始时,`Reader` 会调用 `BeginArary()`。若 array 含有元素,它会按元素的类型来读用函数。相似地,最后它会调用 `EndArray(SizeType elementCount)`,其中 `elementCount` 参数对处理器来说只是协助性质。 每个处理器函数都返回一个 `bool`。正常它们应返回 `true`。若处理器遇到错误,它可以返回 `false` 去通知事件发送方停止继续处理。 例如,当我们用 `Reader` 解析一个 JSON 时,处理器检测到该 JSON 并不符合所需的 schema,那么处理器可以返回 `false`,令 `Reader` 停止之后的解析工作。而 `Reader` 会进入一个错误状态,并以 `kParseErrorTermination` 错误码标识。 ## GenericReader {#GenericReader} 前面提及,`Reader` 是 `GenericReader` 模板类的 typedef: ~~~~~~~~~~cpp namespace rapidjson { template > class GenericReader { // ... }; typedef GenericReader, UTF8<> > Reader; } // namespace rapidjson ~~~~~~~~~~ `Reader` 使用 UTF-8 作为来源及目标编码。来源编码是指 JSON 流的编码。目标编码是指 `String()` 的 `str` 参数所用的编码。例如,要解析一个 UTF-8 流并输出至 UTF-16 string 事件,你需要这么定义一个 reader: ~~~~~~~~~~cpp GenericReader, UTF16<> > reader; ~~~~~~~~~~ 注意到 `UTF16` 的缺省类型是 `wchar_t`。因此这个 `reader` 需要调用处理器的 `String(const wchar_t*, SizeType, bool)`。 第三个模板参数 `Allocator` 是内部数据结构(实际上是一个堆栈)的分配器类型。 ## 解析 {#SaxParsing} `Reader` 的唯一功能就是解析 JSON。 ~~~~~~~~~~cpp template bool Parse(InputStream& is, Handler& handler); // 使用 parseFlags = kDefaultParseFlags template bool Parse(InputStream& is, Handler& handler); ~~~~~~~~~~ 若在解析中出现错误,它会返回 `false`。使用者可调用 `bool HasParseEror()`, `ParseErrorCode GetParseErrorCode()` 及 `size_t GetErrorOffset()` 获取错误状态。实际上 `Document` 使用这些 `Reader` 函数去获取解析错误。请参考 [DOM](doc/dom.zh-cn.md) 去了解有关解析错误的细节。 # Writer {#Writer} `Reader` 把 JSON 转换(解析)成为事件。`Writer` 做完全相反的事情。它把事件转换成 JSON。 `Writer` 是非常容易使用的。若你的应用程序只需把一些数据转换成 JSON,可能直接使用 `Writer`,会比建立一个 `Document` 然后用 `Writer` 把它转换成 JSON 更加方便。 在 `simplewriter` 例子里,我们做 `simplereader` 完全相反的事情。 ~~~~~~~~~~cpp #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" #include using namespace rapidjson; using namespace std; void main() { StringBuffer s; Writer writer(s); writer.StartObject(); writer.Key("hello"); writer.String("world"); writer.Key("t"); writer.Bool(true); writer.Key("f"); writer.Bool(false); writer.Key("n"); writer.Null(); writer.Key("i"); writer.Uint(123); writer.Key("pi"); writer.Double(3.1416); writer.Key("a"); writer.StartArray(); for (unsigned i = 0; i < 4; i++) writer.Uint(i); writer.EndArray(); writer.EndObject(); cout << s.GetString() << endl; } ~~~~~~~~~~ ~~~~~~~~~~ {"hello":"world","t":true,"f":false,"n":null,"i":123,"pi":3.1416,"a":[0,1,2,3]} ~~~~~~~~~~ `String()` 及 `Key()` 各有两个重载。一个是如处理器 concept 般,有 3 个参数。它能处理含空字符的字符串。另一个是如上中使用的较简单版本。 注意到,例子代码中的 `EndArray()` 及 `EndObject()` 并没有参数。可以传递一个 `SizeType` 的参数,但它会被 `Writer` 忽略。 你可能会怀疑,为什么不使用 `sprintf()` 或 `std::stringstream` 去建立一个 JSON? 这有几个原因: 1. `Writer` 必然会输出一个结构良好(well-formed)的 JSON。若然有错误的事件次序(如 `Int()` 紧随 `StartObject()` 出现),它会在调试模式中产生断言失败。 2. `Writer::String()` 可处理字符串转义(如把码点 `U+000A` 转换成 `\n`)及进行 Unicode 转码。 3. `Writer` 一致地处理 number 的输出。 4. `Writer` 实现了事件处理器 concept。可用于处理来自 `Reader`、`Document` 或其他事件发生器。 5. `Writer` 可对不同平台进行优化。 无论如何,使用 `Writer` API 去生成 JSON 甚至乎比这些临时方法更简单。 ## 模板 {#WriterTemplate} `Writer` 与 `Reader` 有少许设计区别。`Writer` 是一个模板类,而不是一个 typedef。 并没有 `GenericWriter`。以下是 `Writer` 的声明。 ~~~~~~~~~~cpp namespace rapidjson { template, typename TargetEncoding = UTF8<>, typename Allocator = CrtAllocator<> > class Writer { public: Writer(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) // ... }; } // namespace rapidjson ~~~~~~~~~~ `OutputStream` 模板参数是输出流的类型。它的类型不可以被自动推断,必须由使用者提供。 `SourceEncoding` 模板参数指定了 `String(const Ch*, ...)` 的编码。 `TargetEncoding` 模板参数指定输出流的编码。 `Allocator` 是分配器的类型,用于分配内部数据结构(一个堆栈)。 `writeFlags` 是以下位标志的组合: 写入位标志 | 意义 ------------------------------|----------------------------------- `kWriteNoFlags` | 没有任何标志。 `kWriteDefaultFlags` | 缺省的解析选项。它等于 `RAPIDJSON_WRITE_DEFAULT_FLAGS` 宏,此宏定义为 `kWriteNoFlags`。 `kWriteValidateEncodingFlag` | 校验 JSON 字符串的编码。 `kWriteNanAndInfFlag` | 容许写入 `Infinity`, `-Infinity` 及 `NaN`。 此外,`Writer` 的构造函数有一 `levelDepth` 参数。存储每层阶信息的初始内存分配量受此参数影响。 ## PrettyWriter {#PrettyWriter} `Writer` 所输出的是没有空格字符的最紧凑 JSON,适合网络传输或储存,但不适合人类阅读。 因此,RapidJSON 提供了一个 `PrettyWriter`,它在输出中加入缩进及换行。 `PrettyWriter` 的用法与 `Writer` 几乎一样,不同之处是 `PrettyWriter` 提供了一个 `SetIndent(Ch indentChar, unsigned indentCharCount)` 函数。缺省的缩进是 4 个空格。 ## 完整性及重置 {#CompletenessReset} 一个 `Writer` 只可输出单个 JSON,其根节点可以是任何 JSON 类型。当处理完单个根节点事件(如 `String()`),或匹配的最后 `EndObject()` 或 `EndArray()` 事件,输出的 JSON 是结构完整(well-formed)及完整的。使用者可调用 `Writer::IsComplete()` 去检测完整性。 当 JSON 完整时,`Writer` 不能再接受新的事件。不然其输出便会是不合法的(例如有超过一个根节点)。为了重新利用 `Writer` 对象,使用者可调用 `Writer::Reset(OutputStream& os)` 去重置其所有内部状态及设置新的输出流。 # 技巧 {#SaxTechniques} ## 解析 JSON 至自定义结构 {#CustomDataStructure} `Document` 的解析功能完全依靠 `Reader`。实际上 `Document` 是一个处理器,在解析 JSON 时接收事件去建立一个 DOM。 使用者可以直接使用 `Reader` 去建立其他数据结构。这消除了建立 DOM 的步骤,从而减少了内存开销并改善性能。 在以下的 `messagereader` 例子中,`ParseMessages()` 解析一个 JSON,该 JSON 应该是一个含键值对的 object。 ~~~~~~~~~~cpp #include "rapidjson/reader.h" #include "rapidjson/error/en.h" #include #include #include using namespace std; using namespace rapidjson; typedef map MessageMap; struct MessageHandler : public BaseReaderHandler, MessageHandler> { MessageHandler() : state_(kExpectObjectStart) { } bool StartObject() { switch (state_) { case kExpectObjectStart: state_ = kExpectNameOrObjectEnd; return true; default: return false; } } bool String(const char* str, SizeType length, bool) { switch (state_) { case kExpectNameOrObjectEnd: name_ = string(str, length); state_ = kExpectValue; return true; case kExpectValue: messages_.insert(MessageMap::value_type(name_, string(str, length))); state_ = kExpectNameOrObjectEnd; return true; default: return false; } } bool EndObject(SizeType) { return state_ == kExpectNameOrObjectEnd; } bool Default() { return false; } // All other events are invalid. MessageMap messages_; enum State { kExpectObjectStart, kExpectNameOrObjectEnd, kExpectValue, }state_; std::string name_; }; void ParseMessages(const char* json, MessageMap& messages) { Reader reader; MessageHandler handler; StringStream ss(json); if (reader.Parse(ss, handler)) messages.swap(handler.messages_); // Only change it if success. else { ParseErrorCode e = reader.GetParseErrorCode(); size_t o = reader.GetErrorOffset(); cout << "Error: " << GetParseError_En(e) << endl;; cout << " at offset " << o << " near '" << string(json).substr(o, 10) << "...'" << endl; } } int main() { MessageMap messages; const char* json1 = "{ \"greeting\" : \"Hello!\", \"farewell\" : \"bye-bye!\" }"; cout << json1 << endl; ParseMessages(json1, messages); for (MessageMap::const_iterator itr = messages.begin(); itr != messages.end(); ++itr) cout << itr->first << ": " << itr->second << endl; cout << endl << "Parse a JSON with invalid schema." << endl; const char* json2 = "{ \"greeting\" : \"Hello!\", \"farewell\" : \"bye-bye!\", \"foo\" : {} }"; cout << json2 << endl; ParseMessages(json2, messages); return 0; } ~~~~~~~~~~ ~~~~~~~~~~ { "greeting" : "Hello!", "farewell" : "bye-bye!" } farewell: bye-bye! greeting: Hello! Parse a JSON with invalid schema. { "greeting" : "Hello!", "farewell" : "bye-bye!", "foo" : {} } Error: Terminate parsing due to Handler error. at offset 59 near '} }...' ~~~~~~~~~~ 第一个 JSON(`json1`)被成功地解析至 `MessageMap`。由于 `MessageMap` 是一个 `std::map`,打印次序按键值排序。此次序与 JSON 中的次序不同。 在第二个 JSON(`json2`)中,`foo` 的值是一个空 object。由于它是一个 object,`MessageHandler::StartObject()` 会被调用。然而,在 `state_ = kExpectValue` 的情况下,该函数会返回 `false`,并导致解析过程终止。错误代码是 `kParseErrorTermination`。 ## 过滤 JSON {#Filtering} 如前面提及过,`Writer` 可处理 `Reader` 发出的事件。`example/condense/condense.cpp` 例子简单地设置 `Writer` 作为一个 `Reader` 的处理器,因此它能移除 JSON 中的所有空白字符。`example/pretty/pretty.cpp` 例子使用同样的关系,只是以 `PrettyWriter` 取代 `Writer`。因此 `pretty` 能够重新格式化 JSON,加入缩进及换行。 实际上,我们可以使用 SAX 风格 API 去加入(多个)中间层去过滤 JSON 的内容。例如 `capitalize` 例子可以把所有 JSON string 改为大写。 ~~~~~~~~~~cpp #include "rapidjson/reader.h" #include "rapidjson/writer.h" #include "rapidjson/filereadstream.h" #include "rapidjson/filewritestream.h" #include "rapidjson/error/en.h" #include #include using namespace rapidjson; template struct CapitalizeFilter { CapitalizeFilter(OutputHandler& out) : out_(out), buffer_() { } bool Null() { return out_.Null(); } bool Bool(bool b) { return out_.Bool(b); } bool Int(int i) { return out_.Int(i); } bool Uint(unsigned u) { return out_.Uint(u); } bool Int64(int64_t i) { return out_.Int64(i); } bool Uint64(uint64_t u) { return out_.Uint64(u); } bool Double(double d) { return out_.Double(d); } bool RawNumber(const char* str, SizeType length, bool copy) { return out_.RawNumber(str, length, copy); } bool String(const char* str, SizeType length, bool) { buffer_.clear(); for (SizeType i = 0; i < length; i++) buffer_.push_back(std::toupper(str[i])); return out_.String(&buffer_.front(), length, true); // true = output handler need to copy the string } bool StartObject() { return out_.StartObject(); } bool Key(const char* str, SizeType length, bool copy) { return String(str, length, copy); } bool EndObject(SizeType memberCount) { return out_.EndObject(memberCount); } bool StartArray() { return out_.StartArray(); } bool EndArray(SizeType elementCount) { return out_.EndArray(elementCount); } OutputHandler& out_; std::vector buffer_; }; int main(int, char*[]) { // Prepare JSON reader and input stream. Reader reader; char readBuffer[65536]; FileReadStream is(stdin, readBuffer, sizeof(readBuffer)); // Prepare JSON writer and output stream. char writeBuffer[65536]; FileWriteStream os(stdout, writeBuffer, sizeof(writeBuffer)); Writer writer(os); // JSON reader parse from the input stream and let writer generate the output. CapitalizeFilter > filter(writer); if (!reader.Parse(is, filter)) { fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), GetParseError_En(reader.GetParseErrorCode())); return 1; } return 0; } ~~~~~~~~~~ 注意到,不可简单地把 JSON 当作字符串去改为大写。例如: ~~~~~~~~~~ ["Hello\nWorld"] ~~~~~~~~~~ 简单地把整个 JSON 转为大写的话会产生错误的转义符: ~~~~~~~~~~ ["HELLO\NWORLD"] ~~~~~~~~~~ 而 `capitalize` 就会产生正确的结果: ~~~~~~~~~~ ["HELLO\nWORLD"] ~~~~~~~~~~ 我们还可以开发更复杂的过滤器。然而,由于 SAX 风格 API 在某一时间点只能提供单一事件的信息,使用者需要自行记录一些上下文信息(例如从根节点起的路径、储存其他相关值)。对于处理某些情况,用 DOM 会比 SAX 更容易实现。 opentimelineio-0.18.1/src/deps/rapidjson/doc/encoding.md0000664000175000017500000001506415110656147021013 0ustar meme# Encoding According to [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf), > (in Introduction) JSON text is a sequence of Unicode code points. The earlier [RFC4627](http://www.ietf.org/rfc/rfc4627.txt) stated that, > (in §3) JSON text SHALL be encoded in Unicode. The default encoding is UTF-8. > (in §6) JSON may be represented using UTF-8, UTF-16, or UTF-32. When JSON is written in UTF-8, JSON is 8bit compatible. When JSON is written in UTF-16 or UTF-32, the binary content-transfer-encoding must be used. RapidJSON supports various encodings. It can also validate the encodings of JSON, and transcoding JSON among encodings. All these features are implemented internally, without the need for external libraries (e.g. [ICU](http://site.icu-project.org/)). [TOC] # Unicode {#Unicode} From [Unicode's official website](http://www.unicode.org/standard/WhatIsUnicode.html): > Unicode provides a unique number for every character, > no matter what the platform, > no matter what the program, > no matter what the language. Those unique numbers are called code points, which is in the range `0x0` to `0x10FFFF`. ## Unicode Transformation Format {#UTF} There are various encodings for storing Unicode code points. These are called Unicode Transformation Format (UTF). RapidJSON supports the most commonly used UTFs, including * UTF-8: 8-bit variable-width encoding. It maps a code point to 1–4 bytes. * UTF-16: 16-bit variable-width encoding. It maps a code point to 1–2 16-bit code units (i.e., 2–4 bytes). * UTF-32: 32-bit fixed-width encoding. It directly maps a code point to a single 32-bit code unit (i.e. 4 bytes). For UTF-16 and UTF-32, the byte order (endianness) does matter. Within computer memory, they are often stored in the computer's endianness. However, when it is stored in file or transferred over network, we need to state the byte order of the byte sequence, either little-endian (LE) or big-endian (BE). RapidJSON provide these encodings via the structs in `rapidjson/encodings.h`: ~~~~~~~~~~cpp namespace rapidjson { template struct UTF8; template struct UTF16; template struct UTF16LE; template struct UTF16BE; template struct UTF32; template struct UTF32LE; template struct UTF32BE; } // namespace rapidjson ~~~~~~~~~~ For processing text in memory, we normally use `UTF8`, `UTF16` or `UTF32`. For processing text via I/O, we may use `UTF8`, `UTF16LE`, `UTF16BE`, `UTF32LE` or `UTF32BE`. When using the DOM-style API, the `Encoding` template parameter in `GenericValue` and `GenericDocument` indicates the encoding to be used to represent JSON string in memory. So normally we will use `UTF8`, `UTF16` or `UTF32` for this template parameter. The choice depends on operating systems and other libraries that the application is using. For example, Windows API represents Unicode characters in UTF-16, while most Linux distributions and applications prefer UTF-8. Example of UTF-16 DOM declaration: ~~~~~~~~~~cpp typedef GenericDocument > WDocument; typedef GenericValue > WValue; ~~~~~~~~~~ For a detail example, please check the example in [DOM's Encoding](doc/stream.md) section. ## Character Type {#CharacterType} As shown in the declaration, each encoding has a `CharType` template parameter. Actually, it may be a little bit confusing, but each `CharType` stores a code unit, not a character (code point). As mentioned in previous section, a code point may be encoded to 1–4 code units for UTF-8. For `UTF16(LE|BE)`, `UTF32(LE|BE)`, the `CharType` must be integer type of at least 2 and 4 bytes respectively. Note that C++11 introduces `char16_t` and `char32_t`, which can be used for `UTF16` and `UTF32` respectively. ## AutoUTF {#AutoUTF} Previous encodings are statically bound in compile-time. In other words, user must know exactly which encodings will be used in the memory or streams. However, sometimes we may need to read/write files of different encodings. The encoding needed to be decided in runtime. `AutoUTF` is an encoding designed for this purpose. It chooses which encoding to be used according to the input or output stream. Currently, it should be used with `EncodedInputStream` and `EncodedOutputStream`. ## ASCII {#ASCII} Although the JSON standards did not mention about [ASCII](http://en.wikipedia.org/wiki/ASCII), sometimes we would like to write 7-bit ASCII JSON for applications that cannot handle UTF-8. Since any JSON can represent unicode characters in escaped sequence `\uXXXX`, JSON can always be encoded in ASCII. Here is an example for writing a UTF-8 DOM into ASCII: ~~~~~~~~~~cpp using namespace rapidjson; Document d; // UTF8<> // ... StringBuffer buffer; Writer > writer(buffer); d.Accept(writer); std::cout << buffer.GetString(); ~~~~~~~~~~ ASCII can be used in input stream. If the input stream contains bytes with values above 127, it will cause `kParseErrorStringInvalidEncoding` error. ASCII *cannot* be used in memory (encoding of `Document` or target encoding of `Reader`), as it cannot represent Unicode code points. # Validation & Transcoding {#ValidationTranscoding} When RapidJSON parses a JSON, it can validate the input JSON, whether it is a valid sequence of a specified encoding. This option can be turned on by adding `kParseValidateEncodingFlag` in `parseFlags` template parameter. If the input encoding and output encoding is different, `Reader` and `Writer` will automatically transcode (convert) the text. In this case, `kParseValidateEncodingFlag` is not necessary, as it must decode the input sequence. And if the sequence was unable to be decoded, it must be invalid. ## Transcoder {#Transcoder} Although the encoding functions in RapidJSON are designed for JSON parsing/generation, user may abuse them for transcoding of non-JSON strings. Here is an example for transcoding a string from UTF-8 to UTF-16: ~~~~~~~~~~cpp #include "rapidjson/encodings.h" using namespace rapidjson; const char* s = "..."; // UTF-8 string StringStream source(s); GenericStringBuffer > target; bool hasError = false; while (source.Peek() != '\0') if (!Transcoder, UTF16<> >::Transcode(source, target)) { hasError = true; break; } if (!hasError) { const wchar_t* t = target.GetString(); // ... } ~~~~~~~~~~ You may also use `AutoUTF` and the associated streams for setting source/target encoding in runtime. opentimelineio-0.18.1/src/deps/rapidjson/doc/dom.md0000664000175000017500000003615015110656147020003 0ustar meme# DOM Document Object Model(DOM) is an in-memory representation of JSON for query and manipulation. The basic usage of DOM is described in [Tutorial](doc/tutorial.md). This section will describe some details and more advanced usages. [TOC] # Template {#Template} In the tutorial, `Value` and `Document` was used. Similarly to `std::string`, these are actually `typedef` of template classes: ~~~~~~~~~~cpp namespace rapidjson { template > class GenericValue { // ... }; template > class GenericDocument : public GenericValue { // ... }; typedef GenericValue > Value; typedef GenericDocument > Document; } // namespace rapidjson ~~~~~~~~~~ User can customize these template parameters. ## Encoding {#Encoding} The `Encoding` parameter specifies the encoding of JSON String value in memory. Possible options are `UTF8`, `UTF16`, `UTF32`. Note that, these 3 types are also template class. `UTF8<>` is `UTF8`, which means using char to store the characters. You may refer to [Encoding](doc/encoding.md) for details. Suppose a Windows application would query localization strings stored in JSON files. Unicode-enabled functions in Windows use UTF-16 (wide character) encoding. No matter what encoding was used in JSON files, we can store the strings in UTF-16 in memory. ~~~~~~~~~~cpp using namespace rapidjson; typedef GenericDocument > WDocument; typedef GenericValue > WValue; FILE* fp = fopen("localization.json", "rb"); // non-Windows use "r" char readBuffer[256]; FileReadStream bis(fp, readBuffer, sizeof(readBuffer)); AutoUTFInputStream eis(bis); // wraps bis into eis WDocument d; d.ParseStream<0, AutoUTF >(eis); const WValue locale(L"ja"); // Japanese MessageBoxW(hWnd, d[locale].GetString(), L"Test", MB_OK); ~~~~~~~~~~ ## Allocator {#Allocator} The `Allocator` defines which allocator class is used when allocating/deallocating memory for `Document`/`Value`. `Document` owns, or references to an `Allocator` instance. On the other hand, `Value` does not do so, in order to reduce memory consumption. The default allocator used in `GenericDocument` is `MemoryPoolAllocator`. This allocator actually allocate memory sequentially, and cannot deallocate one by one. This is very suitable when parsing a JSON into a DOM tree. Another allocator is `CrtAllocator`, of which CRT is short for C RunTime library. This allocator simply calls the standard `malloc()`/`realloc()`/`free()`. When there is a lot of add and remove operations, this allocator may be preferred. But this allocator is far less efficient than `MemoryPoolAllocator`. # Parsing {#Parsing} `Document` provides several functions for parsing. In below, (1) is the fundamental function, while the others are helpers which call (1). ~~~~~~~~~~cpp using namespace rapidjson; // (1) Fundamental template GenericDocument& GenericDocument::ParseStream(InputStream& is); // (2) Using the same Encoding for stream template GenericDocument& GenericDocument::ParseStream(InputStream& is); // (3) Using default parse flags template GenericDocument& GenericDocument::ParseStream(InputStream& is); // (4) In situ parsing template GenericDocument& GenericDocument::ParseInsitu(Ch* str); // (5) In situ parsing, using default parse flags GenericDocument& GenericDocument::ParseInsitu(Ch* str); // (6) Normal parsing of a string template GenericDocument& GenericDocument::Parse(const Ch* str); // (7) Normal parsing of a string, using same Encoding of Document template GenericDocument& GenericDocument::Parse(const Ch* str); // (8) Normal parsing of a string, using default parse flags GenericDocument& GenericDocument::Parse(const Ch* str); ~~~~~~~~~~ The examples of [tutorial](doc/tutorial.md) uses (8) for normal parsing of string. The examples of [stream](doc/stream.md) uses the first three. *In situ* parsing will be described soon. The `parseFlags` are combination of the following bit-flags: Parse flags | Meaning ------------------------------|----------------------------------- `kParseNoFlags` | No flag is set. `kParseDefaultFlags` | Default parse flags. It is equal to macro `RAPIDJSON_PARSE_DEFAULT_FLAGS`, which is defined as `kParseNoFlags`. `kParseInsituFlag` | In-situ(destructive) parsing. `kParseValidateEncodingFlag` | Validate encoding of JSON strings. `kParseIterativeFlag` | Iterative(constant complexity in terms of function call stack size) parsing. `kParseStopWhenDoneFlag` | After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate `kParseErrorDocumentRootNotSingular` error. Using this flag for parsing multiple JSONs in the same stream. `kParseFullPrecisionFlag` | Parse number in full precision (slower). If this flag is not set, the normal precision (faster) is used. Normal precision has maximum 3 [ULP](http://en.wikipedia.org/wiki/Unit_in_the_last_place) error. `kParseCommentsFlag` | Allow one-line `// ...` and multi-line `/* ... */` comments (relaxed JSON syntax). `kParseNumbersAsStringsFlag` | Parse numerical type values as strings. `kParseTrailingCommasFlag` | Allow trailing commas at the end of objects and arrays (relaxed JSON syntax). `kParseNanAndInfFlag` | Allow parsing `NaN`, `Inf`, `Infinity`, `-Inf` and `-Infinity` as `double` values (relaxed JSON syntax). `kParseEscapedApostropheFlag` | Allow escaped apostrophe `\'` in strings (relaxed JSON syntax). By using a non-type template parameter, instead of a function parameter, C++ compiler can generate code which is optimized for specified combinations, improving speed, and reducing code size (if only using a single specialization). The downside is the flags needed to be determined in compile-time. The `SourceEncoding` parameter defines what encoding is in the stream. This can be differed to the `Encoding` of the `Document`. See [Transcoding and Validation](#TranscodingAndValidation) section for details. And the `InputStream` is type of input stream. ## Parse Error {#ParseError} When the parse processing succeeded, the `Document` contains the parse results. When there is an error, the original DOM is *unchanged*. And the error state of parsing can be obtained by `bool HasParseError()`, `ParseErrorCode GetParseError()` and `size_t GetErrorOffset()`. Parse Error Code | Description --------------------------------------------|--------------------------------------------------- `kParseErrorNone` | No error. `kParseErrorDocumentEmpty` | The document is empty. `kParseErrorDocumentRootNotSingular` | The document root must not follow by other values. `kParseErrorValueInvalid` | Invalid value. `kParseErrorObjectMissName` | Missing a name for object member. `kParseErrorObjectMissColon` | Missing a colon after a name of object member. `kParseErrorObjectMissCommaOrCurlyBracket` | Missing a comma or `}` after an object member. `kParseErrorArrayMissCommaOrSquareBracket` | Missing a comma or `]` after an array element. `kParseErrorStringUnicodeEscapeInvalidHex` | Incorrect hex digit after `\\u` escape in string. `kParseErrorStringUnicodeSurrogateInvalid` | The surrogate pair in string is invalid. `kParseErrorStringEscapeInvalid` | Invalid escape character in string. `kParseErrorStringMissQuotationMark` | Missing a closing quotation mark in string. `kParseErrorStringInvalidEncoding` | Invalid encoding in string. `kParseErrorNumberTooBig` | Number too big to be stored in `double`. `kParseErrorNumberMissFraction` | Miss fraction part in number. `kParseErrorNumberMissExponent` | Miss exponent in number. The offset of error is defined as the character number from beginning of stream. Currently RapidJSON does not keep track of line number. To get an error message, RapidJSON provided a English messages in `rapidjson/error/en.h`. User can customize it for other locales, or use a custom localization system. Here shows an example of parse error handling. ~~~~~~~~~~cpp #include "rapidjson/document.h" #include "rapidjson/error/en.h" // ... Document d; if (d.Parse(json).HasParseError()) { fprintf(stderr, "\nError(offset %u): %s\n", (unsigned)d.GetErrorOffset(), GetParseError_En(d.GetParseError())); // ... } ~~~~~~~~~~ ## In Situ Parsing {#InSituParsing} From [Wikipedia](http://en.wikipedia.org/wiki/In_situ): > *In situ* ... is a Latin phrase that translates literally to "on site" or "in position". It means "locally", "on site", "on the premises" or "in place" to describe an event where it takes place, and is used in many different contexts. > ... > (In computer science) An algorithm is said to be an in situ algorithm, or in-place algorithm, if the extra amount of memory required to execute the algorithm is O(1), that is, does not exceed a constant no matter how large the input. For example, heapsort is an in situ sorting algorithm. In normal parsing process, a large overhead is to decode JSON strings and copy them to other buffers. *In situ* parsing decodes those JSON string at the place where it is stored. It is possible in JSON because the length of decoded string is always shorter than or equal to the one in JSON. In this context, decoding a JSON string means to process the escapes, such as `"\n"`, `"\u1234"`, etc., and add a null terminator (`'\0'`)at the end of string. The following diagrams compare normal and *in situ* parsing. The JSON string values contain pointers to the decoded string. ![normal parsing](diagram/normalparsing.png) In normal parsing, the decoded string are copied to freshly allocated buffers. `"\\n"` (2 characters) is decoded as `"\n"` (1 character). `"\\u0073"` (6 characters) is decoded as `"s"` (1 character). ![instiu parsing](diagram/insituparsing.png) *In situ* parsing just modified the original JSON. Updated characters are highlighted in the diagram. If the JSON string does not contain escape character, such as `"msg"`, the parsing process merely replace the closing double quotation mark with a null character. Since *in situ* parsing modify the input, the parsing API needs `char*` instead of `const char*`. ~~~~~~~~~~cpp // Read whole file into a buffer FILE* fp = fopen("test.json", "r"); fseek(fp, 0, SEEK_END); size_t filesize = (size_t)ftell(fp); fseek(fp, 0, SEEK_SET); char* buffer = (char*)malloc(filesize + 1); size_t readLength = fread(buffer, 1, filesize, fp); buffer[readLength] = '\0'; fclose(fp); // In situ parsing the buffer into d, buffer will also be modified Document d; d.ParseInsitu(buffer); // Query/manipulate the DOM here... free(buffer); // Note: At this point, d may have dangling pointers pointed to the deallocated buffer. ~~~~~~~~~~ The JSON strings are marked as const-string. But they may not be really "constant". The life cycle of it depends on the JSON buffer. In situ parsing minimizes allocation overheads and memory copying. Generally this improves cache coherence, which is an important factor of performance in modern computer. There are some limitations of *in situ* parsing: 1. The whole JSON is in memory. 2. The source encoding in stream and target encoding in document must be the same. 3. The buffer need to be retained until the document is no longer used. 4. If the DOM need to be used for long period after parsing, and there are few JSON strings in the DOM, retaining the buffer may be a memory waste. *In situ* parsing is mostly suitable for short-term JSON that only need to be processed once, and then be released from memory. In practice, these situation is very common, for example, deserializing JSON to C++ objects, processing web requests represented in JSON, etc. ## Transcoding and Validation {#TranscodingAndValidation} RapidJSON supports conversion between Unicode formats (officially termed UCS Transformation Format) internally. During DOM parsing, the source encoding of the stream can be different from the encoding of the DOM. For example, the source stream contains a UTF-8 JSON, while the DOM is using UTF-16 encoding. There is an example code in [EncodedInputStream](doc/stream.md). When writing a JSON from DOM to output stream, transcoding can also be used. An example is in [EncodedOutputStream](doc/stream.md). During transcoding, the source string is decoded to into Unicode code points, and then the code points are encoded in the target format. During decoding, it will validate the byte sequence in the source string. If it is not a valid sequence, the parser will be stopped with `kParseErrorStringInvalidEncoding` error. When the source encoding of stream is the same as encoding of DOM, by default, the parser will *not* validate the sequence. User may use `kParseValidateEncodingFlag` to force validation. # Techniques {#Techniques} Some techniques about using DOM API is discussed here. ## DOM as SAX Event Publisher In RapidJSON, stringifying a DOM with `Writer` may be look a little bit weird. ~~~~~~~~~~cpp // ... Writer writer(buffer); d.Accept(writer); ~~~~~~~~~~ Actually, `Value::Accept()` is responsible for publishing SAX events about the value to the handler. With this design, `Value` and `Writer` are decoupled. `Value` can generate SAX events, and `Writer` can handle those events. User may create custom handlers for transforming the DOM into other formats. For example, a handler which converts the DOM into XML. For more about SAX events and handler, please refer to [SAX](doc/sax.md). ## User Buffer {#UserBuffer} Some applications may try to avoid memory allocations whenever possible. `MemoryPoolAllocator` can support this by letting user to provide a buffer. The buffer can be on the program stack, or a "scratch buffer" which is statically allocated (a static/global array) for storing temporary data. `MemoryPoolAllocator` will use the user buffer to satisfy allocations. When the user buffer is used up, it will allocate a chunk of memory from the base allocator (by default the `CrtAllocator`). Here is an example of using stack memory. The first allocator is for storing values, while the second allocator is for storing temporary data during parsing. ~~~~~~~~~~cpp typedef GenericDocument, MemoryPoolAllocator<>, MemoryPoolAllocator<>> DocumentType; char valueBuffer[4096]; char parseBuffer[1024]; MemoryPoolAllocator<> valueAllocator(valueBuffer, sizeof(valueBuffer)); MemoryPoolAllocator<> parseAllocator(parseBuffer, sizeof(parseBuffer)); DocumentType d(&valueAllocator, sizeof(parseBuffer), &parseAllocator); d.Parse(json); ~~~~~~~~~~ If the total size of allocation is less than 4096+1024 bytes during parsing, this code does not invoke any heap allocation (via `new` or `malloc()`) at all. User can query the current memory consumption in bytes via `MemoryPoolAllocator::Size()`. And then user can determine a suitable size of user buffer. opentimelineio-0.18.1/src/deps/rapidjson/doc/schema.md0000664000175000017500000004345615110656147020473 0ustar meme# Schema (This feature was released in v1.1.0) JSON Schema is a draft standard for describing the format of JSON data. The schema itself is also JSON data. By validating a JSON structure with JSON Schema, your code can safely access the DOM without manually checking types, or whether a key exists, etc. It can also ensure that the serialized JSON conform to a specified schema. RapidJSON implemented a JSON Schema validator for [JSON Schema Draft v4](http://json-schema.org/documentation.html). If you are not familiar with JSON Schema, you may refer to [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/). [TOC] # Basic Usage {#Basic} First of all, you need to parse a JSON Schema into `Document`, and then compile the `Document` into a `SchemaDocument`. Secondly, construct a `SchemaValidator` with the `SchemaDocument`. It is similar to a `Writer` in the sense of handling SAX events. So, you can use `document.Accept(validator)` to validate a document, and then check the validity. ~~~cpp #include "rapidjson/schema.h" // ... Document sd; if (sd.Parse(schemaJson).HasParseError()) { // the schema is not a valid JSON. // ... } SchemaDocument schema(sd); // Compile a Document to SchemaDocument if (!schema.GetError().ObjectEmpty()) { // there was a problem compiling the schema StringBuffer sb; Writer w(sb); schema.GetError().Accept(w); printf("Invalid schema: %s\n", sb.GetString()); } // sd is no longer needed here. Document d; if (d.Parse(inputJson).HasParseError()) { // the input is not a valid JSON. // ... } SchemaValidator validator(schema); if (!d.Accept(validator)) { // Input JSON is invalid according to the schema // Output diagnostic information StringBuffer sb; validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); printf("Invalid schema: %s\n", sb.GetString()); printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); sb.Clear(); validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); printf("Invalid document: %s\n", sb.GetString()); } ~~~ Some notes: * One `SchemaDocument` can be referenced by multiple `SchemaValidator`s. It will not be modified by `SchemaValidator`s. * A `SchemaValidator` may be reused to validate multiple documents. To run it for other documents, call `validator.Reset()` first. # Validation during parsing/serialization {#Fused} Unlike most JSON Schema validator implementations, RapidJSON provides a SAX-based schema validator. Therefore, you can parse a JSON from a stream while validating it on the fly. If the validator encounters a JSON value that invalidates the supplied schema, the parsing will be terminated immediately. This design is especially useful for parsing large JSON files. ## DOM parsing {#DOM} For using DOM in parsing, `Document` needs some preparation and finalizing tasks, in addition to receiving SAX events, thus it needs some work to route the reader, validator and the document. `SchemaValidatingReader` is a helper class that doing such work. ~~~cpp #include "rapidjson/filereadstream.h" // ... SchemaDocument schema(sd); // Compile a Document to SchemaDocument // Use reader to parse the JSON FILE* fp = fopen("big.json", "r"); FileReadStream is(fp, buffer, sizeof(buffer)); // Parse JSON from reader, validate the SAX events, and store in d. Document d; SchemaValidatingReader > reader(is, schema); d.Populate(reader); if (!reader.GetParseResult()) { // Not a valid JSON // When reader.GetParseResult().Code() == kParseErrorTermination, // it may be terminated by: // (1) the validator found that the JSON is invalid according to schema; or // (2) the input stream has I/O error. // Check the validation result if (!reader.IsValid()) { // Input JSON is invalid according to the schema // Output diagnostic information StringBuffer sb; reader.GetInvalidSchemaPointer().StringifyUriFragment(sb); printf("Invalid schema: %s\n", sb.GetString()); printf("Invalid keyword: %s\n", reader.GetInvalidSchemaKeyword()); sb.Clear(); reader.GetInvalidDocumentPointer().StringifyUriFragment(sb); printf("Invalid document: %s\n", sb.GetString()); } } ~~~ ## SAX parsing {#SAX} For using SAX in parsing, it is much simpler. If it only need to validate the JSON without further processing, it is simply: ~~~ SchemaValidator validator(schema); Reader reader; if (!reader.Parse(stream, validator)) { if (!validator.IsValid()) { // ... } } ~~~ This is exactly the method used in the [schemavalidator](example/schemavalidator/schemavalidator.cpp) example. The distinct advantage is low memory usage, no matter how big the JSON was (the memory usage depends on the complexity of the schema). If you need to handle the SAX events further, then you need to use the template class `GenericSchemaValidator` to set the output handler of the validator: ~~~ MyHandler handler; GenericSchemaValidator validator(schema, handler); Reader reader; if (!reader.Parse(ss, validator)) { if (!validator.IsValid()) { // ... } } ~~~ ## Serialization {#Serialization} It is also possible to do validation during serializing. This can ensure the result JSON is valid according to the JSON schema. ~~~ StringBuffer sb; Writer writer(sb); GenericSchemaValidator > validator(s, writer); if (!d.Accept(validator)) { // Some problem during Accept(), it may be validation or encoding issues. if (!validator.IsValid()) { // ... } } ~~~ Of course, if your application only needs SAX-style serialization, it can simply send SAX events to `SchemaValidator` instead of `Writer`. # Remote Schema {#Remote} JSON Schema supports [`$ref` keyword](http://spacetelescope.github.io/understanding-json-schema/structuring.html), which is a [JSON pointer](doc/pointer.md) referencing to a local or remote schema. Local pointer is prefixed with `#`, while remote pointer is an relative or absolute URI. For example: ~~~js { "$ref": "definitions.json#/address" } ~~~ As `SchemaDocument` does not know how to resolve such URI, it needs a user-provided `IRemoteSchemaDocumentProvider` instance to do so. ~~~ class MyRemoteSchemaDocumentProvider : public IRemoteSchemaDocumentProvider { public: virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) { // Resolve the uri and returns a pointer to that schema. } }; // ... MyRemoteSchemaDocumentProvider provider; SchemaDocument schema(sd, &provider); ~~~ # Conformance {#Conformance} RapidJSON passed 262 out of 263 tests in [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) (Json Schema draft 4). The failed test is "changed scope ref invalid" of "change resolution scope" in `refRemote.json`. It is due to that `id` schema keyword and URI combining function are not implemented. Besides, the `format` schema keyword for string values is ignored, since it is not required by the specification. ## Regular Expression {#Regex} The schema keyword `pattern` and `patternProperties` uses regular expression to match the required pattern. RapidJSON implemented a simple NFA regular expression engine, which is used by default. It supports the following syntax. |Syntax|Description| |------|-----------| |`ab` | Concatenation | |a|b | Alternation | |`a?` | Zero or one | |`a*` | Zero or more | |`a+` | One or more | |`a{3}` | Exactly 3 times | |`a{3,}` | At least 3 times | |`a{3,5}`| 3 to 5 times | |`(ab)` | Grouping | |`^a` | At the beginning | |`a$` | At the end | |`.` | Any character | |`[abc]` | Character classes | |`[a-c]` | Character class range | |`[a-z0-9_]` | Character class combination | |`[^abc]` | Negated character classes | |`[^a-c]` | Negated character class range | |`[\b]` | Backspace (U+0008) | |\\|, `\\`, ... | Escape characters | |`\f` | Form feed (U+000C) | |`\n` | Line feed (U+000A) | |`\r` | Carriage return (U+000D) | |`\t` | Tab (U+0009) | |`\v` | Vertical tab (U+000B) | For C++11 compiler, it is also possible to use the `std::regex` by defining `RAPIDJSON_SCHEMA_USE_INTERNALREGEX=0` and `RAPIDJSON_SCHEMA_USE_STDREGEX=1`. If your schemas do not need `pattern` and `patternProperties`, you can set both macros to zero to disable this feature, which will reduce some code size. # Performance {#Performance} Most C++ JSON libraries do not yet support JSON Schema. So we tried to evaluate the performance of RapidJSON's JSON Schema validator according to [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark), which tests 11 JavaScript libraries running on Node.js. That benchmark runs validations on [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite), in which some test suites and tests are excluded. We made the same benchmarking procedure in [`schematest.cpp`](test/perftest/schematest.cpp). On a Mac Book Pro (2.8 GHz Intel Core i7), the following results are collected. |Validator|Relative speed|Number of test runs per second| |---------|:------------:|:----------------------------:| |RapidJSON|155%|30682| |[`ajv`](https://github.com/epoberezkin/ajv)|100%|19770 (± 1.31%)| |[`is-my-json-valid`](https://github.com/mafintosh/is-my-json-valid)|70%|13835 (± 2.84%)| |[`jsen`](https://github.com/bugventure/jsen)|57.7%|11411 (± 1.27%)| |[`schemasaurus`](https://github.com/AlexeyGrishin/schemasaurus)|26%|5145 (± 1.62%)| |[`themis`](https://github.com/playlyfe/themis)|19.9%|3935 (± 2.69%)| |[`z-schema`](https://github.com/zaggino/z-schema)|7%|1388 (± 0.84%)| |[`jsck`](https://github.com/pandastrike/jsck#readme)|3.1%|606 (± 2.84%)| |[`jsonschema`](https://github.com/tdegrunt/jsonschema#readme)|0.9%|185 (± 1.01%)| |[`skeemas`](https://github.com/Prestaul/skeemas#readme)|0.8%|154 (± 0.79%)| |tv4|0.5%|93 (± 0.94%)| |[`jayschema`](https://github.com/natesilva/jayschema)|0.1%|21 (± 1.14%)| That is, RapidJSON is about 1.5x faster than the fastest JavaScript library (ajv). And 1400x faster than the slowest one. # Schema violation reporting {#Reporting} (Unreleased as of 2017-09-20) When validating an instance against a JSON Schema, it is often desirable to report not only whether the instance is valid, but also the ways in which it violates the schema. The `SchemaValidator` class collects errors encountered during validation into a JSON `Value`. This error object can then be accessed as `validator.GetError()`. The structure of the error object is subject to change in future versions of RapidJSON, as there is no standard schema for violations. The details below this point are provisional only. ## General provisions {#ReportingGeneral} Validation of an instance value against a schema produces an error value. The error value is always an object. An empty object `{}` indicates the instance is valid. * The name of each member corresponds to the JSON Schema keyword that is violated. * The value is either an object describing a single violation, or an array of such objects. Each violation object contains two string-valued members named `instanceRef` and `schemaRef`. `instanceRef` contains the URI fragment serialization of a JSON Pointer to the instance subobject in which the violation was detected. `schemaRef` contains the URI of the schema and the fragment serialization of a JSON Pointer to the subschema that was violated. Individual violation objects can contain other keyword-specific members. These are detailed further. For example, validating this instance: ~~~json {"numbers": [1, 2, "3", 4, 5]} ~~~ against this schema: ~~~json { "type": "object", "properties": { "numbers": {"$ref": "numbers.schema.json"} } } ~~~ where `numbers.schema.json` refers (via a suitable `IRemoteSchemaDocumentProvider`) to this schema: ~~~json { "type": "array", "items": {"type": "number"} } ~~~ produces the following error object: ~~~json { "type": { "instanceRef": "#/numbers/2", "schemaRef": "numbers.schema.json#/items", "expected": ["number"], "actual": "string" } } ~~~ ## Validation keywords for numbers {#Numbers} ### multipleOf {#multipleof} * `expected`: required number strictly greater than 0. The value of the `multipleOf` keyword specified in the schema. * `actual`: required number. The instance value. ### maximum {#maximum} * `expected`: required number. The value of the `maximum` keyword specified in the schema. * `exclusiveMaximum`: optional boolean. This will be true if the schema specified `"exclusiveMaximum": true`, and will be omitted otherwise. * `actual`: required number. The instance value. ### minimum {#minimum} * `expected`: required number. The value of the `minimum` keyword specified in the schema. * `exclusiveMinimum`: optional boolean. This will be true if the schema specified `"exclusiveMinimum": true`, and will be omitted otherwise. * `actual`: required number. The instance value. ## Validation keywords for strings {#Strings} ### maxLength {#maxLength} * `expected`: required number greater than or equal to 0. The value of the `maxLength` keyword specified in the schema. * `actual`: required string. The instance value. ### minLength {#minLength} * `expected`: required number greater than or equal to 0. The value of the `minLength` keyword specified in the schema. * `actual`: required string. The instance value. ### pattern {#pattern} * `actual`: required string. The instance value. (The expected pattern is not reported because the internal representation in `SchemaDocument` does not store the pattern in original string form.) ## Validation keywords for arrays {#Arrays} ### additionalItems {#additionalItems} This keyword is reported when the value of `items` schema keyword is an array, the value of `additionalItems` is `false`, and the instance is an array with more items than specified in the `items` array. * `disallowed`: required integer greater than or equal to 0. The index of the first item that has no corresponding schema. ### maxItems and minItems {#maxItems-minItems} * `expected`: required integer greater than or equal to 0. The value of `maxItems` (respectively, `minItems`) specified in the schema. * `actual`: required integer greater than or equal to 0. Number of items in the instance array. ### uniqueItems {#uniqueItems} * `duplicates`: required array whose items are integers greater than or equal to 0. Indices of items of the instance that are equal. (RapidJSON only reports the first two equal items, for performance reasons.) ## Validation keywords for objects ### maxProperties and minProperties {#maxProperties-minProperties} * `expected`: required integer greater than or equal to 0. The value of `maxProperties` (respectively, `minProperties`) specified in the schema. * `actual`: required integer greater than or equal to 0. Number of properties in the instance object. ### required {#required} * `missing`: required array of one or more unique strings. The names of properties that are listed in the value of the `required` schema keyword but not present in the instance object. ### additionalProperties {#additionalProperties} This keyword is reported when the schema specifies `additionalProperties: false` and the name of a property of the instance is neither listed in the `properties` keyword nor matches any regular expression in the `patternProperties` keyword. * `disallowed`: required string. Name of the offending property of the instance. (For performance reasons, RapidJSON only reports the first such property encountered.) ### dependencies {#dependencies} * `errors`: required object with one or more properties. Names and values of its properties are described below. Recall that JSON Schema Draft 04 supports *schema dependencies*, where presence of a named *controlling* property requires the instance object to be valid against a subschema, and *property dependencies*, where presence of a controlling property requires other *dependent* properties to be also present. For a violated schema dependency, `errors` will contain a property with the name of the controlling property and its value will be the error object produced by validating the instance object against the dependent schema. For a violated property dependency, `errors` will contain a property with the name of the controlling property and its value will be an array of one or more unique strings listing the missing dependent properties. ## Validation keywords for any instance type {#AnyTypes} ### enum {#enum} This keyword has no additional properties beyond `instanceRef` and `schemaRef`. * The allowed values are not listed because `SchemaDocument` does not store them in original form. * The violating value is not reported because it might be unwieldy. If you need to report these details to your users, you can access the necessary information by following `instanceRef` and `schemaRef`. ### type {#type} * `expected`: required array of one or more unique strings, each of which is one of the seven primitive types defined by the JSON Schema Draft 04 Core specification. Lists the types allowed by the `type` schema keyword. * `actual`: required string, also one of seven primitive types. The primitive type of the instance. ### allOf, anyOf, and oneOf {#allOf-anyOf-oneOf} * `errors`: required array of at least one object. There will be as many items as there are subschemas in the `allOf`, `anyOf` or `oneOf` schema keyword, respectively. Each item will be the error value produced by validating the instance against the corresponding subschema. For `allOf`, at least one error value will be non-empty. For `anyOf`, all error values will be non-empty. For `oneOf`, either all error values will be non-empty, or more than one will be empty. ### not {#not} This keyword has no additional properties apart from `instanceRef` and `schemaRef`. opentimelineio-0.18.1/src/deps/rapidjson/doc/features.md0000664000175000017500000001170715110656147021043 0ustar meme# Features ## General * Cross-platform * Compilers: Visual Studio, gcc, clang, etc. * Architectures: x86, x64, ARM, etc. * Operating systems: Windows, Mac OS X, Linux, iOS, Android, etc. * Easy installation * Header files only library. Just copy the headers to your project. * Self-contained, minimal dependences * No STL, BOOST, etc. * Only included ``, ``, ``, ``, ``, ``. * Without C++ exception, RTTI * High performance * Use template and inline functions to reduce function call overheads. * Internal optimized Grisu2 and floating point parsing implementations. * Optional SSE2/SSE4.2 support. ## Standard compliance * RapidJSON should be fully RFC4627/ECMA-404 compliance. * Support JSON Pointer (RFC6901). * Support JSON Schema Draft v4. * Support Swagger v2 schema. * Support OpenAPI v3.0.x schema. * Support Unicode surrogate. * Support null character (`"\u0000"`) * For example, `["Hello\u0000World"]` can be parsed and handled gracefully. There is API for getting/setting lengths of string. * Support optional relaxed syntax. * Single line (`// ...`) and multiple line (`/* ... */`) comments (`kParseCommentsFlag`). * Trailing commas at the end of objects and arrays (`kParseTrailingCommasFlag`). * `NaN`, `Inf`, `Infinity`, `-Inf` and `-Infinity` as `double` values (`kParseNanAndInfFlag`) * [NPM compliant](http://github.com/Tencent/rapidjson/blob/master/doc/npm.md). ## Unicode * Support UTF-8, UTF-16, UTF-32 encodings, including little endian and big endian. * These encodings are used in input/output streams and in-memory representation. * Support automatic detection of encodings in input stream. * Support transcoding between encodings internally. * For example, you can read a UTF-8 file and let RapidJSON transcode the JSON strings into UTF-16 in the DOM. * Support encoding validation internally. * For example, you can read a UTF-8 file, and let RapidJSON check whether all JSON strings are valid UTF-8 byte sequence. * Support custom character types. * By default the character types are `char` for UTF8, `wchar_t` for UTF16, `uint32_t` for UTF32. * Support custom encodings. ## API styles * SAX (Simple API for XML) style API * Similar to [SAX](http://en.wikipedia.org/wiki/Simple_API_for_XML), RapidJSON provides a event sequential access parser API (`rapidjson::GenericReader`). It also provides a generator API (`rapidjson::Writer`) which consumes the same set of events. * DOM (Document Object Model) style API * Similar to [DOM](http://en.wikipedia.org/wiki/Document_Object_Model) for HTML/XML, RapidJSON can parse JSON into a DOM representation (`rapidjson::GenericDocument`), for easy manipulation, and finally stringify back to JSON if needed. * The DOM style API (`rapidjson::GenericDocument`) is actually implemented with SAX style API (`rapidjson::GenericReader`). SAX is faster but sometimes DOM is easier. Users can pick their choices according to scenarios. ## Parsing * Recursive (default) and iterative parser * Recursive parser is faster but prone to stack overflow in extreme cases. * Iterative parser use custom stack to keep parsing state. * Support *in situ* parsing. * Parse JSON string values in-place at the source JSON, and then the DOM points to addresses of those strings. * Faster than convention parsing: no allocation for strings, no copy (if string does not contain escapes), cache-friendly. * Support 32-bit/64-bit signed/unsigned integer and `double` for JSON number type. * Support parsing multiple JSONs in input stream (`kParseStopWhenDoneFlag`). * Error Handling * Support comprehensive error code if parsing failed. * Support error message localization. ## DOM (Document) * RapidJSON checks range of numerical values for conversions. * Optimization for string literal * Only store pointer instead of copying * Optimization for "short" strings * Store short string in `Value` internally without additional allocation. * For UTF-8 string: maximum 11 characters in 32-bit, 21 characters in 64-bit (13 characters in x86-64). * Optionally support `std::string` (define `RAPIDJSON_HAS_STDSTRING=1`) ## Generation * Support `rapidjson::PrettyWriter` for adding newlines and indentations. ## Stream * Support `rapidjson::GenericStringBuffer` for storing the output JSON as string. * Support `rapidjson::FileReadStream` and `rapidjson::FileWriteStream` for input/output `FILE` object. * Support custom streams. ## Memory * Minimize memory overheads for DOM. * Each JSON value occupies exactly 16/20 bytes for most 32/64-bit machines (excluding text string). * Support fast default allocator. * A stack-based allocator (allocate sequentially, prohibit to free individual allocations, suitable for parsing). * User can provide a pre-allocated buffer. (Possible to parse a number of JSONs without any CRT allocation) * Support standard CRT(C-runtime) allocator. * Support custom allocators. ## Miscellaneous * Some C++11 support (optional) * Rvalue reference * `noexcept` specifier * Range-based for loop opentimelineio-0.18.1/src/deps/rapidjson/doc/internals.zh-cn.md0000664000175000017500000005270415110656147022244 0ustar meme# 内部架构 本部分记录了一些设计和实现细节。 [TOC] # 架构 {#Architecture} ## SAX 和 DOM 下面的 UML 图显示了 SAX 和 DOM 的基本关系。 ![架构 UML 类图](diagram/architecture.png) 关系的核心是 `Handler` 概念。在 SAX 一边,`Reader` 从流解析 JSON 并将事件发送到 `Handler`。`Writer` 实现了 `Handler` 概念,用于处理相同的事件。在 DOM 一边,`Document` 实现了 `Handler` 概念,用于通过这些时间来构建 DOM。`Value` 支持了 `Value::Accept(Handler&)` 函数,它可以将 DOM 转换为事件进行发送。 在这个设计,SAX 是不依赖于 DOM 的。甚至 `Reader` 和 `Writer` 之间也没有依赖。这提供了连接事件发送器和处理器的灵活性。除此之外,`Value` 也是不依赖于 SAX 的。所以,除了将 DOM 序列化为 JSON 之外,用户也可以将其序列化为 XML,或者做任何其他事情。 ## 工具类 SAX 和 DOM API 都依赖于3个额外的概念:`Allocator`、`Encoding` 和 `Stream`。它们的继承层次结构如下图所示。 ![工具类 UML 类图](diagram/utilityclass.png) # 值(Value) {#Value} `Value` (实际上被定义为 `GenericValue>`)是 DOM API 的核心。本部分描述了它的设计。 ## 数据布局 {#DataLayout} `Value` 是[可变类型](http://en.wikipedia.org/wiki/Variant_type)。在 RapidJSON 的上下文中,一个 `Value` 的实例可以包含6种 JSON 数据类型之一。通过使用 `union` ,这是可能实现的。每一个 `Value` 包含两个成员:`union Data data_` 和 `unsigned flags_`。`flags_` 表明了 JSON 类型,以及附加的信息。 下表显示了所有类型的数据布局。32位/64位列表明了字段所占用的字节数。 | Null | | 32位 | 64位 | |-------------------|----------------------------------|:----:|:----:| | (未使用) | |4 |8 | | (未使用) | |4 |4 | | (未使用) | |4 |4 | | `unsigned flags_` | `kNullType kNullFlag` |4 |4 | | Bool | | 32位 | 64位 | |-------------------|----------------------------------------------------|:----:|:----:| | (未使用) | |4 |8 | | (未使用) | |4 |4 | | (未使用) | |4 |4 | | `unsigned flags_` | `kBoolType` (either `kTrueFlag` or `kFalseFlag`) |4 |4 | | String | | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `Ch* str` | 指向字符串的指针(可能拥有所有权) |4 |8 | | `SizeType length` | 字符串长度 |4 |4 | | (未使用) | |4 |4 | | `unsigned flags_` | `kStringType kStringFlag ...` |4 |4 | | Object | | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `Member* members` | 指向成员数组的指针(拥有所有权) |4 |8 | | `SizeType size` | 成员数量 |4 |4 | | `SizeType capacity` | 成员容量 |4 |4 | | `unsigned flags_` | `kObjectType kObjectFlag` |4 |4 | | Array | | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `Value* values` | 指向值数组的指针(拥有所有权) |4 |8 | | `SizeType size` | 值数量 |4 |4 | | `SizeType capacity` | 值容量 |4 |4 | | `unsigned flags_` | `kArrayType kArrayFlag` |4 |4 | | Number (Int) | | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `int i` | 32位有符号整数 |4 |4 | | (零填充) | 0 |4 |4 | | (未使用) | |4 |8 | | `unsigned flags_` | `kNumberType kNumberFlag kIntFlag kInt64Flag ...` |4 |4 | | Number (UInt) | | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `unsigned u` | 32位无符号整数 |4 |4 | | (零填充) | 0 |4 |4 | | (未使用) | |4 |8 | | `unsigned flags_` | `kNumberType kNumberFlag kUintFlag kUint64Flag ...` |4 |4 | | Number (Int64) | | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `int64_t i64` | 64位有符号整数 |8 |8 | | (未使用) | |4 |8 | | `unsigned flags_` | `kNumberType kNumberFlag kInt64Flag ...` |4 |4 | | Number (Uint64) | | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `uint64_t i64` | 64位无符号整数 |8 |8 | | (未使用) | |4 |8 | | `unsigned flags_` | `kNumberType kNumberFlag kInt64Flag ...` |4 |4 | | Number (Double) | | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `uint64_t i64` | 双精度浮点数 |8 |8 | | (未使用) | |4 |8 | | `unsigned flags_` |`kNumberType kNumberFlag kDoubleFlag`|4 |4 | 这里有一些需要注意的地方: * 为了减少在64位架构上的内存消耗,`SizeType` 被定义为 `unsigned` 而不是 `size_t`。 * 32位整数的零填充可能被放在实际类型的前面或后面,这依赖于字节序。这使得它可以将32位整数不经过任何转换就可以解释为64位整数。 * `Int` 永远是 `Int64`,反之不然。 ## 标志 {#Flags} 32位的 `flags_` 包含了 JSON 类型和其他信息。如前文中的表所述,每一种 JSON 类型包含了冗余的 `kXXXType` 和 `kXXXFlag`。这个设计是为了优化测试位标志(`IsNumber()`)和获取每一种类型的序列号(`GetType()`)。 字符串有两个可选的标志。`kCopyFlag` 表明这个字符串拥有字符串拷贝的所有权。而 `kInlineStrFlag` 意味着使用了[短字符串优化](#ShortString)。 数字更加复杂一些。对于普通的整数值,它可以包含 `kIntFlag`、`kUintFlag`、 `kInt64Flag` 和/或 `kUint64Flag`,这由整数的范围决定。带有小数或者超过64位所能表达的范围的整数的数字会被存储为带有 `kDoubleFlag` 的 `double`。 ## 短字符串优化 {#ShortString} [Kosta](https://github.com/Kosta-Github) 提供了很棒的短字符串优化。这个优化的xxx如下所述。除去 `flags_` ,`Value` 有12或16字节(对于32位或64位)来存储实际的数据。这为在其内部直接存储短字符串而不是存储字符串的指针创造了可能。对于1字节的字符类型(例如 `char`),它可以在 `Value` 类型内部存储至多11或15个字符的字符串。 |ShortString (Ch=char)| | 32位 | 64位 | |---------------------|-------------------------------------|:----:|:----:| | `Ch str[MaxChars]` | 字符串缓冲区 |11 |15 | | `Ch invLength` | MaxChars - Length |1 |1 | | `unsigned flags_` | `kStringType kStringFlag ...` |4 |4 | 这里使用了一项特殊的技术。它存储了 (MaxChars - length) 而不直接存储字符串的长度。这使得存储11个字符并且带有后缀 `\0` 成为可能。 这个优化可以减少字符串拷贝内存占用。它也改善了缓存一致性,并进一步提高了运行时性能。 # 分配器(Allocator) {#InternalAllocator} `Allocator` 是 RapidJSON 中的概念: ~~~cpp concept Allocator { static const bool kNeedFree; //!< 表明这个分配器是否需要调用 Free()。 // 申请内存块。 // \param size 内存块的大小,以字节记。 // \returns 指向内存块的指针。 void* Malloc(size_t size); // 调整内存块的大小。 // \param originalPtr 当前内存块的指针。空指针是被允许的。 // \param originalSize 当前大小,以字节记。(设计问题:因为有些分配器可能不会记录它,显示的传递它可以节约内存。) // \param newSize 新大小,以字节记。 void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); // 释放内存块。 // \param ptr 指向内存块的指针。空指针是被允许的。 static void Free(void *ptr); }; ~~~ 需要注意的是 `Malloc()` 和 `Realloc()` 是成员函数而 `Free()` 是静态成员函数。 ## MemoryPoolAllocator {#MemoryPoolAllocator} `MemoryPoolAllocator` 是 DOM 的默认内存分配器。它只申请内存而不释放内存。这对于构建 DOM 树非常合适。 在它的内部,它从基础的内存分配器申请内存块(默认为 `CrtAllocator`)并将这些内存块存储为单向链表。当用户请求申请内存,它会遵循下列步骤来申请内存: 1. 如果可用,使用用户提供的缓冲区。(见 [User Buffer section in DOM](doc/dom.md)) 2. 如果用户提供的缓冲区已满,使用当前内存块。 3. 如果当前内存块已满,申请新的内存块。 # 解析优化 {#ParsingOptimization} ## 使用 SIMD 跳过空格 {#SkipwhitespaceWithSIMD} 当从流中解析 JSON 时,解析器需要跳过4种空格字符: 1. 空格 (`U+0020`) 2. 制表符 (`U+000B`) 3. 换行 (`U+000A`) 4. 回车 (`U+000D`) 这是一份简单的实现: ~~~cpp void SkipWhitespace(InputStream& s) { while (s.Peek() == ' ' || s.Peek() == '\n' || s.Peek() == '\r' || s.Peek() == '\t') s.Take(); } ~~~ 但是,这需要对每个字符进行4次比较以及一些分支。这被发现是一个热点。 为了加速这一处理,RapidJSON 使用 SIMD 来在一次迭代中比较16个字符和4个空格。目前 RapidJSON 支持 SSE2 , SSE4.2 和 ARM Neon 指令。同时它也只会对 UTF-8 内存流启用,包括字符串流或 *原位* 解析。 你可以通过在包含 `rapidjson.h` 之前定义 `RAPIDJSON_SSE2` , `RAPIDJSON_SSE42` 或 `RAPIDJSON_NEON` 来启用这个优化。一些编译器可以检测这个设置,如 `perftest.h`: ~~~cpp // __SSE2__ 和 __SSE4_2__ 可被 gcc、clang 和 Intel 编译器识别: // 如果支持的话,我们在 gmake 中使用了 -march=native 来启用 -msse2 和 -msse4.2 // 同样的, __ARM_NEON 被用于识别Neon #if defined(__SSE4_2__) # define RAPIDJSON_SSE42 #elif defined(__SSE2__) # define RAPIDJSON_SSE2 #elif defined(__ARM_NEON) # define RAPIDJSON_NEON #endif ~~~ 需要注意的是,这是编译期的设置。在不支持这些指令的机器上运行可执行文件会使它崩溃。 ### 页面对齐问题 在 RapidJSON 的早期版本中,被报告了[一个问题](https://code.google.com/archive/p/rapidjson/issues/104):`SkipWhitespace_SIMD()` 会罕见地导致崩溃(约五十万分之一的几率)。在调查之后,怀疑是 `_mm_loadu_si128()` 访问了 `'\0'` 之后的内存,并越过被保护的页面边界。 在 [Intel® 64 and IA-32 Architectures Optimization Reference Manual ](http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-optimization-manual.html) 中,章节 10.2.1: > 为了支持需要费对齐的128位 SIMD 内存访问的算法,调用者的内存缓冲区申请应当考虑添加一些填充空间,这样被调用的函数可以安全地将地址指针用于未对齐的128位 SIMD 内存操作。 > 在结合非对齐的 SIMD 内存操作中,最小的对齐大小应该等于 SIMD 寄存器的大小。 对于 RapidJSON 来说,这显然是不可行的,因为 RapidJSON 不应当强迫用户进行内存对齐。 为了修复这个问题,当前的代码会先按字节处理直到下一个对齐的地址。在这之后,使用对齐读取来进行 SIMD 处理。见 [#85](https://github.com/Tencent/rapidjson/issues/85)。 ## 局部流拷贝 {#LocalStreamCopy} 在优化的过程中,我们发现一些编译器不能将访问流的一些成员数据放入局部变量或者寄存器中。测试结果显示,对于一些流类型,创建流的拷贝并将其用于内层循环中可以改善性能。例如,实际(非 SIMD)的 `SkipWhitespace()` 被实现为: ~~~cpp template void SkipWhitespace(InputStream& is) { internal::StreamLocalCopy copy(is); InputStream& s(copy.s); while (s.Peek() == ' ' || s.Peek() == '\n' || s.Peek() == '\r' || s.Peek() == '\t') s.Take(); } ~~~ 基于流的特征,`StreamLocalCopy` 会创建(或不创建)流对象的拷贝,在局部使用它并将流的状态拷贝回原来的流。 ## 解析为双精度浮点数 {#ParsingDouble} 将字符串解析为 `double` 并不简单。标准库函数 `strtod()` 可以胜任这项工作,但它比较缓慢。默认情况下,解析器使用默认的精度设置。这最多有 3[ULP](http://en.wikipedia.org/wiki/Unit_in_the_last_place) 的误差,并实现在 `internal::StrtodNormalPrecision()` 中。 当使用 `kParseFullPrecisionFlag` 时,编译器会改为调用 `internal::StrtodFullPrecision()` ,这个函数会自动调用三个版本的转换。 1. [Fast-Path](http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/)。 2. [double-conversion](https://github.com/floitsch/double-conversion) 中的自定义 DIY-FP 实现。 3. (Clinger, William D. How to read floating point numbers accurately. Vol. 25. No. 6. ACM, 1990) 中的大整数算法。 如果第一个转换方法失败,则尝试使用第二种方法,以此类推。 # 生成优化 {#GenerationOptimization} ## 整数到字符串的转换 {#itoa} 整数到字符串转换的朴素算法需要对每一个十进制位进行一次除法。我们实现了若干版本并在 [itoa-benchmark](https://github.com/miloyip/itoa-benchmark) 中对它们进行了评估。 虽然 SSE2 版本是最快的,但它和第二快的 `branchlut` 差距不大。而且 `branchlut` 是纯C++实现,所以我们在 RapidJSON 中使用了 `branchlut`。 ## 双精度浮点数到字符串的转换 {#dtoa} 原来 RapidJSON 使用 `snprintf(..., ..., "%g")` 来进行双精度浮点数到字符串的转换。这是不准确的,因为默认的精度是6。随后我们发现它很缓慢,而且有其它的替代品。 Google 的 V8 [double-conversion](https://github.com/floitsch/double-conversion ) 实现了更新的、快速的被称为 Grisu3 的算法(Loitsch, Florian. "Printing floating-point numbers quickly and accurately with integers." ACM Sigplan Notices 45.6 (2010): 233-243.)。 然而,这个实现不是仅头文件的,所以我们实现了一个仅头文件的 Grisu2 版本。这个算法保证了结果永远精确。而且在大多数情况下,它会生成最短的(可选)字符串表示。 这个仅头文件的转换函数在 [dtoa-benchmark](https://github.com/miloyip/dtoa-benchmark) 中进行评估。 # 解析器 {#Parser} ## 迭代解析 {#IterativeParser} 迭代解析器是一个以非递归方式实现的递归下降的 LL(1) 解析器。 ### 语法 {#IterativeParserGrammar} 解析器使用的语法是基于严格 JSON 语法的: ~~~~~~~~~~ S -> array | object array -> [ values ] object -> { members } values -> non-empty-values | ε non-empty-values -> value addition-values addition-values -> ε | , non-empty-values members -> non-empty-members | ε non-empty-members -> member addition-members addition-members -> ε | , non-empty-members member -> STRING : value value -> STRING | NUMBER | NULL | BOOLEAN | object | array ~~~~~~~~~~ 注意到左因子被加入了非终结符的 `values` 和 `members` 来保证语法是 LL(1) 的。 ### 解析表 {#IterativeParserParsingTable} 基于这份语法,我们可以构造 FIRST 和 FOLLOW 集合。 非终结符的 FIRST 集合如下所示: | NON-TERMINAL | FIRST | |:-----------------:|:--------------------------------:| | array | [ | | object | { | | values | ε STRING NUMBER NULL BOOLEAN { [ | | addition-values | ε COMMA | | members | ε STRING | | addition-members | ε COMMA | | member | STRING | | value | STRING NUMBER NULL BOOLEAN { [ | | S | [ { | | non-empty-members | STRING | | non-empty-values | STRING NUMBER NULL BOOLEAN { [ | FOLLOW 集合如下所示: | NON-TERMINAL | FOLLOW | |:-----------------:|:-------:| | S | $ | | array | , $ } ] | | object | , $ } ] | | values | ] | | non-empty-values | ] | | addition-values | ] | | members | } | | non-empty-members | } | | addition-members | } | | member | , } | | value | , } ] | 最终可以从 FIRST 和 FOLLOW 集合生成解析表: | NON-TERMINAL | [ | { | , | : | ] | } | STRING | NUMBER | NULL | BOOLEAN | |:-----------------:|:---------------------:|:---------------------:|:-------------------:|:-:|:-:|:-:|:-----------------------:|:---------------------:|:---------------------:|:---------------------:| | S | array | object | | | | | | | | | | array | [ values ] | | | | | | | | | | | object | | { members } | | | | | | | | | | values | non-empty-values | non-empty-values | | | ε | | non-empty-values | non-empty-values | non-empty-values | non-empty-values | | non-empty-values | value addition-values | value addition-values | | | | | value addition-values | value addition-values | value addition-values | value addition-values | | addition-values | | | , non-empty-values | | ε | | | | | | | members | | | | | | ε | non-empty-members | | | | | non-empty-members | | | | | | | member addition-members | | | | | addition-members | | | , non-empty-members | | | ε | | | | | | member | | | | | | | STRING : value | | | | | value | array | object | | | | | STRING | NUMBER | NULL | BOOLEAN | 对于上面的语法分析,这里有一个很棒的[工具](http://hackingoff.com/compilers/predict-first-follow-set)。 ### 实现 {#IterativeParserImplementation} 基于这份解析表,一个直接的(常规的)将规则反向入栈的实现可以正常工作。 在 RapidJSON 中,对直接的实现进行了一些修改: 首先,在 RapidJSON 中,这份解析表被编码为状态机。 规则由头部和主体组成。 状态转换由规则构造。 除此之外,额外的状态被添加到与 `array` 和 `object` 有关的规则。 通过这种方式,生成数组值或对象成员可以只用一次状态转移便可完成, 而不需要在直接的实现中的多次出栈/入栈操作。 这也使得估计栈的大小更加容易。 状态图如如下所示: ![状态图](diagram/iterative-parser-states-diagram.png) 第二,迭代解析器也在内部栈保存了数组的值个数和对象成员的数量,这也与传统的实现不同。 opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/0000775000175000017500000000000015110656147020301 5ustar memeopentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/iterative-parser-states-diagram.dot0000664000175000017500000000357315110656146027211 0ustar memedigraph { fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" penwidth=0.0 node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] node [shape = doublecircle]; Start; Finish; node [shape = box; style = "rounded, filled"; fillcolor=white ]; Start -> ArrayInitial [label=" ["]; Start -> ObjectInitial [label=" {"]; subgraph clusterArray { margin="10,10" style=filled fillcolor=gray95 label = "Array" ArrayInitial; Element; ElementDelimiter; ArrayFinish; } subgraph clusterObject { margin="10,10" style=filled fillcolor=gray95 label = "Object" ObjectInitial; MemberKey; KeyValueDelimiter; MemberValue; MemberDelimiter; ObjectFinish; } ArrayInitial -> ArrayInitial [label="["]; ArrayInitial -> ArrayFinish [label=" ]"]; ArrayInitial -> ObjectInitial [label="{", constraint=false]; ArrayInitial -> Element [label="string\nfalse\ntrue\nnull\nnumber"]; Element -> ArrayFinish [label="]"]; Element -> ElementDelimiter [label=","]; ElementDelimiter -> ArrayInitial [label=" ["]; ElementDelimiter -> ObjectInitial [label="{"]; ElementDelimiter -> Element [label="string\nfalse\ntrue\nnull\nnumber"]; ObjectInitial -> ObjectFinish [label=" }"]; ObjectInitial -> MemberKey [label=" string "]; MemberKey -> KeyValueDelimiter [label=":"]; KeyValueDelimiter -> ArrayInitial [label="["]; KeyValueDelimiter -> ObjectInitial [label=" {"]; KeyValueDelimiter -> MemberValue [label=" string\n false\n true\n null\n number"]; MemberValue -> ObjectFinish [label="}"]; MemberValue -> MemberDelimiter [label=","]; MemberDelimiter -> MemberKey [label=" string "]; ArrayFinish -> Finish; ObjectFinish -> Finish; } opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/utilityclass.dot0000664000175000017500000000335715110656147023552 0ustar memedigraph { rankdir=LR compound=true fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" ranksep=0.3 nodesep=0.15 penwidth=0.5 colorscheme=spectral7 node [shape=box, fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, style=filled, fillcolor=white] edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] subgraph cluster0 { style=filled fillcolor=4 Encoding [label="<>\nEncoding"] edge [arrowtail=onormal, dir=back] Encoding -> { UTF8; UTF16; UTF32; ASCII; AutoUTF } UTF16 -> { UTF16LE; UTF16BE } UTF32 -> { UTF32LE; UTF32BE } } subgraph cluster1 { style=filled fillcolor=5 Stream [label="<>\nStream"] InputByteStream [label="<>\nInputByteStream"] OutputByteStream [label="<>\nOutputByteStream"] edge [arrowtail=onormal, dir=back] Stream -> { StringStream; InsituStringStream; StringBuffer; EncodedInputStream; EncodedOutputStream; AutoUTFInputStream; AutoUTFOutputStream InputByteStream; OutputByteStream } InputByteStream -> { MemoryStream; FlieReadStream } OutputByteStream -> { MemoryBuffer; FileWriteStream } } subgraph cluster2 { style=filled fillcolor=3 Allocator [label="<>\nAllocator"] edge [arrowtail=onormal, dir=back] Allocator -> { CrtAllocator; MemoryPoolAllocator } } { edge [arrowtail=odiamond, arrowhead=vee, dir=both] EncodedInputStream -> InputByteStream EncodedOutputStream -> OutputByteStream AutoUTFInputStream -> InputByteStream AutoUTFOutputStream -> OutputByteStream MemoryPoolAllocator -> Allocator [label="base", tailport=s] } { edge [arrowhead=vee, style=dashed] AutoUTFInputStream -> AutoUTF AutoUTFOutputStream -> AutoUTF } //UTF32LE -> Stream [style=invis] }opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/move1.png0000664000175000017500000003732115110656147022044 0ustar memePNG  IHDR ?sRGB>IDATx]xTE= ZBIAAQ@ATPAPTP^~E@{&M{I+s'evͽ|gޞ7sg޽ٞ={,#@6A Fc010iei30!# ،@GI#? #`Lű,FF84Ëc3Y&,0q0if<LY``0/dy4#0!a^dGbС]vfᴌ#`%?~|a&)Ҹs ]\N0C ((sT<=|z0id>3BI#SqbF ![d &Llj@VOXl&Yl#00Ps?c=z4 (^Tc `Ցƕ+WpBxzz?jH p #t&#`V:Ҡ{xx૯4Zjذa(#)))9ܿ_przw7$$ Nu_+..Nˣ|dxK?BWSrr2/.ˈ@~PP!(Q]tHM62ť #!hܸqF/F2epbDcʿ?;vDZкuk ȑ#Ǐr*ҿI*۶m n޼)w9B9{,nܸ3gb(11-‚ аaCX ~*'O޽{Aӈ%K`Ȑ!r<}ɓ'/@qi$'HZ`AڵKEژ5 #B:9roIUI&h+&dذatDH֭[ i"E~C)|F-DTΕ+#V' ]uµkא7o^L+,Փ x$sΥJN:˗/wt-=( FЉէ'/sń uԑ:HJA{: )FWZWʠ;wt&4MI+6=_3@ Vie˖-Z:uLYfϞ޽{8ruAJ5jZ17oԇ)cNF lT0-Oovsxv> *ehיF,8gϮҹy`ŞԩSMnGԔ6n)CVt3Dې<9#N#+me& GUn#`A4,.g8"Lث&F0iX\ΚpD4WM`Ұ 5#0i8br "aAp9kF`p^61DIÂr֌#"ሽmb,GDI{X& Y3#* Lf& GUn#`A4,.g8"6i#Tc'ԹS~wWDGWH^ e6Ε{)ǔJB]qk]y4QQ#xFR\"=sZj]T^=c(4|2$}!;} w݆kWDGE#O<"&SvQκ ģsSҦRx꼔/yJ"9!Hʆʢnҍ6IqXq9\:ï\>_ ޾ 1=(w/ż[2zv| ͛7n*'%w7H(ֱ0UC[xf95ë]^Nmq6Gϟ3'QΟ0t}/'CñvjlرC F2Ӆ8P~7N|X|;P鎀ªK萀s/ÑCz^ءMצHcX{ v"e ;3-DM61?sZ4k7~V O3Dj>hi+X/>^U@T%ӧOc-(Q]-;vl=a#>3mQVh zVFyu6X=i0nZN{5IXyZ͔Ftx*T¯& aDX"-ˆ#pʕ8p@ٳ05o,Z9K{Pzao87\kթVXg ?5)T46nވ%=_ܰ*m6x$&$_;"*_KNJFB\&=[1 )tyb|TZOfKU\cbbľh6A[;e=:$wE&_w׏@7`gd > ZkjuG٢a2c*z}Փh轟Aņeڇwۮ#Fw D>~>iF4سd;G~iWuelwD9E{RJXv-[M0۷O瞐Gɇ1o^SM&iNhOO4!_X7hSrј?pt?HJHBU~z3ޟiuh'lILW-GNl w.ޕ w"I4}.INNٰk_HJLBח=eD.O/\PZqܰH{uFo|:Vi;j3]*]^)ԩ+E28|0r􆇗G z#' hhXp邨԰xqUT7>m)L(WՋcgzK1Eg2^D}N%)Rj7 WwŊ q=(e根ު~jH`ǁ˴4b9_J4|0M~N\%s03E6l(?Ii&Ԯ]ܻ{.Zlʕ+bŊMgɧe~PB(Qt(Q >NT~-z_A8ǯ)+j!I2W1ax|g::#ˡ"4"9r7:w%n7~]>w/ ޥ8eE#:4S\radTC/?Eڥ+^(6RGQ i;U &+ONP݇rxINNRAy\h@RGa2  O$||)MB A;[Mufw#-s_{D⭦hR] oErB1=ӝ+qXr!?ONNNԩ֬YICݺuK54hJ,;w}]ϟח%+WM6ș3'F`)7!߿#M1hdVș; $Ί' i[*_ ]b JT-!N: i9TFMz5#Y ]u)uGf$ƸxrUaLTFP.ݸȌ=ӓd1ex:igPc㓰a&y>|htQ(Blb()M2Pnæ3| w bjנU[ !#qhHKo]Q@s]dV++sv~^MntD }hjrA/"!޽{eh,ÇիFI&I 6lRaɒ%Xt7CDƍA#cv0{z25a$">B+h*B :c?)2S)WvvNHװzjG#m!'nܸ}Lʜt2ud޽{KBT .dDDK+V0 N臾kqʳ@AӿZjd4k>V虸sbH"EN4E%+NMۀG/ѼXL)#;>N3%cX}As0cv5sǘv"^6b:OBP,n@>M0s|K%N/ްH"[@1$y[/fM $BִB7t;(QS#R`c֬Y7nIhѢrNi*hKQȡ @4...?~ d~zCj΅ZkAzBXb*=0NIA#RS$:5MpF4-#ِP^)hdS`͵N3S [j|vjpdTםQC*%E~1? h.h} oۧu#%=,i7^`8sl{o9@W^hQ|rSLTPAl׮6mÇKE& )h "@-VVhڲeMQW^s=o/)[&!'tëw1$j84bg”gA;eDYW'zIK#=JNJn<ɐO[iKshy,gZ˞$9GKԱ>$46aPV)zCgP"֐4TJthf͚ #74 ~\hA##Gȼiv:6%/et\z$7=Ǥ7(|auҠC$a0i> ZV%Ҡ R4X#e3  +IB8HO?N}B6n܈e˖Iť%Z[5ݖڦ_ [Li3ys#Î;ar7=H"`a,Ihܖh#IW&,Z֤%PTa: GkC۔[ztgulj'`ƌr9k&)IЅ kVDhUоk׮ +*L`P ]O9 .h{,ډbB6mzM8Qn1ϝ;W_0̕Adr8eTD_ C7}R6X}ŇP=ʲ(}0FGTAkST5̬vCgn)SqԚftऩmʹy&8hk8![o"V'e"^r5/h\i?E֭믿J#m&alobuVcL ܬNjh5/Fׯ_ǴiWj z$УV|.MY!d!.6Ziʱ:iT.N-|M,z.s`$&``l<{2>Ǿeq[S|sNښ ,uj7A~|$bB)˿ZR_ǴϷ_ʒ4P<#@D8pjN˗[Fgx󏙭WOl~DhdfmTzlτ0L.Ȫ-1%!܊?2S~9\7ku_uSgrVc !; {ԗEi.gc j-2\LfIGKdC /hY";G2+ۨ%MƠ&訕cMӏ; ċrAad2E/2EkZB# z׬\S(试J!PgER{WcmAoswPJT#$so yURS+-Y/ -|!KNI[)E+˕#0d dF!dҏ?+kami5ܢCA&-ViP6lO_`]呂t8Y0<џ%|Ti+ar_W޻y6ihtGbłVL5hLG)\|_FLJ*`pD: j00PT +j߿9VaF|>`J&H,ȡGbV HȈ1Y9C Smw͹O虽sڵ6ᘹ iЏO>wYshzRtGhI# {6vGr1XjmP]tF6ݮ:뀰sŲvx?R"o 9' q49>*'b ) @*MS+S-Ո/jjK*G_LO2z8<*4*~O2ʢ=_&+ ^?X9d_ *.bKXbPdkd"̠4DNi:W{s )dq4B!{V_b"c6VXnw0- a_E*մv8 )ȵb@?j?@dfCzVWkˌ;b '6P*MOߍ T92=QZKK cqzdtm$|U,=)jA}p5 Xo5B3/ N _L!Y;Ѵj3 C_*iPeɰ̢y%Ӭ.Dq~MS~a nOgER6>ͫ;}IƅA.]4F4~=pU~|i}oTZ5 #BY"g 9k>o q zjժq#;8j@hfj;a@ YRv8GL*mw=m#Gs{fÛm LT[=х@1eT\&mBzQXK`(js%P?y=r:Zi5Z'iSܗAux ,߰xt% 1hZOVH6蔖kԨ={`u84o?/>S ›D:u=#))R(";V&xʵrr5Y(ϥțsAr<E0bXR:jpܺ'q]Ha4mA?^qܣ#ZW}Qu坶A";W ,reˡ| #,#p-xھ~;*Q3AxtNZPJ0l4hjҫW/Yf͚!W\XjNhAiI/_>Iľ}@EƎ-Z]GFm#Ϡ/_|.^3fpveРA/׾A# M 6 NN/x`4\\R]|yd|0 `W^y4 "ѣXti'y敷.\zI@4XF4^MKoTrDѻwo+VL׺ukyU* "πI_AQZ-[6% Fl4كGk׮/5-_\N?i"hٳq-[V߿N#`VARFZx"ZN%ݤ4=*TQ 'q h[m4جN)AI J# F069=1_8'F07LFc& `n#`n4̍(88L!w i&Q:v|s%/GyS:EI>jlϟEEܹhvqt qe*荜~pvwF6g J/R: W$kEKxCV1ptC\XBuEKJ|$ؽe+nhL<'778!kC&^#H9H{M)"ULNcE²g!W'wz<Ѩs'4nJ16{k ޹k'] 7WWj' oQqX7r-i^}M:6" JWGgx37|S~sy쫹ȄX7h tnlU4BCC1yd9=D;SΞ#j[56gvÐ?HFdcQ%iz?1O=W>dx>zE$%b۬y+Q5kSN1Q4vh~8\\QmUW3NNJ˵^MvNyQtxoWwcCcVlݑKQW MmJAa:srlHgn CnWҞ507m6GfîHĉ=K [ 7+:?4 7ުop}0b_aJ9BD=|L_<5 irڔ_~F AoZ{Փ!T?oÞX 3]0sNG%3UVlT(qIp IJyFq[MmE4h>Q})" O,ߴ[0<\vb娑qef̼{c]&ťHr "Ei]OnUCz7#*t(g?Ok4{Qd}^ cZ=j>OV %ר(ڲM4a?ܽtO-X>9S6Lzk 4}B&pd1< z<=^c\0$d9(_D-]$ Bo?@EѺ ,'¢PtA ?^>^2m'EJ?Ȱi xsloSFMz{**2m>7}]R WƑ#GPn]]QfZ* 1jۃ{t&@v\{|߃h돾KSB (.9]0+so]…'V3JLIފ[Zߦ^un쁢EZ2Ʈ=^y@N_&' wX[ A$'&Շ0x{i(^Kћµ~4Z SŻc֯C,o0~>4Q/栗^îq(\~bf|yFϺb0BSήE ^zUl]+6H/]6]˖S?* v+̅/.lZ I;W A{N~sU%T'(E]4I6.Wm4ڌ2 [Zݡz){+Fآ+nһ\H5|=DIP\a-K#J(WDVu:Ֆd+_.}<PeU#9W$/wVS|ͶѤW#HFVaqquMz{<9z(EŃ-S:A/; (k1O/R ׻`q%w/뱑0NjʋMЊIh3샞baf]\+ON*z4#"E]⁣O(N%(T*e)F(|\҄Ӊ>pB2-ӧOxq?F =\g5_\J=U%ƣmNHxq[xzgk71IٳgU lӤA> ӜӔb7KQK| 5?GqQ(%t> BL+}{aQMEGrtRx}TM!ș'EiIRؿ&٫\\-ʨ ]|Ԗ6R*r7κo p9UIti7]#!„^ŜC˟7a鄩W(;~ؽ/DAJORʞ" .ۮ?F! aum"]“H\*CI Jx,{ 4.ۼ1xa%tOuݾp1zZ2M4"ϰ-nnh'-@ә}ubEA@6ނZBwAkvW%X=i-+Br%I7,|dP>>;X' wUq8TWÿ3HdϒB|#H5;V)iX!EeG tݫt| ^ fV*E !jJ611ֲ`M P!?NϠӲkhk@$aPE(/Z5ə^Dё$ԆoHƿ=S|^bmE!M[aę^hUاR?.mX =5%T(n?Uѣ1m4l# WW$fWw=<@({4kcNW9ï)Us/Φ' %(Cr</1M+yUj[&]79"`]v j[ir$& F@ʅC7ڈƩ2iҠj}Q̈́4I H6MĨjK!# Jڿ & bTYUq~dmaaӫ'(> Qw]ZfHtuw)ШHlL0w}M4!`#¡LWJǤ\sät(N/vg+ձz]j}LFlzP5 dE>`E~ld+B3AYcmAj# ZU%!BG呆~l8$ ##;_m6UqQXժުCnd?i w9H#%s,!#R{mӤ,I^i|Dҭ~0 LB}an0g{O|P[gYnDp6Ģi$ #BjM;b1(sPX*v2U& OMg{Vn#lw $ 3~ln "fэY P|M03OV-d|xuxTEVt9bTAAB ֔x9i8ɲ'̝۲dM42hy00. h Y;'`_V:) \y7܈H|cFD5呰X8КE칼`;f3 ʕrF%v#ccS7HUߴ- c@^f +}>AB\":$}z G iWQlsgkJV"}XP;)[6XA4i.\NxlRprxRIhV3SV>Ljnpqsgfo4*4(׿!#=jrVj,QP&p3KayٝQbEUoӤAȴklHXZ Zw{헾Z{u62XL?]K/ȣRYrlMkGI+9KW:p{)ժ͓Ff-L1ܒ5JyhH]^TRg Ӥ%!b!%;Wkw)#TE#]G}[g/CbJϣg4n⃬^ÎHlA&{ݞkM+B b%v=k4|>LrI g#[~%HR>w ]uƼO`drB'BS)lS/Kiw~D,Դm6_@+ulE"qm4՝:v¾{qu~&O>XIu2DzJ#'o%?#~*WVh|/KKlT,mqÿsouhXX|$6;KgQ9l?fh 9.n"<!eA#:M9qA+3BoVI4hR]cmDR#жD Hɓ7Cl< j$ {=h/ض[g@͢ѱCG!J{C/FE opSƒPڋKc0! F8r.8$VHLHĖѩ~g ~/753r^f+ huOBT)306mXB<]>>?|BcKbWA-S#х'pubmè.41}ZTn]_s=>?N@|2r) f'?ؙѠikݑ!HCMA3[wרZH[Oo'ajU咽O ӞE@xÍ:"bMm.O@m!ꉮϟvb¥pwEzť H]u="ūǮ!P4c~ޖ*fGǮ]Pn,e&ݏqP37|w{[Q8˹/_GJu:Rb↸8DG;7m?u66ǰ1_N߷{Vݾ#0)cccnL4_j1B1q %>Qz*8O$FN.N>ŊQ$"jTѮICi)5j$bbbpMܿ_>$Jr 'R#o 800U=f9r|O>;6$O,G.K`}0iC3N4t7F@L#& MFЇ>d>#DIC',|`!0:` d}0iC3N4t7F@V޽{8p. #Xc4Ͷi,#`}`rBei#4#`W0iUwqe`P]!aWŕeGIC>0v]uWP& kLv]\YF@}4#`W0iUwqe`P]!aWŕeGIC>0v]uWP gIENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/normalparsing.png0000664000175000017500000010016715110656147023670 0ustar memePNG  IHDR`0sRGB@IDATx`E_%*H)@"DEE UQTz!;rKn~۝>7囙t! B `HAB@E@<B@! P)B@ ! 0͛s='B@! H k֬Ȓ%K9s?rҥKgsܾ}ې_yi~ԩS.j>/^鹒7W2?﨨(\vp9lݸqP8;{, ũ߮<;Fb?S]>|mӶwJ] Xekˆ2>|gm`s!>>p|iaǕ:Im8Owu9]z˼]y_Z[ o߾;ܳgώ|9 LѠ9rѸExp5z;wn$ݿ+ys/wտ'~gȐA5RÞ%3?hͫdGg׮<;Fbfjù+W狄/wMZJ!`H###UG ݰ0ʕ˶8S^`^`:g̘ѕ`tbvŸR'MC_mNGv?5}_w\\2gά)k{G|:3v{B@!DnH΄B@Ta@B@! |ay#G?wZ+e_~٩_`V8ٿ޽|!4/+̙3Vn޹s'N4NΩNøq㔒Mj waX?ǜ3}qQKaÆ_{hqŊlY[}bNi;w.n݊~ϟO1Ea)q#f͚ 6`=~sf̺tR̜9Ǐwy^C1M ,o }suxb,{~͙Wʕ+ݓO>02aӦMJa\f/SLXnXv2a…|_ߺ Xjj7|cV*ޢE hV^K,QlN<EQcnlmY[RqrϞ=A-qwF-C]Q*T)S9͞ US<[lA.]@PsF_xǏڵkUA&L^|PcR# Bׯݸq]e{IJ p|\V3 Xw|Iz饗Ԉ;LMa~ [ {Fzz<|( z ฼ʙ6tÆ -ky_9/QT22R>k4 Whlϑ!C%pdsr7JpΌ>:l2μ+Ju8Yv|ПH$~)ۈy]UxxsD# ǎsۜ9sN=`XE?~;Q$0`@rG;6,<ݻltU _l<3U~j޼yi#q$v1dG1]裏0j(ˇA־}{T^:t0u8?<)_lL | UNc1|xTr̈af5k?^&l݈IM/(4h`$C~o׮ʗ/uj|Sd˖Mu]LتU+ԬYӡ80UTQ sg?݈ሣ>'1|&jԨrTC\̤6/;&<o.;yڤCl9Ϻ9;H_|, ՠ~<(СCqA5g] 1bhCϺ~iGc9ov6Jm#Ӝ9sԋLpmmcѿmsE×5X}=ؘ< 0qXÙPm lٲrҩ]61b85j"ɓ'{l|)0/˗%2\9(T gɳQ_&F p|ceˆ #0ŊSkQ7n NPq̤{ĈB%KT#@s6M4|sљ3i\:{ժUB  裏M6{,#yзҩ tXgnr}3##Xz]*U@ L 6>+F0{lCG+cQ+߃\3g _k&MXbG>|8J.N {,CKC!]`AuV&+ۈaq%=^? 0PgA|ҝV?FzU,{aswzz=x| c=sѴiS5!9#}N/:uꨩ"P@C۶mS ReŸ/^<]wbϔݰbP;XF?l71: 89nʑ1vԆcI0gy2̗_~>|gL8}Q gpUpԙ=jgyqm'Neİ\F{q`d;=u;z/{Uwf*VqHoY: ;6Cbo*湺;M t}.64ĨݩS'=Wwbltp/m۪d[n hQg&et,Gػb_~1EzF G7W!;~}Y0:9FG4ȅp҈Rj1*ra~8Ğ; ]21b=r3N2k7Z;_!wSc$gֻ_v:9JiP#7@^Э[7#ޓq>vw[덞]۹a/YO*,G3M6M sgϘJQ%S~}רQ#5gV+?tw JE/N5O=)e hC͟6F D>{{ƕgh^lIm88++kCrmvūޛ0t^Wj6Q{'F%ӆ3 >|ލ{=\J5e5_M3|СDM6Xpڨ, GuMI9|W2JGѽdpWS"Nz*|ƴ}: ӿ7|9z.3r:%q^d|̃=CV-Z>ZȻpϝ-1*E k_4l"ͳV}QCh\U@1̭JǛZ9R+|r*W uܩ g+]2ZGSuveNߝ+9ApJrd:5ưpB9(*Zes(PI9P֪USI:Bg p)Y-at妇o@Ə! @#9i,!my_(A! 0F 'mr:"pF6.{.26~8\ͧHP[sTF+ys/vտu~8p->"Fq]/oNU֩+ƕ͋maNl"uKX v+e%Fz';+Οj䝔50^g0єΕz\Octu:5/Y;#~|9Mx%}#'G wG^#вeKIWBɱdXޖ ! s"%B@! l p%"B@! w?@ɾB@["mȽB@?' +P/Bw["r/B p ! % ݖ ! s"%B@! l p%"B@! Gyy%B@h.]իW[X f͚]vi:a*.`ԩxQPT"A s ֭[[NtIjC Axx8xB/C!_|hР,HzB pJ*{O}>c\ׯ_… DcJS׮]K… ($oBBB'){}۶mc$FDg%>@@(Z=zTy(V .&Mʕ+>#<={k{A*U5kV=ZϟΟ.R͋aÆB毿ٳgUBӾ}{L6Mo! \$ E`] Ə9>ߏ{LO?Uɓx"rέztǢE-[6lڴIA۷cܸq8u"##ѡCt.]Ν;1o<̘1Cc/\rZSBXBo! \$ u.B PPרQC']t(^88<_lYeO W\B{??hBBB0w\%իW՜[gϽ{jgݻ-iWXΝSqQ!FpwCaÆؼy>|o6TTI S`ݬY0|p4jٳg'O{VT}WZU}8pիWOƆ E'A#7B0'!\fW^X~o(۷ozj೗w ֭[@ȓ'N8P鎂&,,L9sF S@9wיI!8yԫWO v f n[Mw6S=SLhժ˧ xsoQ֭@]ƀ| !`܍q_B/Pp~gIqMJpqqqmonݺg پ}aٳg'|ʕ0<׫sS9s `Kg_WhcƌI ' 7B@H ֒%Eo(?駟бcGd̘ai v/_>bq9xMA%K(#""`a}縼ԪU |6m9??}tP/@:3 !|өb gg8rkGZJqLِ$@9J)UwU-As#C xꩧ9{že˖jv۶mÔ)SIT JMJ9O;R2:>bL"^TALqU5 *Q;]m"B Ȱ|թ( p8jSp_ͣ !vsO;CA[sYpFNs~^' =wC)HFKlww`dBWR !pO>lhvb|2,u,% R{IrX 呫) B]r&b#B p ! HN@{r&b#B Ȝ_W_ewATT`EAѠA] _,\ҥK9|޽C;k7'|ڵkǻヒ˗/[7Q]|:`D@zTPYfjߺuz0 PB7nh,C^-XoܸyF=hÆ SŋWg|<0?u*;uꄋ/*)SgϞu VB… ޽;ΝaGV\6m8_~ew6*AM4#<^{NJ+<<"7ߴltӮ];L4I 4,W׮]㏣dɒj~{nݬ^Zd1 ƑZéMJq rYC=6hڴioF]{ʕSuc|XϞ=?=v~~gSN駟VBٳʍt Q7챲'o^^Pr`/T~)Ν;?'N@BTtd:2ĉp4C9q V\ NC>}cƌUY;w|lذAI0#G_$‘ o|rL>]e:'ۑ#GrZ :tf  K}-p^BYn13 ~֯_]l/_^O)n|jDСC:;ue̙ի4G~m۶i B@Ԅ#EC\`ACQ7GA${4ZR=dCpU66pPYh:v9#ǏLj#,^ TCpܬGA8 ˗Oastgyg5 kƣ7)}5*ɜ_#q[WS&ȩ00ƺ`N u85։#ݏ| o7Kچ8LJިm˾cr 9m-|b[cDKrwk.|w`#HB MSUJm۶-8S`qmS:KK!%]Ua/e@ l<ϝZ*1{/YBP|9Pr֕-[6|m>mv?35/N؆uy )ig# 1ۻQ~\Z+;j,qą E.ncDIԶT! seImSbb;k^Ű͞ij.U t0/15e\BVZN ";v,7onH:Q (@@{IP!Iq&3=x;w ""gΜATTbbb>}Ҷwbb"ҥK> v4ׯmSrsah4Ϳ9s"k֬(^8Ñ!Jɵ6Ez$%GD,(Y4 GM,4K9OtW'&Pk[u d h`(8i6Q#./)͛7cт/Q,o,:6-O@ƌ!~IVT s ˾#{Zj~Yɴ"&~AիGѿGE//R&fɄ{VO˙Q\C M) !`z&%" !8p#E ހZZ1? d:ùslȽ % !pitx5Z[Vh 2.5\?Mܼy3+H@}V$OE=& گ{RU ӤnIơ bZ,w_f?tFױu,9guK! O@JB@X~BOMi&㧮3 ɄS]`xgʵ$v >y~cqف$T Gng! A@;(JB@ll,0:WqqB1K0}3?؝$Dh 6֜قՇx(s8rh5[MToк ܌F֊qW//\ *H&A3[lA (Y<3)s|8i.93wEqeK[c/!Q0zjGăЬX&,ӞD185/T_Guͽcn0v)eĪ {̸,rdOۚF5ȓ8x%r!9DUb r.E{K¬G5P@WUQPNMXd:^ k~tg?yŝmApDݎզ *Lx^,]/ =TSۂODFmIM`úUVB,"B _gRVi <%m$͍sZ[7+_ZBH/?y68X-+Z^.Sts&7oIvθ%.! # ?r%Bѣ\:WSRyYHR-)%wow;.eqEмA|N'?oV|8k3m;b;_%;nʂYv+\;8C@@+!NB"ip_%|o:vQYqﯪ{[pN,])_lBeP0v;/U{@N˦n2.\TBKk4TB]!-L3 &6^ůTl #1B@G@z汕@du sOk-'jCpuy1ȗB7 ]|-nŋ=Nk#oEѝQS~^ Y? 6_H6DSK{%,s:uf7Br$F!b Cv Q.Z~zϜGtsQT>sԳ{clSgbI++yk Ќ8n ._Z*zz[]ZJX!o ~ŇP3Ӹb#cWw<6n? *YjcτiZ[ɫdIq&'@ =jQ l,Y~ڸȭ@@{0Բ]^j|Ww8#y31h^$f!w(Dx96u㘏w".]:m{ܭKI^^=~_R& 0ɜ?`4!A8:QB52/-æpS[K.&93ڒ\ۋ# =wB@D5H&@ \6Hr[Yf-mqIDB@' =w\V(ԥNR#BD!B ;PgX 7f`ϝ1 FlpwIGXnΥw;PJw7”B@P "*; sg6D[!V2"^vsQk 44ԡ8!v"PbId˖QgDdɒF.p7&*=bŊ!̝ӑ #1B@G@yl% %Pti=zMGSWEFPP!;b%pwIGK gΜ(X Ll} 5j5u6\VwwFiX鄰!3h֢ !n"MTƍc8D"<%uG2f+ ! L& d}pȘ1#:vy _.lJ#ݟq[!  4owBc冃AEQrST( O>;hH p4qI/͛Ͽ2}UYێ`βxPdΜ9/" [%ݠ!PBǟxo?A%},È`ASRP! DB-Hwd{HL=9؉_vbuȕ+W@W '|w_S@`~)v NބG/\9IMp!<> (pe  2)yB wxkHl޼~by]^02f bފ{NaӸjժeY$B Ppr&MaÆ߱xp:/RE"kh:ҧOt-ɕE۲jV~'hGR?11Q4?V;>v rSvjy -lӷpmTVy5jP~% ݻ% & ϝ;wpq9sQQQM۷ocA`csʎ]56!!Iyoįp:u*]CSHpٲ~bG *![1BW/WjBBCC9y~͋/I?~k'])SFې9"""зo_+ !zPzvRJ`ҥ;w.s`g9^QgS, !"Qbn%pE+ٳ'ڷoָSkڵkgALLLj0B@xw–Q/2f͊ b?O>]/F& ݷGr̙+V(a={v!PdIL0~!n3!LFxW_}5Z>%Om۶xgqMG^/ 䅀NK(4lذa}S֭[s !pAx5k`ǎϑ)S&@>Hi/[nvnB;D{?ݻw'+=FzUVMk:t@=@?j }j_zI,Iصkqر$@ѧOԩSG͵'q*QKl ]d]-! 3%!`sYkåe'Ng}wvkj3˗/5^F{Zsp7*|:.x%e_|F .57ӸqcK`GM9rD]J]'"BD{8D~UGo.%v|ӦMofĉ(R{9a5z-[5~]}y!,i^-׳sI5PKΟ?tas$B/ ȩp~Ymi#`=<}i̟??ɡ0NRC.]O5 #+VT͛We2<.>>^Ss ,qplpNܷ5X[_ۺ~M,umڴɓ'goxvg[,رcpB\WFIm$.t|>r̩ z[D$P1Ǩ۰"Hl(Y }D!,P@a zGt0kB] =\mddE<~EL\4.EDtgOEj:hެ5jԨ(8)=>ƭiS!փ^'1!/ɤpzI8-6oތ?A‰hޮ4Au+>sOZj)zH@DG}}:m N/UGr}-.'Khи8f!44 ͤ+q :L&pNTnzp #0l8w DLx[i߾ _yjWYX~G]Rcƽ m#]p ˻K|{{?r {!I~2ýMb5(X;S߻IN@z^˗/c|^/EbﴳcBq :=&Oc9ޓɺπ* /}d~8U0g×ۏ~!ĂJn wwqDj|=g$Ze-kDlq"܍&~: Y[R '+66WJbܸ~Ƣ}3;>>ׯE%onߎ/ݹ{m?w["R+!:3 BX&]IͷQ@0s7LWpmtn3 N\uGD%T("^DiP\uto L>uZMD}Ѩ0H\`/8RD4u:3 [lA(/ͩQ(x-lNNB[bЅX(/y ٲȥ=Gbc0}<է)ꬆo޸cíh0v7cG/`ղ]gv˫`F/,^\MEep+4<֨yFF@kķ۰ ZqKj;.pfmMA5~إz=yggؘ35 Gep9k߶  9rG ñn/iPXoO]lq"܍"ϞдݣDWL';P@7 kVqXJU:t ˨zO^OOr[S]ےm﮸xN"''OUlDLBLѣ([1&wukT7 `|_#o=:_yfC\\#]/gdOguquyE' 8+!NBYݖҊgeH4i2eʠzwm{ mSgN_Un+UDKedr*n ˒m)Rs)[omxt57NՊhضO@59}9K:uf~o2Q cdYcė F;C~GCRlPMn9RiSk~ɺIJ[Ӕ]lkdzzș+ цUcbެ͸|‡hіt}!S&j6SW!`wcė dȀnztRsi5C^ ZЅsU;]ZM!!]/!Oba6$ɍH%'-vÔ ˵lC -퉆$$_t3ćڒ;O|$"ྉ&5,'M$4j1S mo jTOgg-Q>y?]`6SW!`yoc/!yd1 wf2Y cd'Aqqq,&&9Q-cj?9sk[%B ZrN! VL0g+ν~rhѺJ0cpXolw2E cn QJ ]߻SN]\1"܍q_$ ])6[K{ʩ0F@1N˃sDSU=NJ$#Sk6SW!`wcė (4)9?WcT! n % ֹkǦOGr-YFLl).B8/Ȕ) CCd=?efO9uqp7I|y@l9p;*փ)WRQȒ%i)5ʩ0F@1N˃+'ny0ŤI0@IDAT9}UaϻzAFfo-M Co姿myl+$d^,\ҥqxeu8٣s-1zxԎB 7+Ի~‡W ߩ~zwLf e^"*s̙ ġ<_l{L\4*3vw/w_QFC˛m޹O_etKWӂ?l~ B "܃-džG<KtϜj1VIx~qh~~-y|&bc IS'z7SM/$D|g7nK-@hXd㱗^*<=E`[pEg\˗urP\ș+s;&of9$ =8J1cFt#y?`ִȒ52iOm^ɇD(̖|=ˑ{̳>j9}-wY'epӏ~sg9 6{Ow[%% =h ޼y m01n4o8_hTǂѶCMS,wbƨX3nsgx?:uTq]anY_43B2oVY$ Q~YJB:͂ѱ4fRjҼ"^*ϣ1[JKdn>{0rDDrX?ֆuxoXv8+S4_{3.5u~͋_.m״Ϻ-znx,ІMofnK mx9} snѵ(Z.Rac獃pm?J5r]ȪxIpOCn|@ bƇ;,cb0abo6gǺ߇g料Xl,_oF3 7Jߕt][V ,4ǻ#C-ši[J+] ~N@NB}wM{1=X|OhmP359|s?ϗ s;Īk+W.33;J( 2da`Ͻuѩ"4`AJ@{V?=wޞ;2b_pyCŠ{chȑ3 kknF02f FwĨP@;cZmEl[?xDýx_iHibl$n лwo;g47oƂ@D4o]Cƌ!ɒط4^3 Gon2`"V4vi VE >&?4Uf M3x/oZ&O?p/^QFy$DIE{f w'>>;mXcQh6/Y8 +v-J { ^P/mr^M[km}o}mf{7 ׯ@|%҇NTNĹӑZ7k5j|;%*GYqI yXOP?y(=r3%As?~gΜcѣҥ+ڷoMHH@t>JUKЄ{S m={T) Wi2|B|$rfˉlUb6񿟢#QQQrH( ѭSsԭO>G,O?m5W bBCC9-[`f6mBժU fƌ(Q:u5?Ɲ}Y >\5|an_Ӡ,ٳgѹsg?<֯_0aaa8q"-Z_Ulܸ1K,E V"܃ ,@0ʕ+1bdsgPe˖J׫W>R K!,+WSOӮ]b֭JdaPn';wn̝;SLQsmڴADDӑ7puI3Cgo~Ï?ɓ'#K,iW">y*> H.Pdd$^yt ͚5SCw_`ZJg:*ROoK}JB,""+Ujɒ%ꫯ֬n/D|+yD@Z pO+A o:*:;[F2eTocǎ+ 'eTlڴ NRj& ݟk/Ξ߯&L PBAPr)7 pc_~ݻwWJ @LL7$i pw x$YfI&`ϝ={ΓKZAN=I&y@mӧO9)/5Dx"}QW kתՇtE)ݼyS o۶͇r'Y pElD`j?^D3z)7K|Xn %!DrQ޸Ƙ=u[j6iԨQ:ٳ믿ƛo_]-lKKޱc˗/cx}){!`!z-u43<}s  !  y+V ]vjڵkh޼9g%:^>Z>jsrܸq5j/^"Ehn%[B 9ïYJR{0pKd1BWp|O| T_;#g0ԩ6mADhb4}{聇zH]^/fU$ jO>ÇWvo8$Fx(y~sNu4K1b@ ߿vΝ̙3o7Q>}:Ɋ+pA<J?W! 3.e˖o޼?)nz0w3Q_}^|ETX~-J.Jop߼y38 'yҨTv&TP6@|L.tiTVͧKBs J@FP|Q)hwǶ@t-eCv"}̉pj^!8MhKǏ֍իW)N8ݫL{_DZ` -|:J&)O޾ ,9^x?pjnذgϞUF8%#FE@4"ڵ u%KgI;Qv, ?;yU"4w ]v7GG\;'%J ?;?j\=+W.5Lφ!`f xfQ@p:Z{ѷ+V#ϐ!] a̸pMog (ߡ46݇jP|xk{Ss3p7jyutMeMkךv=Ƒc寠=L7i7*qoӆ8|RGީ~xuK(Y5x]%[C& ʟ{=۽{wT "܃U~ 5kĖ-[zj9!!*\*x._!::pwzlܬmShyvgԆ"C'pB˜≋ύNBпƾ^*UPZ|9jv1$ $ =+"s]{6<[hj4.g:*r,;EC-&&TrKwYC&:P嫦93ML%y?y$ 6T\t~ܣ|hYK3gƍՁ3Gatxar5{vmܾy۞R h ?FLufW^{n#Y?B0'sw{Y|M'hE_[nInإ_i| o[c?y{7oF6hQw$ND\B}q+RE+UK^G&pSWЩD/ ; 5vo9~j>hH^Zu7ALl L{m%mڿX5\= 3U؅cєЯx?;K؄6o;} m;Y[oh:*%^pMe6bl?d3 xy|GڵՙrLJ ;DہV8q"7o_ x ˇxx4KBaZo/ӧS}bj8;<܊o[cqy[8vV-ۥ_}Km*fHKش^mPOIV,3kw8kFoMh\wG<{h#'t>} F.'~L S`/#g/by.[ou1~8mB_Tߍ/^;K"g9Pm&3Bz,qvQViqy /8ˆ CD%v϶F\P2Ӯ6lޫٷ- jyD'ao ?!TiR3ggde*#^]%g1gŠg!`w{TԎgO[dr'psv|!$[L';P@7 kVqwEZ臆eT'hGb5S*xRMn޹Mj]P GДR\<,9Q)Y/ȗCy )nߡ xb+_W"qebxQ+ӑ`.DY-WD%_MߦMK/Ç5/EnBX֖-[ߋ3-&,)[1aר,:uo⫙0կ5Emƒ^?*)u*<_wu@[fMҺ_O=tFraaysgQgLsy}}\hJ0:i%j~S;=Zƹq.o\σhJrS&E7n;mN엨Vrxtj6Sgֻ;^=בANwzoexh 8ޤKT \n?p|U|{Sm(~`HNS ̇'<ϔTUVUKO _Jr`HD>K@N٪17cs=v SfU=lwsԇGCRX2Bv׆ˣ ǫüYu|s|UMݴ<\9{U)eFMj{Spn|ݩ;h_!s _*5:cbbp=S/bÌaye/ ޣG}`n! yBˏ6*q*TҘˬ.[jK}:y?ylr~rhڜMT_6njɟsG91 o4n۷ǐ!C\@ǼI̾L@z\;w^ԭ[;wƍZȢPrŋB]|LwBuy4nǎXd\ dD'C_?σ?P~}*܊B(VVPgV.b͚5ѡCp^H_ܧSNjs**TȇslsLuT+ȬYI]fLa#PiuNp?+K?v$C}=cx:u?0瘸a2gɜ">Qkwhf6:~8ÍAB@z>^ׯZo~hȖ-nGxx/{QȒ%iȑ-'xX0ԼZhlމZk*NZjhC1ػu_2f[$$hy.\} /pʾ~ڴ>v7yp}ݧzԎ/ZYhѬ=6NX剜y9iDTM>y"ozEM8M@x\jβ'Ar'tp]ye⚄JrΪsJ$*ܳaRƍ̙3_]f+T\^}Ȣ=TRyvBASr+ʹWJ*UO?l\)ayљUϔg7{1.^+u{U8RN;a(ZJ>*dڠ2rHoFᶸU6q4\"=m˞XfͤUVoJ"E\f״اsǵ_H~9xX\;KcWd܅RhpA;P>~YdC͉hŬr`AY8QRoCΝ7_~#pC@{=!GyX+_,`9ppٸ$yt|ٺ9>NVp^ӴRTa($NjyK~WQ&KQO?4\n/^(M5|N3|TȨ$Æ$6XwA~'1cMp p h\KF!#Gݻ' ̢Xbү@c8RFרaygtt(lڰ[V..5?tH֭)!G~-*o铴6?cl,I*]q\NAFDqPS4W1s{ߤ?R5پiڴ;VWJ_TǹK.FU9pN:Rvmc|ؑ/vМqTr-`7'qoQFR0W%ºxNp&GȩεJuʛ957?3, fBKQ|(-Zڵk6'Ee1/ -͝;WhHŊM&-.>䓍pܸq*〯_<6Ki}R\i&ywTpYV->i/-fx߼y,[,| }pq~Rn]ٻw9îib p70^┅&$ .zWJOTǰwmC`?sb&O>PRR L"Lh<"#`cuWPşq$D|Æ wJ@* ?yOƌ*ܣ-8 ^zvRL ;d…Ҷm$.M3bjT@@{6[qǎFsQTl{*)mf7P**ܳ V^`teNF5k9P`,kVJ,u1G6Te,q9vݫ?'C$PRR ر)u juF@{h\|q7;"Tg Uҿ9"M4Ijh5vAϏ&)*=6?`"PG,8^p\N%E R\uUsϥbNaPP9Vլ*>JyՌfZڵ`[gϞXC@}[6nڸt\d2jZ۶m̛7/)/l~)4ga7=pz!cƌx \0}BrnM g1۪iڴi.m)tժUIYӧѣG'ePnsrw2i$پ}{RNSs6*-ڠ#k[UkO4h@ʖ-+&LI@ qKZVJGh߱cʐ!C1 `5{嬏EUFଳΒ͛?u?pϢ-Z$r EA`?8!RRݗ.]*+VH*m=UizTXmڴ뮻N&>+˕+~m@-wTԯ__vذaQHA@{n-[ݸ9W\!B#?#p+\-"?q֭[ *3gΔ jڵkZ~E3~XB:t?pjQFɓ套^3<3L3_|q*TGxB O<ҷo_s :_V\ׯ={/5 x_S 5k֘pOVպxA[o56'M7Gڷo/5k֔|GME½bŊ^q5" p[/lܸ1uB@ 0ܜ:R"n:^zVPkX p7KjդO>5Pg>9c((ʋKܳ>;EH8Qٳ:i5I{ ƕ 2~#_RҥKݻlb-_`oK|li$G W?O'ySib{;8E@;ZN< G !xUPjU!_Wz1B<83dРA[V^nOpg~;>e[X+ PxlȎEH[~a>|t-GXW o \H\Kܹscq8I' ӧOoV$ k' @S.\8 JEP^xs1c`ZH[>ai֬-[6*4?@cRQR`9߯_?R pf~mi׮K } ,h?%E@84ib?S'i)?C9t4lПWϑ8%E@8ZSL1^cC=#?$~hʊ@ kΜ9rKڵeٲe)PB5E%a{b&ݸÍ3%6Uw;z")b6^nb1ڲ9rD~W!_/o `|톬'칟b@9c>֋th {!/ʦͽX^/l70As |l… 妛n2+xnk۶UXziCr+h<\%񼴹i)K*e: .Y+V,`<*U0ʔ)tTk#leTx=|u,nj=\w*TΏ?0h˕+1m{Ƨ-3r3f,hɶG= yzc]^a޽۪ͽX7/ DqOϟs# !LWx2fzi@vi&׉i`_Cp{Z/O?T +}4nX/^,VO6hmqyWeڴir%˳&?Rׯ_Jgy'_Ɗ@6`Mj}Ν; ^픒רQCuxN:#q&M:_x͟?_3hOW.*p6m!eզ$f$a<d'{y}?̈={==Vuԑo&bUNٻwop2*fڵSOe3(zgZU[l1:m۶Lbqa6'֬Y\!ksg?_Zn-ƓL$>l5j0!&ժU&~\˟-p :/lVod aPԣGر,Y3/uֹ~rY;50(XEddd`Æ 3cV^&u`U}BeY.] C&yԩGo&IiF|Iy58Vx<Ƌ֬Y#~ᝓ.>C0W+W ˆDQ~a30Â85xv6n+`Dj]%3Yb|ǒ1&X00>bM8?#MLUV*UH׮]͊A0b * /Pڷoi hx!F&mcCo&'lW0ED"ԟh/9C 1>vё<>w28T0ŋ'js.ȜsQfZQaQx 3^!+d,ɂz饗j{I|H{!-^Ȫ̄F,&E"c.H#sZjenhѢzvXd8 cb0f-2m\JtpK?30LJ AƆ){u2ÄeũW-FkUbċռyTKԕ8L)lL]" +1HM V8ne߾}}60YH{e2EenLz6@H1:^yF 0l0K{{M6e`lN"l҃Grݱc•IjV/rb6hPmƈxW&zYHR4-|F?MΧ"[(+u0`6ze ed9ϲiOTh)c"5TG~:#d6yE;DCvi; cXӧT\~q2~xWEcA+(+1@.4ndD- Y `VL~hW>é^ tՆPãt'CŬ&6iFPO6U;>ajK/Ԭ̘d2QD%)l""̏I.LO:fF`$Cwlyqsm+)!I-D?BE۹|1 t}bx9o&O@d$h=v&?.A#lnaR +0&YVYb"W^ʋ=T^>]c#Ba8ժU(2`V\`:0cݾ)Q<7M'`hH"Jq̘1&X4^h Fal6H,054Ah5"1)P74ڥHA[ڃܽSHSi;mIbuP/;Н_5kseDz7Ątfˆ:u8 &X"Pk] sa?cN 9U9$OPcՖ%\٢}Ny4NP|&[D6*N` 1;u$ehn>3ki?&s4Xh9˄73&ag&Il"d8jlGtLjAߜ= G}4ppӁ2 gᬚ2GpTA!?tSİ' GcɝhBwTΊ ;p .8BG9(O(rVa9FM*#T4genop8gPNv(sH ¥ ^A ?mf5JV-$͊:2`YeGy7 sѸe0nl >B? &ƤO;~o_%+w)P؄!TKXr*% \즕n]dw8d(MV"Yi"8hGlmH^]ỈmUX6hwT9Cѯh' )VOP#MX\#-U ("L|2EPE@rXS:FVd K¥ā{v8\> ]Ipm{:>eޔ|z!¹2F*7OGeKPg6n`60cheyImǂ[7tWlK{Im 7;& @S r9Fϟ"WpգeUk-p7}("s]iE@PP5>VPE@*}dZ`E@PE k~IDATTgUE@P|1Z~z*WIENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/architecture.png0000664000175000017500000004027115110656146023474 0ustar memePNG  IHDRsRGB@IDATx})J. X=+yv}X޻( T* JR].&w>Ns;9sźvsWDD@D@D d%6lh}GUK{8j," " ?P܏wMuxR H" " O@ <#)p?5YD@D Hg# " " ~$ ǻ:d<)@D@DxTg' @X*h"Y]v,8*Yƞz8"D@F^&ZbޭZ|)_nW9&>a+[˵^C`olß3+J)kee[kK g!{,[=mm\*oe6c riV뱖Uap= N5?-J{uεMF =p>n3Fϴ][8=iv۱w۲?纒/l[7o n+QUV^ [ok^Қk\] ;<ފ/>[W/C_¶]<)_ٯ\UpUhϙb`o쉶bGj:oa7m f{' Usn~[l?w>0׳왋0|h=\=`kھrckjī:MT&,!" adjd >O^9NgК_߿v>|#VYG㽁c{u飃}@|eik*pz۷nUU]0\i&:rz@oig >bŊ9m篰aϏ5ntF\<^]/[X۶a8Tg5zۚmwlv&wo嚟Y=RsH@Bx{g>lxʆ͛ޣV/~Y6{X:OfٺUs]izN!O ueuיNI@fܾ[V.MY-&] cJVf *mz8n] ''[8ıE.[nxZ r1wyn-l69 e *rݬE@D@ A vc7Jt2پrrv^Az79={mQ%'(t"ЋV6>9K_zϮ}*[:oQ/Ծzugs&0|f/7m\^unƷ3nZOnׂ@lY/@y]גl\شޙCwشSdiR8HgXek'/ޞut&|<њvhb{Vp=mK_juqQOxxh{{G60$M"" "Px+Qʻx.PmǏu+t [d+QB`@0׶yٟF\RtjBowP[yl6'њulj;wt=mqlX#A)Oqk1 g%K]to}Ӳd-," iB`nx6-| nx sa}f7΀|omoYC r̫vhm'\yWٳV,xcN?햓r߰::.W)OO>޺];okw~'z3޼?z-J`C ́ "ov֝VaK4Cz+^:+0llnx•V=I&mxRVI>pi] ۷o{W#G|/S]nl1(GF}[!:ZDsRt;l p Lzdj&|'|%,S5j;ݞM" " ETJJ6Pxćx|8H()D@D@D >Q@B H'N&" " ! *ED@D@J@ U$xBqd" " "RRD@D@D ['G"" "  % P:ćx|8H( =il8/ٴCSZ/n\v1".˷׳{[֭}[W|o~ EZzi#nʯn}ѢE6xЇv}d' d7n/UN?t6mX$Xo߾I8{>率%S'Ew( 5D;S$wTuQRɺyY?IՑ?xF "x " " "oR&D@D@D Yx7Q'" "  :ěx<H)@S|7OK]D&.g6]}<Hǃ؅)Sln}.;j9?t9/[R  ;ي+^Jvvm޼9H+7l`-;O{+۾}]~VJkڴ6qDW6 W^֠A[u믨]f_jԨav^QicJo!%n*B@ <w%G}dsu=pr_venG|{챇-XmZ=ʕ+g/v;wګE 5 跐%k\ o׎r=쳭aÆv}n3p=s={g-ŋǻ2>rrr")DYx ;蠃RV~ c3z㔱{֪UW^F,<'GO ! A9[F!BG`ECSPĝ;wv=I&ٞ{x)j~2T_6LۮzGҥK?A_[3j h/~!5pV\7 , EVys|qC@=3wygĢbG,/wyJ,L6͵UУĤHNlCWEf /Biqcvg[˖-w9 6QJ=^7J S9œ>Uֳ=uy=joso4"-׋[A͇O?ٖ9g?HK ;ώ<\C WZe=¬Uo#h<ʠMaʎ %i(WPܸᢉJw KxcCq3<アB/E$l b Z-'&a̶zȼy{fݭYҥwmKp>Xb|)ep 1Ac6p,+$ ~'7mTxbom(`x!X`P=!,`X?ꫯ~ppQBo޸-$ JWYx~ha z G{7{; ǧ>T%:t~7yۣe#skJ\/s#<ŋO6zxĒݶm[geO9f8z(A9}QW^REXg_+*&EPX~a?J V ß7c'5fڵkg~S\#7o6:,׋Tnj3ݳ0j4@s%JNX8.7ϵ h;.ލ7xe|3w'˔)-G{{Ǥ÷/8fӧѱxc%Amz0`wq{/hhcƋ`3Z/^T @IARKϙg%2sw (C18WPr{'jz^;D|}ae nz2OO0v&L`Gv=WyOrFcadРAnpB#ҀU]{F/F2=4%MCC{N!.#ڻ9x&B-tQw~r 6rE`oKsqp3QPz`Pe#%=!z>K9Q%0?fhPe 9X`le5ְF{7 ٞ{~ ^"ߏg&UsA_\/Kd(@ JP|h῏&p1F|^HDl*e8Aa'u_AۦgFO>d!/~, *4Q6o@; kkdYr4b(iUZ31unk~ܯ^Ȩ6l O8".Ͻ =}~|11/]zQ6P@0U HqQL̘r P gB!Z/CbQDŋYNh>+bmk{}Gc7n~6|ܯ,ܺpו}mOmErۙQ `Ĕ J]{G:`$X'^Rb Jw.I\gw%]' V4Q֌Q\L=D #'J`6uƳ>ްH&!;p@wPx+ֲhXB n<5~e w}6?,$wWRw]t;"&zU,Ȯq݇"Oz?Fk C+{=46 IO|>Cgh#I747 b`yE~Q޾N-Q)-{q  Hd͟z`wy&AQ7,+w8W<$ w5\\44!7//B>TEK3>\Y7͡5EBx\! U$']FRE =H|b /Hr $/FDcnWMV-|ƞ!@df0'eh<qoB*Ý4ZJ~"Cog =&Q4hlaEE# эi+$ {$ zU@續-#}gBg<--ГF!=/~tZv4&^/cey)0 R U&lN㎙p|nL8COxq""SN*W˸䡡`N䋒 5=?0gb-u饗w^zOHAЦtz|"it"~TdD֟p  )ב"<y%w~%8(I! O}w 0ЉL'e8K2C" -hQ,$VX(ClQL(O6ibB,g&(aI <#6 XbFp;-gB)UDRe 0uU(h%`ni|c;mբU| c|0ӡVH.$Z* J xrOpB9čR U&6z(Z$ڰHW7(IF``F 23)p]klE@Y/e>u-̶ljWj0>Uk~#PAA{Bpn?@\FIh_mXW>zM?7BuUE R+cgۜ9KlmoKlͶaum\1x U+Xn-苎sF- "ʼn +fA|d Hh"CϤ7׭e3BM[e^y;6q`[犠.S5ξ xkԪa"w [(oE .6,2Q& n- HۧGGtxGXBp;s큭Pz$VbBڵ fם7,;>x׬o;S┛zٍe,U˵+ka۶n-[Ö lUxɇ֘ 7RadЛղL)~6c?<&@c~'Al{?Pҫȟ8^2F1%z" " A@=݀Cg)7fڐ'ڲ/~< ;ic'3fp&Xz둦̯ mH)/;y}{nٻ/4 +L1rH{E}hȗ^X_D@K@ [&ۧON >u&)3cIMQu@/r?AV2iE/TL @.MHU񌶟 )ᚐR;7o[7o-PRV _:фňgvgxID@D@C@ <1,R&Rgcvg'LQ# ^tlӺd%{GuVϗ% ^|3Hd8q+=%" @z 6#޽q1˗^U@H:izN;1\SFZ2RP*Jy)Sػk.c'֮]k3gδ)/_^4EV $.\sլYu\-{td|YɖwH(Wj_-)?+=nӦMQKb. a#^el?ۘ1c졇r)o~4+}Ƕnj4Zn7ڵSN9%h$rigMdfbOlNUƧfkVji0J$E7iHD eˆ[\g!l|˗eAɌ7Θɋ=z8r'%\SdD믷=#0"'MQt$[AyatQVNS˪[IQO]b"piGG?ST\ (l>8gnܸvwڠAQ5ߞCYYYv}UW]e[{-ÇSvMb%JY-m_Cnx -@ժUw/Y6nܘPySu.Rk֬YΝ;駟:+u V Žᢋ.}'͚5pD LiL:ZVF|V)]DVN l+VX؋\t}vݮ'дiSgwywADB/c9ݛZ ̰u9MaF_0[ dqQUXb$pG=cիWp\^O?t|͈=3.0zцhРq0C$cu (z'JI`܀|ώʗ(pz䒿 '! :0d@)otzW\q?q]vu8FН$~p}ipQ6vX{G\rN}6a0^s=HBO8iժAF\~Õu ne]fUV'P "Px`R:[>;t\vnj]2]'@ן/y_7,3KAd˖-.+q9W0e*n%!IcEG;.:xF\|nֳ?eyQ%𰺇=ݰw%#_*5V;@@Alz D f" W鍝wyqSNu5`L1x`g@'J5N&dU>| /ЙwUy +Ć_:?!;cwAY3 A1Ӿ; ?Cf͚Jq1ȩ  J0܌̔FIlvV\ͼE$/0mĉv '`5Й&ygbkQFv5׸]wZIeJszpfv6f CIcMxHwNYE:.:v[n3{lcS5s; ]&iK!C\i%zNԩS00r7nt_0&ȇu.4shüޞD;oߞx"ֱ|y=v]ɨ/\&M@ <7PL6w}RÅU믿CJաC7VRQΡJ=L4HNn'D7z >c%EN#GoQGE҂R|xi\{qFiqqI/q2e.Nk.c=Ǝk@cKˠ@sZ`<'|8prYėN)C0HWJhMm43c Dž>|1S='?dWR2XdKAݻy9O1az7 LT\È8%>e *jdRhO{Μ9.#V NK3K烄32Uׯo e$;)Ou&%q wE%KyǏ7nt/:r*/&+K//3z"0>}7|g9wP".WS <,LUVr=^Β]~)DSZT + y~t%fq#: d]&!d dОRt|A1dv>BLޘWN>ڽ!lt0g: |["Kvorޙ4bv;]wРAӔm5Rj' eeA|#і`BѣG.ytAv嗫{Ÿ|8;3az'{"$-~3 !:feUn*4m^~c؊ |&Aìd1 wCmjԵ}nsZ#ʸq믿uֹ~խ[7S|$ɛ=v_}SɯՀ Tլm۶YlVqNVVl0[~l\n(q">jVbT7j:xljcYjMχ&I%V ׯi&C]F03Zbx~C#={*KUb;nUhhFWr 3zJ|$R 8x"R֭[ [&Lpʛi 5d_'Ƴ{wK+RvV⥳[YQ׏íRˮVfB-;BgNz'NtTugd5Jxy}S] Biɧ}m ʥzUhѶ| 0I Djݺu/\ KOH:D$gzO/lgۺU3)L@ XNc}L@ 70U߾} aZNLr1AHvsYH HMyر믨usik*ΨQ w/JIBY\OF't >+]:=&;AD m K/ϵ25)ViV XVj2ɴ O;m6+F/{nVZ9x-4+M.C Jj\eB۸W+s!gzS%ЋOꥫmt2[6on o>Pgy3G̪1`QNٌof6-٨鱷|AULSҖd)כ7nu[Z#5+`-U%dO&Ppb[ZD@D@|15_vꩧھ=/;v <x]rw?֣G8xD@D@~7{'%; Q nB:sШQ\AXvv "О6f̘]7+rJ+yc-" N`裏ZNNK$G=-g RG׾_!|!8=zկ_HJvX>c͛/\ONp̼BJ*mڵkrer" E`V\Ep vgUOUVΝ;&Kg+D4,`y^SnٲXW|^M&;̺. _gKO>܎8;Íe}}/^NѣΏwaO?. ㏮Y^=^z뭮ʶ^zs۶mbDD@2@mݺuAK(=ݻTM48)whqmTθ>'Nxuz ;={tϝ&WVN.p7;L. Ԯwuܪnݺ1n8wly!?x{W7w}{ %) &σn0n.:,Oc9H=9|M{ݹ8vw}zU/(2(b49slr'z͛7~m;?#C!Xy)=au<L3\&c_O<Ӽϣmɒ%֭[70pXbB/tFa(wz䡂;?a.ZsymӦMPf󒈀@&@qv(&I{L[FqzB$m|0@{^{A(}OGMt$>PEMO?I&cAdZhL 9,__,/" Hou&q(B/JSz̙İh"7K/u=P:e䕂Ly\>[ 0k :ԙŽ2g7SNߘa@#7&tQGeN2QD@ҕJ w#{MlK_w+{M9w8锿Xۡ: aZ(pL6D=bBo߾}y.]/"; ~'8 v0`as=9k)HQGD@Ҏ AֲeK7jo4|m!C;q6ydٷYfΌN|…vuױ‡ f!9 '?"cyo28IuI :P(m cM C}b:sT6a_9+{B[UƤV o@ׯwq x|~k@G@D@D@H@ ܏wMuxR H" " O@ <#)p?5YD@D Hg# " " ~$ ǻIgIDAT:d<)@D@DxTg' ," "5h [nBD@D@D/>֜cIENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/move3.dot0000664000175000017500000000265615110656147022053 0ustar memedigraph { compound=true fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" ranksep=0.2 penwidth=0.5 forcelabels=true node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] subgraph cluster1 { margin="10,10" labeljust="left" label = "Before Moving" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] c1 [label="{contacts:array|}", fillcolor=4] c11 [label="{|}"] c12 [label="{|}"] c13 [shape=none, label="...", style="solid"] o1 [label="{o:object|}", fillcolor=3] ghost [label="{o:object|}", style=invis] c1 -> o1 [style="dashed", constraint=false, label="AddMember"] edge [arrowhead=vee] c1 -> { c11; c12; c13 } o1 -> ghost [style=invis] } subgraph cluster2 { margin="10,10" labeljust="left" label = "After Moving" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] c2 [label="{contacts:null|}", fillcolor=1] c3 [label="{array|}", fillcolor=4] c21 [label="{|}"] c22 [label="{|}"] c23 [shape="none", label="...", style="solid"] o2 [label="{o:object|}", fillcolor=3] cs [label="{string|\"contacts\"}", fillcolor=5] c2 -> o2 [style="dashed", constraint=false, label="AddMember", style=invis] edge [arrowhead=vee] c3 -> { c21; c22; c23 } o2 -> cs cs -> c3 [arrowhead=none] } ghost -> o2 [style=invis] } opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/move1.dot0000664000175000017500000000164715110656147022050 0ustar memedigraph { compound=true fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" ranksep=0.2 penwidth=0.5 node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] subgraph cluster1 { margin="10,10" labeljust="left" label = "Before" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] { rank = same b1 [label="{b:number|456}", fillcolor=6] a1 [label="{a:number|123}", fillcolor=6] } a1 -> b1 [style="dashed", label="Move", dir=back] } subgraph cluster2 { margin="10,10" labeljust="left" label = "After" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] { rank = same b2 [label="{b:null|}", fillcolor=1] a2 [label="{a:number|456}", fillcolor=6] } a2 -> b2 [style=invis, dir=back] } b1 -> b2 [style=invis] }opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/move2.dot0000664000175000017500000000273615110656147022051 0ustar memedigraph { compound=true fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" ranksep=0.2 penwidth=0.5 node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] subgraph cluster1 { margin="10,10" labeljust="left" label = "Before Copying (Hypothetic)" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] c1 [label="{contacts:array|}", fillcolor=4] c11 [label="{|}"] c12 [label="{|}"] c13 [shape="none", label="...", style="solid"] o1 [label="{o:object|}", fillcolor=3] ghost [label="{o:object|}", style=invis] c1 -> o1 [style="dashed", label="AddMember", constraint=false] edge [arrowhead=vee] c1 -> { c11; c12; c13 } o1 -> ghost [style=invis] } subgraph cluster2 { margin="10,10" labeljust="left" label = "After Copying (Hypothetic)" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] c2 [label="{contacts:array|}", fillcolor=4] c3 [label="{array|}", fillcolor=4] c21 [label="{|}"] c22 [label="{|}"] c23 [shape=none, label="...", style="solid"] o2 [label="{o:object|}", fillcolor=3] cs [label="{string|\"contacts\"}", fillcolor=5] c31 [label="{|}"] c32 [label="{|}"] c33 [shape="none", label="...", style="solid"] edge [arrowhead=vee] c2 -> { c21; c22; c23 } o2 -> cs cs -> c3 [arrowhead=none] c3 -> { c31; c32; c33 } } ghost -> o2 [style=invis] } opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/move2.png0000664000175000017500000012105515110656147022043 0ustar memePNG  IHDRk sRGB@IDATx]|8(NpCZP(RZ(Oi)PKpw!hI|'=.w]Nvsݑ7wߛ7q>|XF` G*0#01`F`^z`F`KF` ~5aF #o` `MF0)[`F@?0)/XF`b=LJ`FLJ ք`XRF`~5aF #o` `MFwjբ… ;(.`F 8~8T|yw)*TƎk"`F}Xv-ݻwnRb9#0u)7`EI}5gF`R.1#0)o߱#xӰaÌN*VHUT1:oǝ;wh…2 y斒9ѣGiҥQ)rԫW/J(0-( ƏO?S9r$ 0ҤIC8nѢ(QHEQm۶Fctm޽UL|@ϟ?'XRjÇ BHtQ$I /_NX*ېXܽ{ Bx~@ҥK, "QnڴiSF 7o'|bt܏L2Ѷm"]z|O2ek!!!ۮ$~=)~?}lA=,YPܸпիWB4gxBb.۷F2mڴHż!XIbܸq Fp=GmT#ùYfQ֬Y@ThQڳgV~}߷o_2e$Oj֬IX1 2Xf A/eE7@?_^hoݺe =ӧ:t >x 5kL-K@J pUV|N]|9Ao!p`, 쫯dr?,hXF@{4#% X`GsK~D0hco=X $S O-[4< $j %W ʅ+ˇ; {#G\Yݘ0aBm޸qc``ޜ2@%K,?̛8b) ޖV9R@קviӆ6n(- 'qNԛRv 68F^ERŃ~d#.?ET A,#=iӕn~7C2 0ÕDqi5[ȤI0 : xyY%ҽBR?[ <\_kA%QO+i`1@ XX 2[ -1"j.2X3h#4,sbH~K*t)'O̕sJ H sz={H0 XMZ$aM}A >=Ƽ(G5C "&1)AT0y$e~(.;sbKp!VZ| (gϞ7@ #D{)O ڵk44B!h\QpIB@HpC+`̩a&- VRKaI81]30DH+,aÜTtFݲe$p WZ%%Q]]%pB`}4g=cFp u]SΝ;4A Ă'e`, 'N44,0E6@vM#l)O .dɒAq*P?&:%4iBXk{"V[0Ђ As޿٢L-P, -G$G 0L!a } K/2|tW,T-.ׯ_? pC A'p Aȇ(>JnɲVoFp q ު0P8c"'] Q 1?QTiq͖^|z~ )Ań$\f Z0bnAJ@ztX1& h9T-߶ ,֬z~III+R}g(ч[#XH-Z1sQt|0C@9%5Qۚ0HhI.SQ1 ,/Q237GgIzŋG"t7$wk x vy F`<GR;0!FŪ2#0)yzsFp#ܨXUF`<&%Oan#0nu0#=cF`RrbUFt<}#LJnY*#00`7BIɍ:UeF`R1#F0)Qg#x:LJ>F`&%7,V`OGI{0!t})>|Ο>Dwn_d^( n/J?Q>| ĉGDZ"87ouZW_DZRAyŕ޸2fHKA*uz*X,)[ϯ$X۷رtqzx|b dOego^wܸKԔS}ұfuk+X}^]9%7>^R )( %I-LŋҥK˶=]ҝ;whͪ%t U6uM>YS ܦ70?zE'ΝwQԬeg*W 6 bEcsh͚j.DԠYӉ㇪^ȑմd\SS[ -nqIA*GƍaBCCit/)nFz 2L_vKYd ={F ̡/nSUdɼ6W5gx^~%uԓ .쪪Xv-%NZjeCIu7OF~Kq_#Q*y<ޜh7U^4Ao߾Ƚg<XG?4 JHѕ )!CjԷouZp2رaM芔h`ԬJbٮ%Lk]ZRny@ڶ/Zf]eqf!pE?gUjTM%]M}hʺ!7o_辥VX4]y5*… josB¨2G7UQLjG j/rE9A0AbAablQ@CNÇOh۶=zD˗/Ç򺯯/M>H'M${,wT;=_ٛN_OgyC),aaoiŠ^bqƒ!8Y$B)>"3g6:u4 ڹL\r/iSCOҤIĽ# 5#)]z$Gb5J8N3iZ0eΐ|\eԫ>.-2 SҤJJJ6^c"x&8AtB$pa]Rv.]~ V.PL)J4꛺VRŠSc,]<~Qv$xyyRLIp-]vm wbE]v-[^٧~*Ն|8nѢ <|Pݻw%KCzA4>(o֬YlXbyH3iҤ? esZe%-_[hz)kW|G!v0iU]Z^kڴ {̠=I~pbBw ^[XU)/ޑ,J]ԗz zԨ.ԿKuv16wt)wBv>@<(< …s{պu AJ/U?a]%m@5,v>- "碒*U ҩS.\4# ҅RԺb ֈZgNE\0fd#k??zإ25xi}$ȝP5CR1=(]4 ]TnAi+K+ 'q;cRgs] >`Žհ\0 OXMբN s8υRB#,#7|c.C5)y{{"A@eIv n:C5eM= 0A)?~~w.=ѧMBXg6m+>E$ҲP2wWΙ~=&HkhϞIC΢+W"*R'x`h2=yJtp߮nV3I?>\֩SJ͛7;m7uT qA,AL& k9"&%I:Ν+0WP * RC@HX7BGc^ϭ->bh bkki!A!!:p/ObavYҧ~bn터Fw%d u Cbǐ2Boذ$܈Ak`S6l8Dc,բ-[ʨlZ,( "pc%7ieu".=@Xk0Zpkp!٫+~ޗuJmsBQz2Xx7%hK ^)hnJ[Det."LGbN ,+ P❘`N1髾;@/fEy楟YF)}YmΝl{!̿@ҧO/D]ѧ$q!҄`hÆ p7ӧ o.0gWzuZ֭e:O^|76* a灢}; ,ٳׂ82 GWѣgȍLˑ Wܣp,#<hIhFJs[@3"|%n?]:F@3R 6:Ѫz"<'!+'ڂ@ppыmif^U?PKxyQzIJ6Rڲl֞"gsn\hFJi3xKd`.22<&Mzˆ7Sءս{EJH)O>Ѳ7}rQgu%>>֭2Ao}|#%fhFJŊ\QH ~{kж"EJұc7/K1'O^/ʋgc~2jFJޔ"u6:}McB&%tU*(QΟOϟ*='r<gLU%H -Ymss{ܤ\J"X=wnx|JhӦc&W+yӟ?%K:hJJ+VAE'7= {K+ݥ柵wYK5]P@@qO۰Ex;MI ;ٗf,$^{Y*Q>eϞ;)SRÆi4rp\@`bHNUSnMI ˗JThLn#7; u=ފu[ SWumעn=ŋPntF āS˝L}ug?00?ݻxz.旎g#Dk__!Mx %fȰQ!QVu>A׊L/oL2YO>D hô~a=궺!!_Na'7m/ZV\rSfQ _gsL{]tqG|X?mr-q`?~eٳʔ)( R &CkрzlTgA4fAu6>wE/ #}84doPP͞KS졯BXh>%Kp•׾}'*U͝7MMg3[6>~EnS/~6gɒ%oLQb2+˶x"֭;-,MߐW \n@=ѯ_?7n :DRB)Gd%C J(>ŏWFHmҸ?R ?vO5jԠ?AZ9e'4lԼysWUi8d̘Cz[RHC3g R|5S+fboܸ$E̓UVځӠAh֭t5*_<ر#bظqѺ%k.ӧuE-[0)yfrt&J*\E0Z\r0ݻKgNŋV{kP֭ᆪmP֬Y@s A~z:|xb%>ϛ꧇ 176&faRewbV5LJV#G7~*#Mrb4i н{/KLJVF\x^57nܠ:uꈍCgԩS+H'A߼y"*P (@GUgϞ}9UX֬YcN;X&%CߗA +VR%KPʕŞoi߾}Iy= ohÆ FatWÇԤI¼X=eɒE8s"֮]kX8E;vtH}Ύ;D ,~-O )֠;&NhH"s:AQs+P Ɵ80jN>}ڭcPyzAI&Iئ^a}աCdȷP,`Q'ڎm>`KIZh^'l2hgϞ]F*UJ,IE%*΂Ex5;ؙ]dn:C:u`' ɴOVD!bM͛ݵ7Gu'7&&`)Ԅ1cFI@ P ( ~n:lO۶mes`P~X'O"V-[n ݽ? yMi  @y<}iM#RAyx7& &wHW;,-[6ia= JӋ8a3W qbB|rqÂ}}YJ"+T:A !?D&X7nܸhqB;~&,۹$Nme{x~"P8_=zC3e0)كeqjըL22j)x-9"0: 㯷a}AQok** kժU&`o7Xp}2.1Ot$:7e㯗`=lEpڻlРX%1av%k3z@II`TbE jo>>>rp0ukz@II*MV]%2r|Ek>pӨv}WA̙3NU +\;_&%{@'^Uٳg/8 oLWov5LJZFc )vw˽ -WFIIШ˝5^?jtԵU-`R} 놋ēvv4a1gu|UKD_úy ow嬳Wo!>qFs-C |):kZ"%֍dȏ<~U:ow嬳Wo!5bF "K;vX|g6㊳v%LJZu3#!5<~ƌC'5m $%i!s6QWDIIK5dzM2dP V=qD*G v%}`F&%#8bsحvvUR=u 쾳^'[WԵU-`R}`F&%#8bϏ ! 1t6QWDIIK5d޼ybgu|UKD_úhVP ]V  qnex]0)i՟#GzFZʙ3Sd- -WFIIШ~^|Y]KdɒNU +\;_&%{@)]t#t)PǓN8b̓*gzA#իGШv}V{q*PM 2!v%k3z@II+VPrFZ<ݻZlc]qK/0)'4;:t֍֮]Kaaah*׭[G ٳD1fWo\; LJz ȗ/UT,Yn#D#iƥ 1pkK;+ &%`Dm۶%,V1bg7B=J;wRD,ri_[׳\rL`R j4|pyyXn=*?2e:2nbcR lxԩS)A4X1tAȑ#.Gt0dz?LJ 5I0! 6rM'Nب\\3UmVӧt!:<5k֌ׯn36w+g0∗i}Mq[ 50oݺ0#H&0#`LJvǙF`#F`B@3RiذafԀ`Zp!}Fϧ/j֬I]t۷;xL<?n=hȑD?S9w1Soiݻi޼yQE_d! )iڴim6#ŗ.]Jƍ%Jeʔ}ۦM駟PBԵkWJ0!uؑrP@IDAT㏘kUW2j(*Z,͛#Us 9sfL~y$k γg"|29|!ߡC(}Қ @P>cJ {nԩN- 7o1ISN_I$Yf Mv=*_]z5թS&M*I?x5*[t|?,ˇ5bĜ@\r]_m3g! Kpr޽"qҲeoX̝;H.]H-yw'|"۱cl+TMU_;peABw֭[TtiIRwR3cp9)aO O?³D H) "p$ҦMx1%Nl"UbPUrADСC7J1Ι/_>J:5jՊ0K N-+]tF$m #V d iܸ1+VLY̤@LRӚTc8ĶVp*ի:|70{!`6C6&L~Ȭo޼sm9Ke" .;E.\X94@ W,XEji 0odϞ]>+@dȐAPn]y}ܸqK ֓9i($Ii7/u86' faT N 0ck(_  \LdC!;>iM_*wܑdu9'E@V'OT~7#.Μ9C{@P $|0ҰF0 ӧ˹* rӼ" v'1X~ 6 ?yW比2oXxW\Q~o "A~p*U>IURP}8y!5 ߁vƴ/[QpFeYǿF@_`%UVMNcS>CDJ1iذta@LcAQΟ?/0*p)aՈ *рgyBCT Ai`Yy,O=j6;5,\l/B3M+ČuO}媔i]_]D>B;ե OFpO?;-0aT`]L8BKz);j rAT!k$"5HsJ۷7T<4za'0ُO y={ Na3IժUcDJ !XrXԋy`PhTD$#Rrk8a>sjW%\ø_XF=}Jԝ>%(0h02o`–Q,$SAx[.#E~ƌ2b!DDV+8 I΅lX4O:%tE;ICdi%l20L9o;4wu~Xh/I=ӧmB)F KKX)e5XJD-jAS|WL\w( $_BigF 0PI­sDY#1\{9%ItXKH(;4w.~4g:+[ٴ0!q"A;ܪfjNA̝:]Tl1rap{bA%F`A0/.Ks֟:A6Em`F-wQ#0G;Ϭ!#0&XPF`5dF k0`R#bMWsCF?LJ#֐`XRjn(#0GII}2#k`R5] eF@0)鿏XCF`b LJ#&%k0@AI)t57`#>b F50)Ś2#G!#0/^۷@%q#x5N8^(ǸrOHrӧ9sfJ2%eɒfͪ$oF@"(қ7o۷drܟʽ|#rϩ ҪEwoN]rNS_s]V$^x2d J,rc' ڻw/9^=@92'Pm?RRJА q&"%S~7WQ=(eǏr@IB$t{ڲ:^ǥReQuV,P:|08=ӒOZ1&Ys{.b`tōr*X 4ǖ+NJ.(C9V3m+{^3gҝ;O; U\*T Ds-)цki5T`2҇rt۞xHOR\u΄'A؁[j*P =uX l*؁@Z .^C[l5kS-^z[t\qēG*\y6:իWi?{j׸ L$ ntpo}VlzM:RFMd5U9O:uA3!7'Ux .-,Գg?ʔ)ruӧӤIb,[}i94Sa*?c׌ 5SYi?+]xo؂z0;:~8t*TjQ;KA}ג;G2:wJ.A*n˖OѯVHBR?iR%Vq#e:?Νn:ӈ͘؝ 1cZҥ3s,!ӮsiҮ.M숶epO@ȑ#4{:xpΝ%rjfMO|H@T!`9肔N>M-UdI9"g-3a:q뎾0Geʗ9֭Y袹< uxE?܁珜Ek`hNJϟ?!]Sd]'i:{= xݚ oYTԭ]8Q}U[LħuG\9]ʱ[+ ș3]GbYISJi'kj }Kw-v9^B35 3: $߁Aad? TV;6=Xn *ZL糪= g#WҼs!X={e4V,*^U#9{]TQ"# Q_4@;m 1mߥJ啘{G@SR /Ok[{4n,:|:27o3Pv3Ҿ} YNޥGS?P $R_lYH]E]j8?g{q-,2rV C?MNNRn kT@b$X8o/}yh+KX6)}fԪՏa$hӦ"|ޛRhDO J2g(_5KLXnQݺի`Jx6l, ٸAk v [/3[ŋ"6tjK{c?'Q^'Y eV>@D-"Es>,CEpҒ9C +˒hC 1/Wbb-1Cy۷ԩ1\09СXdE,}ar捫:(AxV6gQZٕJ6˲ϛ74 kJ/"ÅuշKeut-Xu*Cp'N@Kd7 ˓=I0>hժ}s$ID`fyz~ʖ-=qS`bSKUO\$\ܰ]b hE4$~>:S Vd׮Sr+Q:=O>I p~Xm~2ɓ¦R Fy^֍#.c.\tE&,O^jϩJوQ) u\Ԑ×fԧs%ùΗn<G#]FEf4i>1͛ İsIy.mi<) "ڭ'2"i7 ]wZGi BZmR\VN%\{>4czB;oo[K Y´b9IL 2{h9rJw^mK|Nܿrh5Gf|YeɓW |Du+] ny$MlQ t;w 4ib֭; e&B]ӧ/eF21DŽ6@VLk=zʖ- Gr .27q*1- &M*ȻlbЭ$= iD'p7A[Pjps߾`^R4ŭ]uj/Xv M=$[/|d]ZBlХd"*ul1?o<$}Hi2eJOIl#_]c#0aa ٳg䢊Jn&mnnR+WQ26zbKIqE>WhKbk0j{ ''6!$$DlzR.$ k7$cT^d,#dY%0#[RJ,)5d4o.v۽H$ ծJ`Zjų6կYhf)d}ÁQ㣷蠏qE`OHFm5>zʁWh)E}׃JSs`hNJDT]' x؂)v6zD_mܹ;wGA Hbwѥ %hX̣,]~q,v0  E $;+|t~8qݽ(_~*VOݻ"`9膔iݺ}U na:wɳ]r?bB)CK)*⿢\I7ƭyEWQݺ'͜;y@)MwZ͏5H5u1FSK.M2teܹ&e̘FPC,%( :VIZJ"G 1~ʕ+&Լ^hw*U0ի,}^? '-W)jݶ3eȐA3}b"m۶ŵ@ԠA *X0;6nn/K >[4iu_LI&XݒҢ ڻw/9^>YQ IpJ89#^ EL#R6+kOQR$;DģReQu)k֬JeÇ|zȏeKC>a+yDkUQ?TJ?{Q>>VìURuʱ:rNF=uÿڅ{SdI(Es'@w\6UP8.#HIw;SHfaÆ q̏( &NL3ʂ2;3%t AUT1p iڴ YRJ#G[-8=v$q>=,h#A-S(]@=mdذaʥ+ν{?Fxs!\ȑ#U.W;d޽rBi=zPE?UV 00\qՉCCWG-Qm.zM4O^P>9vٳooW7%wԩS'rJ۷odtK ylR4hu^9^ZJy=vK8 aHU@X` q mFPh[R*Wz¶Cl׮jӧո:޼0G ]0p1c7z8 Eo{̙H/]YH0| >8FI\VcW!BGtc'uo[+<7SH G^VЋƋ >T@vںu= |p fׁ*hձ+RoW^@g;d Ro._5:swXcǎ{#@i Wvx saqYF%m (mMqon&oV^oJxJ9s昙5v0K.I2ex9sJԩSjaB7 rʥ7`!zEpam6mKL_G+CDh̙#ޞFE ]Xy…'̹|gj>iʂF@kÇSYhQ`{(!($(&Lvkxtz]c7ʇo|`V \%*%i9zN k&֬Y#+V͗JDL{%l K 7$`c]ID:"NkVQܯǭ+)aF\le!N.cG.ɭtpZR;wn=W 3[QwS,coAO:g=\?#}s(%^fi ,h˖-?zݮc*:q;ׯXJx%%y1HɂeiRDQ-7C$ydvq)SQi٢d͚վy3bO80,RxqG=B|}d̘1SZ]+6fvv^V昗g+ic坁u%g<+g.˻jVZ1&t7ihUa|G\tirqnJXX-o/ۛy&`RgʙjT(kV6$qfa|8ea$ QC.<[],`SXBU j!Qs[3[ҌӤxtRL&ʙSr <ܼy+AN.*ۙF!m뱱\v(8EaȿM21>VvuIQf0 0Ji;ɝGN `R„Us+7z JqCau~_N(/+SML:']_ UK#Hri)Lb.SJF)M!.^S:vV3B./CTUKB8/JS:XuH}1{9 Ll!0w9mJiѢ>m"~k=##ex9Y0l - fovL'3Tv.^o+o5dӞ!Z:d?;|y.;j'oxn4m^F.$=O Xf+} `&`cbVO;C&`R굋5O %Z}Q)U6B:>J5^|=G&2gb$?#4UbAyjaٻ焠vygr_peެeSQ dv[oߞt*ql7&lj'[frd͖ѯu6NV-ڑSJd~Im5'ziЯ%J=5MU1TEIHLxL s; آEeޘ }HA3wyv ;,Mj|μQr2| ՘r 5_c3ˏG5#4ae/Ř6|1%4Y';E F8Ջ!CBf>}2rhRk{0fIwC*U)$-}YECpoN)/(:}#e"{I,ɧ+o<y݊5w1իW.{9YBI5ҹ]lQJ״aXBJ@t^O}=-Hk.=z?)M N] WPMGl6.~ ŪuM?N\-g`I2x|&:^Af*%Oo#3JhRbOɻ4vHcvIWmdF mSzXzH=iF&s.^[GfpAɑܝ sS̗HHl)e.5(-[p|\rR~} -2=+v LCtcvIWφ dڴi 1ydٴ)ngl:n89w\P9E)qӻ8)l;ybȐ!gϞrdͻ2|o@bfafNl>fo6v> |tq5zŋ:y;ۖ-B$@$@lSJFL?>kgFfn'Ȑԫo  (1ZOɠ4ApejSsgOЦ jɾ)N X%.^v]S7n7 r-mXJ&k6sUآ3d͔^@8WIƊ:vV?M}FUB#_Mp)&&Pa׍7ܻzDNҕv7a&ULwʞ c')a5z~;# ' fO:w^-3'WF{Uglm-*n܃=:?{;m'Xɜ9\:j[]͗% ?m- ޗ͖I3JѳҪr-)5r`͵kvLS|ʹW$kj8q+%m0)Zߘj6=~;-=^mί6WwjQdߩʠ>?ყe7eެ?dgm䵗&kۤ_O6"V#EsO3J5B" dWqP2g _|XDfm;\P!cX^hSM)*ՊHԒ,ڽ>y:=-uD̟?8q"9=0Ǎ04Z';ERѢEewݭ*RQJ{Hy*O7Hת[JO,iR~&T}A'_VYxԬSJ?ɐ1XPZX1~0?\['Ƅ{[1Ңm5Mٷ,[C^> ^JxOJ''###:ׯKRo &P`dgV;I҅x&ڨi jH赸JDlr93_N&17?-m##o>I.A! !g[UOS}sTP9$@ڴidɒgϞݻw+6`dgV;Iի_v%Pvssji֫qڜPYー1G@?hjxn^R"bӸdP(-]>~N&|L?M7 4ΟM%KSB!k׮-k׮MQyu)6f׍ [8wI ׫PV-=J_NȂ9[Խ0z\OI݆ed8RY5&XsE)_9Rf1iФ̞I?Yy3* #&^zfNS7l]bE[r#B [?q mJ){Rzce`niЈҩ8)[ yw \eV#m)]3*͚5KVxuirdmcqM9f/x#65v-Gi&۾f0Wqy`na!C\im =#ݘA=ۚy: *gpұ})U*({SoȑQGf[nJ /^,.\7|zn[Aeر2zdҶJ -ߏ۪|ExxZCt`ʍ p|JWH8sjC 顸|PHg5RJԩl۶MN?TeΝXX-/b'ۛy&`RB .,5"U4 7τ|B O]LZ4oi L2I=䭷ޒ۷R;3Eg̘B򷺽g+%G1Ixo-^i<' 7/Z= p_{5 򷗿]|pRB*V$25 Kg0#>`QUZxV#J>}dL9I:uCW^-˖-9MZ]x8s[ ``ű_~$Ys]g+(nwh6YjIjԨ鸪\tIm% 6bŊ9))̎aeW?ϱc.Rtxͩa8Na2wT)R"ԨSX눞DJ~ _}@֮8.eJ=ZK֬w$mpޤIQJjՔr TDz,,1̾+Wl:K~D,"JI/k֬SG%L^I K-j׽¿70X7xw=Fts{tq?w_ӿq rfJ#Σ9k&iӦ gojh\>ԒZ5ܹs#J@6o,kB䁩}::u hva@Ǹ5!5!IDATk16#==O>|­(`*A?Ze z8|z`zoV*.c~*U*Ds_!I 0Yd] +W.={kR`vɐ!Lܣ)Rzy5)"(8T~,%D\ǩR)9c]P[b?rT6)RXiy:RbE-;M$@)!@z^ܻqF6{ɑ#mym1΋;ڵu~7o֣N7o++Wv^AY" %@dR]tIKptRSʔyKK϶mo';KL+s"Z4t0i>zSk^Br%+L! RJ)AvҲeyiwةӣ̙3n4xJ$@RW[8-mUBqbŊHժc e]HTJ~91cFsj$SRq2b H*P3F kL\ ,($@$TJɡv̾ae׼cnWRvgzޡ\pUw_``B! _ P)J->bҋ/QC$wfu[[3g3.]]8q[4y5?jV~7>S/<%0~ 0 BJZq cic2ٰ 9}zv+mCݼyK"'&Ryy3FFj*ÆuoҤ\r].ݬj ӧЬk.yA`&`C! _P)B .\=dy+ʙ3Z.9xo9h=!C:JU4tqT^yiIݟ1cGOW5S瞫-od| @bSJħk) kmqq !F$o)(# H6.jJiLa*y;М^PHHk)y*qD:$fBCW<&7<ġ0!i>J$Rb&^M=J& *%#* # -ؙ) ZQ2֭[:uC/6(# xK=%oIK.jn (bbnk[Ň^c R2eX MbzѮ^R fgI ER/gRBpzpDDDw%Y; RJH]TV09t贀H%@-)xiӦbJ˟7A[WlB$@R򖔇x?^GjϛEB$@RA܊+ʙ3d۶WC3,l($@$ *%_hąʷ.v 0 & H$@$.V\Y2g.$Ӧ `& <eٲ?<͛(`A! RJ5{2e$]-ߗw O+`ˏ?(ΝOM$@IRJo ,(6l>[*5w}.`ӧO>Rti8pDGGM$@ $?0{ʸqkdԕAmqʔ-trɑ#Gp9s۷k<ޔ'JReE! #TJFTR»ݻcWAi.o uD]]{H0ףGٱc[&M;#'Npc {d>}\zU5kFlܸQ^EцKٲ5'Xdz<5`a,HܸqC;裏ԩSҺuk5Ǖ|x uɟ텇wJ͛Czׯ_TRȑC3SSDUJHW&zp|_s=׏7_y2tP}ӧylSH( ?C5߳rJ˩IF0_ʦM䡇_eyy! =rŢҗ^zIի'%gFi `E; :ujmrKe„ sNZRX۶mH̓HBTJv Z)[ 4"a J=~A<(>|X^{5y} g}&{QF%*![rR^XD$F&xmC`ҥS`5>R!]9nP*w?wWJǸWKiaQ bk:u쀲b 5Q@oc= _Fj X:y1F:kAZb7351ׄu[틯?zW~\c60 wR¢͟~IMc?Ҋ{ 8ԫWOm] 'e{\Ŋ?S<?fof͚zجM8o* 0/X$![dlÎ B=#"zoϪY{Ix (%L *!xS\SUk_po(6{E`.CO<^sGXa髯 w0*$B xdo-kK7`H="oCx1 " *$ iu͚5ᅲƍRuZ 8_ [0o\w˺5[~4 خMܹQFA۽PL LʭO> a,> untAM,x:#5fVEY̶e$lSJaҤIҢE? 1F/ O1nv;H\ ئΝɓǵW3=ӵyHm((ҧO//_6P s [=pWcgV; آ0LLl>ffq|X$`Rz?&~ҵsUq~KĽdA󯒏ۈ% ^(%zA|Ns Χ}lQJ|`c6\;_)$`[=7oo/Ϲ -:uۀ;/\ o6h=+$@HHSʕ+ԯ_ߨc gϞ / 7F1 8J \_~rMꫯݻeرw!4nO$e?=stҩ1̙#~ԬYS9PlpB:v4s+߻: цInFӧŐ4k,I3~I#UTQ Jef7aݺuw^W]/ܩS'O-ٳg\jԨa<&Pd$:^)6cs *3gNnݺ@9@)@i@tEaXk箔\]B׮]S(wR`Aɒ%KXt?c,""BC\I  $@$@#@&aHH t P)n۳$@$8TJkHBR=kN$@#@&aHH t P)n۳$@$8TJkHBR=kN$@#@&aHH t P)n۳$@$8)}7c U" vH~q)m]HH@Zj?dHRJvμHHpN OIH#@d{L$@$FJ OIH#@d{L$@$FJ OIH#@d{L$@$FJ OIH#@d{L$@$FJ OIH#@d{L$@$FJ OIH#@d{L$@$FJ OIH#@d{L$@$FgsIENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/iterative-parser-states-diagram.png0000664000175000017500000026433215110656147027212 0ustar memePNG  IHDRah3VsRGB@IDATx cl*(mRJB"QJ e_lBѢȾH) I(Kd</iӦYf]v=jB ;R²c= X^z%|rӡCӤIӸq̟?*ts1-Z0;v4Z JD@JX)WB@x?hLbMf]w4oTR󾰊͘1üzvۛVSΓ`A!CjJB f„ VMسgO&[oeƏoVXahvaVGB@D)aq_/ _}Svm?Vf }w͈#Lz2q) @@JXN(=c͛glV"8ֲbdF:|.Y*jdMOFP~6mld*7uTm6ӮZ/@ (#_aÆ6f5L;<6@GQ_}n%];L vs>\}6pk(Z'y[D,Z^{իڵkڙ|77ԩ͎lذa}]n^0?}*9ovwg8F@JXϏF'@  )b-Z)-ĉ͓O>iPwnK>CcZlѣG.BG+HR’u5[!i袋_n~ sYx馛L͠A, k|Z;{eO0{#M"@ 򪫮2~n*{V92Z* ܔ?>}9餓LF*p皿*ĨIH.R’{5s!v 'TpA?n(>Are70#G4Gq-mt(`rHƠc]! %,~T3Bjs9f=(77;Zp 0/doէO{V8.3l9+l ! dgRD,J\z?@z燲T$ 5kVڒ?͖}J1pC@Βw5c!p˅* HdXk5apBKQ"իN; >ܒúiYd %,YC7$!J%9+g*d:zkm [pGBK0D! %,y\3@bdRQ,ZȐ {2TqI2>uaf^}UK^e! ⏀cPD+VRCoY}ڏRy}6ĭ~xv! bXVMJD'xd\oM3[mi޼yQ5c 'r,d! kTD<쳦GW_poEUz 02[ƍsֲ1G@JXO' X͆nXnXth)rˮj)7zr-27,a_u"@|s H"/[W{|yLnܫ#ܫW V*UXv^z))a᥽b fmܸq^z)kÝe] }QnFJX9HE)a>3g4d 1$'U"`Wyk.B@В%F`I&Fc>au->_:wlxA"@s H oYmݶxgϞmZn]n]ԬY&RH @nH '%{fPhX}J`7mڴ 4!kB |g>n_mH(-ZsVYk̷~^e! b1$@Xt_~ժU">yGw}gg}̎;XR}|+f,(FjhW%,ՅXlW^QDRcv/Qh1`^v$1X?||lذa>wM61?I-MxH7jvB 2`QF~EeEx檫2Gym_~6n2C p*AU2YV\i? D _|Q Xzu1X! ,a?GH qkr;TEO4wu]ײeKsF1cug}l YM<ٮ*WjU3zhk/kɂDV!B-묳{RwN5#!IPP>ܒN1sol2@zjʜyfĉV;wUqDa;vym dz:˶B3x?m4kY8֫B9SRҶB H @b@IUp.,\`5kӧb1"m3hOl%pe_y啦k׮[Ku[z !}EjB vĢ\u+[x--.|O{߾}q6ko RѣeOYfR]ՉB H iѠ@@IuarEW_5k6?c١ڟwy{/R@w}|歷2sN.gΩ־t`"a>U UŕXH;$0Ofu̽]B@)a8<Xp %KW弌5~xd$1_/2eJ4 09ԩSmV$bڵ3[n9 =A(붸9JB馛ʺ7޸:}B ~',~T3Daw; J͇~h/_nHu :ƌaZc5 0f(J1UK^?ֺW_ܡatҥ:}B ~H 9Ռ@$@BYr D(8p"(8(Te 6ؠljf7n\~S-UeP A٘4$B@#}~5;!b G(܁Nd@͛g(N,lĥI7R}~5;!j+㪄$8s&+3U1sSx! %,^SEe,XPnmڴ1Juq\ʉaKU0_AH !P"zzY^|Eӱcrs J h%RbyZ5)!MڷooOn3@YZ".̙3ͮZnJoрJC"@s @!قoF1c*X".2]nhB@bӱB@xJEB>c&M^e`O<$-*"(wvֲ1F@JXO&.EYTQ^{5WGnyܸq,l„ VK-a jB@䌀ҎB@Y￿-9ެ}#GWGjyٲefڴiL{X~iӣGj- !skzB {> Ч1UQJ~wlSzrEk׮@@JX2γf)"E8sV p%[ީj~P_7^z9MݺuˍO?5s9S ! sXD -H\x &r2{*l/ٳgÇlNjb#<ԪU˽IB@$)a :ٚ*C 13?x)RZT!dA>殻V0H.3;찃e]ܛ,@O !AVXa]w0*MΝ s5L_}1b8L5+a'pBm"@s "rJC~~O;0}9Mfm{13eӳgO~U %=X;V#HZ!pMWDx#,~mrTe!(_0SӲaÆf6~-/bAq'nw! xM[D\y啦^z2Vj AA>}%v5);t`X.Vl^zkmi]d o޼yrEiJ! ĜjMT ~͌=ږ7"(,C_~e3g-ZhbM)[ow}ֵ{vW̘1cLw! DvMZ,R'F|6KC5n8E@>[n{O,X`^z%CnܘԴ$CR")a} |'?.D1z֒E1,˖-3K.QZ{55`\}H8R~hB j`9rT2劀~;lsQj֬ih(l,W&r)ZMd" %,]D4~}F{ko߾a$@P E*`~7ktA%]iѢ]%@ l2?TR%6r&5lF0} ! %L׀%G67t9C V8on5kf~8NOsBB[ySF ӥKoYkX&L`㏐LBH +S2-Zd䄀Ȏi! |@`֬Yw5 p75l? @5:! |E@Jq! !wߙ#GZVU%4h`rKsz# %r|U>|k[oCh4?믿1`R9jP 3_5(!IԫW(6,Wt% @|vڮcyЎQB?ZBn7X;0f3,Wا~Za[\WUVNְs9&1zqCC@JXN&,JرcM͚5^a_}ڵn4I5LIJ7U&0CoѺuv6! ", UD{ϼRtB=nݺSN9ŬZnK5_~94um۶:}B $n "@7l]믿~y,\$$qņQѲ?R5C!PR`Ňe˖7ߘڵkg ԓx+j~B@CB@x|߿Ɔʚݻ*7onYg3s̬i! C@ %KL?>qmF/y8xA{,S܄Rt)!x6kM6ɹ](.X c-,W믿ijnB@B@J.! \҅ذGy $2UYE@JXdO.…ӧc9&kY:K}|Wg.u@WC$ZG*"? M@"/N; 24k֬cKfͲuW]uU\y!,a)#0rHӮ];)`C6mXoqmB H @Χ~j;찒%nE_aq;)ab%! Dcƌ1'xbFV<.ڶmkV\i͛ZE! ₀IC7|ѣb-=9s&)a;皱I&*UX{OT#i ~2O]+.R¢{4r!P2R (?&t@o7V5j_s5#0C,f[o-{f뭷`-{?;S@Yz]tfDQ<F*@FeF|AsF֍3Iln>8MKsD@JX"O&-rCVnZ_֭AWō |G !?RX="!+]v8;?q~H R’r5O!'|y1o&kH}!/R7C9d{>q2Əoƍv~|9묳vG ->|9lq&y`Yx8y^"D@JXhG/nBGu93olhѯ*kf̙igrJN=וmڴ1"IGࡇ2[mi\=2%sB+N:s̭jMyGm> %P$\PO?%ѣUڰ=f̘1v; Ξ=ۼfȑ馛7S$ 0zhC $g_>{aUVN-K.ľ<ѻ'>l榛n2믿R&%H +)QK'NݼnnٲիY{m>Vgyܡ6iwqF'N4P`b?MժUMڵ8CsX0ky n#;ZcU$ }Yf/&-qÊvum(Ä\PV 9 4/eI@ѵ#}p9rSu)(b\n~e5,=nzNVb' bysy?x,SO=ZKrC,>4h@L~[κMVh]xP{"^{c_|EC)EI,a4UTL1X>޽pCTVM6Ě1s8Cb70(%tgl-! 7& 7ENKPȤ<餓,Ynq_i '`M6\5:T={JY7DwZ ޱ>B+6` /opSᢜ5kV卬J駟no5~ J} } *4pgQ|Њ־cSgXz x67 k…f6+)VK TS=V[zX}"Ā M>(z~~#yqw y#M^:(*PɶDPIH[/I P٠4A1AZ7 $5ʺM:boZ6+a ##<2e4X̙cJI(~&wd 1+46(YliJ2 f*GzRn(A,X`&Nh(mӿzG7-/ǒo[A*a((_dl9tK/zlD""c:bgRbzahZ5ق={4ݻwUrkp}|6Cw7r KPJ0ޣl 2l4|ݡr0i$[DbgRJ} <>ۺt4fذaVZf6kvѳ*Uh % +0rҥK硸}GfI ̩@ E7#Aj<A R ?guPcew3_ŋ*R¢z4ZRܳ•u>.]jxj CdNPY5uTYDD)aQ|G dJrGąSO=u]ER¢v4ޜ0a}m9rC;}e^)`ިQ#C-Ͱ |X̏8∰1BW~V7|Ӽ{:!V4.!P(O榛n* W {iLq V&&/}p eVW o>- +TGpO4h!E"B@0U%AVzꕤuJ.Iǎ-e]f̙jժ[/#5I]1Ԙ矋iN JV)<5pGJA/<{ {.#]{V3~mэCvi?ܓa΋(9RJ~ 4/ \i^"-bj\ j6gP-5j( b4~^w !؟BC5T55j?J*%:$g,X (A>/ew6-[,^H u V![CNo?|˶g|yuי7xbV̞=EPs 1XBVM̏B*M^?(Ӎdر:8 YWlQ̶tݑxgmLG_KX^l$Sɺ=cӯ>@H +9J yM6A#BL=ܓqdTPW浢sjZN@21,</;k;(+<ܽץKsG~ )>B/TEa"AʤIr:wÞy;T~K-:~RG"w}+PR aB'QT$u So7'xb,,Rw q{]2]w P>rY%` w #<~rJJ8 3떹!kٲeeJbqd9sS+(?9-;{֫Ќ䎑º\\}* ưk9^w?/ne_|ŶdT=zyP$(M;bqf6C Oʹi(TGuep . >7h,IJ5vpSDbҎ@$&&MX%  ^{eEi ^[dw}K.裏󩧞2mvmN=Tw7y-bwnXxbrX ;&?~x饗zƍ+C YG/~Wf͚<:ey衇zhG=Xsa9_>ZӧO[rM7I\pdPϞ=͘1cl;24g{>d>fۗ'.rA@JX.(i"кuk"K#,G}.ATz5XlPr7} FcE6ؒ%K2ufy3ΰ\Q({(Z*Narpl'N,? CbpW%oNT\ZCd|Ui $#;P.`%<`0 s]\} q\s,5Ap!x<1\(e_~r_w\g ý[2XR w^Lg rG\j&@IJPe%  %7]v>|It8mk, 'pe/Hɒ`%!+'ARؾ}{CX|x07j>|} ^m8EԍSv8%16Lr٧ٖ-0uh;fC>б[5b;#j%/A92dʵ.I!uŊkPc1 _#2u=jwJRi,)5Nv•  Z,)aEC„@y9X:u)W( 93H<Î7vVOܚ۲ #*ζt+0^)T'&((sXܮLm@Ⲍ`Rp#$pCpCƢG@// ׷J{_'f EG1dZ]ʖ#U>duB-ۍaH("OHOC!ۘ/biR _V u ,<(<| 'tIc-Sr%bE٠3l&u;VVbpG7k,sqǕsIfÆJ\Xz-[} 1$T# $+Õ), ޸`.U$F|:w#gp9$!u ĩIe6U 5a TM䃀|ҾG0\XbE8{G(CU3 ԘG'pƅ3u^HJ 'tN<Č5v)A@1aŠcC 0χnwKCq淟N@IDAT3hSfΜYZi- (_)nf>,A@bӱCL?XHu/n67TħSAodޔdP\XnK2\}qq 3$/2# 0R\;hC*hC*)6mژ1HX!3bUW]eUH}໣DܑAAs駟6tNM"&_:BUQb &]R8"S>) U}'ӖJ $4w S'$vjk$N94o-*?Hp>Ī(ӎ~*@H@4/x < ԡ7'u֊"$?x) /zlHbCQ`kS ɐ$#>0:uʯs-D@#PΝkm wĴd-YlR"uHg.DP2NThغ=3uI?V3\7mևtY ˜NC]2ə0Ew#NDRC^yy\MYw_JB5jۘbqX8'*VW駟]w߽@3)a4 զQFSOͰVgCCZo^boZ6F?0>c -Ib$j%if(a;@ի{ռډϤt*""vuY kPȊ(e^3uT3rHs7{0*yo[ cd= aIyp'1pAP[s1}t ܭ[7SF SEx ;?PpQUAzK;c1lr8*.޴A(a>LPZ&x=HL$M+V|2 {?QCw …Q.|2dqHJָ>ZvnU #5 ^lmy!P\wq]L9`&(#d&Q;cmٜМsC@JXn8i <1)V`<,2ydu茇^(v(#÷Dt-\!|:|tɒ%,zdwپl Fu EnD7(`7n8X:!\V^% ˹W^3r'IHd5h#BC! WYN=TC@epO>.(*P>Ym(eY~wޡV/lj^M)~ٲ۴ic˂knݺ%;^ wv%B@JXwdgK.Pq=pd9cw#Q(]#!ոqc[_~N_Ѣr̙3-?/ wf|2r T p0S?O֚-Iq ]NتeֳgOk:c}"a5 l1b۶m0kPT)3k$D wܱl뮻T^:/\wʂO0$F@JXodgvW0,}dz4japを }8-'<ʠ奁?\`5/nJI|sٙ9YqP8 Ąm6χWJ߃5L"*C:܎A{k3۷-.XElW)a;aq.q) S8_[neƩx>T 'ٺuL++J $HrCࠃ2۷<$H⇀ Ҵ ).W&Ci6SLqSC~/X4iRn}e_*e)eTY_V2qM^hnoQD@JXZLL)"ޖ؊`½ZYG/pn@#N"Rlv0px-dϛ7ZĂ;2q(OCcO@4*!@AaqqVQ,X CճkZw.!`"㽸NI'STs߿Yhys?H{)a?E DdMx≞€$3Fn^[%sO3c 4p @7ٺ͚5+ـpöY$wY9k,+~ 5RB}z?834M_eA C*nԗOݤ F͛5ȯb1еꫯ&L6ugqsxQB@JXNGj:(&r(zs] ;8A 5g8DcJy5ɪsm;o,GiĜJhȎ A*Q(Mّ\Jx:uXnIेB\$xie .\0'۳:oͅ FXZ͂mz^!b:;ҍ7"ou#e.qEQx?ְ($P2勌SY poёN0!C*=??ܑ[ ;N DeW_PQ޳#vQR]piB @\\|v{V,1rQh;:tH +왛SΝsRN ʕ+W_}eׯu~+aAu`!6 +,G?b$VRFy,ŋ\` L00! ɓm67Tgڎ(cd~6; G}A1922Yr+ROJX8K,G_~1ሻI~./0BMܯRo̙]vw5Gm*b΃cƌѓO>*ZY:thO^zUҨ ZXBQ=}E^=P&/BPoqs$o4r\=~{},IԊ+ .φg׮]mLU1M=ݢҘI7n\ rX1,}g[nYT.(;ص裏˗h1H xׯ_򁥞&|)Wo~ o޸-`*$ "y-~]XPNϟ__rJ>xݻ9餓l)BFi*jx%j2zզ)acv(5s%ie^<48C_S} viu5r(&| > W# n,Fx)3\X'YRH<K/瓄%,pK,1<8p` Gl˖-3ժUq?1j3g{c_m%XHBPpRcǎf梋.WM&"KPGqa?cӭ[7SjBL{1c&NX2,J+3"_howq!ht 6CLJMើ9/n%*} /*ƕ ((\~>̏0xx9f|_wq-';r+[vCa(a%QncGy8cg%,\#V! R$IJjP7JbB)?(aX{ض2u n LG#! ŷ=\;w6lU>m7xZG#u)a9 :YB$4W ﷥9 s A3.}z1GdBwQ漰QG_6n7za|Ȗ.IxsɺO9bpr!iuN:Yfa J !A2&k,^ނ+){1w^]XnBL{{챹~n޽uy} )aApM r$iu C\ n8[l5EV$$Wa+eC=d.rK ;a`q~e>bXqI -Ѽ=#HWQam>$AAqoz Zzj/=((3kNCk> sXÜAmӦ sM`~.,0#c缑=X 7Z)aaV!#oI|HZa#S`I ־}{&FbP4/0-7|X*826&gvXXIBB5\S3E?#)kBW@ nk7nܸ"Zѡ~! %/d.wg6^C"sG w1I ЋH7AYpHVN8w'|N/6s1CV" tv- _o Y أG6~a'D駟ݕ)ay3#pڛ7IKxGPҕ9ɧB?O>1dJTXltz%\A,<믛bQ;'<_nQZNm cǚzR;'IEx%TMmï뮻0zhP" %@tXyatR|``+J Ғ,~2[R)%\[Tr뭷f %sOˈlTuܚ>`1d %,<"#AFLA6 4B8x 7ejI z^/ K2(7hۑ-܇Jpê*_olP K{"Hx!CMm%}ь@T@@JXH"_(C-m6Cc!`V*pw3gΌչHd"hժ4H\֭kyXK%2u,ܓ&Mq]/M~g˿Q w}wKhw% >Mrp %,!s=oXIoI?=xgmFLJ^zWdPR!,5]Jvmg6mj{1K+1P"|\ZlҖ?bd" C+X ;8Q$B _JAWn/Gc=Y^Ps+`N;ĒTRsBI9ܼ(" %GwU1דɔtYxu[C0ތ'I=D,7}A1Ies&ߦ￷_~yd/oֳb<Ƞ*Fŗ^!B1CoƅWcP~wlV_(;9[3f5ŴX"-^>Q~SYi; p%F4H + BABꪫlJ8(8|y`}C,`ĤQp R|Xdذa_~;Y^veeSNrUh%\2d=lP)[bœ)@A":6sP(Yӷ4]I>:C}gFao d³~F$q^zq^ziƏ Y# %,`oL|Mqk.W >*^l( ΄ lmF,dիWϷr9ꨣ%\bZrE/ ص^k-_q+rF,_rTo~σڃ?_]]%i~O΁@%xc'OTxw'2:/"{_q?؅Id +ِVZ#;oLHtqsC 3fU 9Ioʨַo_[Í}8ȁO Q Q( 4a5;(@?$G}d3Z!S5 TrCf CA\/Xq> ta]3)a ъLP+\~ ^CQIiӦebLB6GH"BDJpCt 7|X+I(6I X^|EӱcG߇T0gP8xg]+sͺkb')'+W95,'|IJ/Ƴщ'Zn?` 鞛I,]Q@iѦ5+_)^g|\$& /"Aw pe^+{) 9HX&xA)'' 0ņH + 2qX -oEIF.5b&c?-IMZ%97|kt ]oz%M[+n vM.] n_#~C"P:E''=[t͆QhMlTZ5>X" Kukv ưp̞= c!SNQa1l2#!o:t1Ǥeh7EɪnHk x;c"2Noᆢl|$8zR̃0ibO- T B,yf\A|[:ul97T/PʠE~m 5F1{f1x},Y<<z)(A]G^rr5XJ߆sںA%=zo:DiL, 3ݏ|2EN.B汆eld ˙ 8Nji2݄:nZItu,d+wF% i~Btr=&~(n O$|`eu}*5"(ć-XWF hkX0Ɗep2Y $(G ?6<%zv,  a saR4s=-Gc!ލ"9-I Kι.h]7HP-YKTȇ)Ħ+a1/-{=7ް1HZ"#W.}dOX`wV֒I6\v(/) X1Eݭ񀫌{Ƚo̜93xP"+8[OFK.B\aHbguMnq/7l{oÆC7!b6pÈa{dap`U#tRU 0*a̟fjg!7~T ,jz> KXq{KE@JFugyu#J`]q6!襗^|'m ^r07ͷh"9!dJ&E&)a!m?IETk:VZO>Lݦ!EO*t)'$A=2jBЊr?..:\a $@x8L'sSSځq"G),g~ A֢ARu԰aCF*m!d ▣v.U$P̦LbM݁VW%! %?l#2e+ahٲed!ҏ7xO0Qͯ=\@BxIu SO=4^zYwu Vm,I~s̱σ$̷sV C'}eDf>IPZPRK sL_^x9oET k`N;d-[f>3gvL֮ Y:JQ"*)aa<+%԰P &I%S]  Hm{-Vi)s Fnw$5q;$;,S!G"*)d)y:+Vtx2=|oVO#@S T-;wTH7:u2V[Yŏ C74IG@JFEb><-UI EH\=6XF$8ŃK%QP¨HWaAɐ!C,~Auh?Xe 5iVF0k֬i֭2frtE2h(#+֘ &yI'Y%=` ‹\sw}=8&ҡp뮻E뢧*+[rX̜r;E ޑcTV-"0e y de$Ɍ`N/QRš7on/_n?pA?UTd7ܺ%35\un +믿^Į)a;M믿6#Iqi%%  VX2tPECWҚ01dxRzj^qcw7㨍=(,RߦM(X ./ AZ*?~7n\_]&%! %;,#S,tzZsVZvy?[ : F,a2*D̙3Kս/r P9C0o-lrm}Fr |x-QrG:sj*0 ?={vh^q697o^Z %v2Qx|]!%!:LZӧO/B@J .06EɖC`g^ڵAr23K qJIK0jJXm,uK1 f<@sa ,i\쳏NRO?]ĭI&6SrĈeȔ<#l\c={,s_͠ALfʎ ^kS)̥r!(% L(Fu~Gv6!LâBQfΰ漐(ѽ{wo"?%: s 7 wga>ss嗛:̟?n?m|ٯ?#6/|CV4|A5ŗǰ`07 \!J걤rT9jHܸqc›; D`GttFsQ c%D crK[o SN/ze(a<e9;gJ-Zxl vy( 0BJK`SN5sNj{-Z30FQȔzWi bvhr: D;y5jAѣGfC2cɒ%f[.z!uwܗ rg=0<^ɬ7׽[xLHbQ{{'b F,a zȊpSR9ć;nKCTuւbŊN& va樺#k׮6v+xYŊ{G?ִi)`߉ +ҎCS)a < ta!wLwj<ܢ|#Eʙ~EKQXYSg5 pS7tSk'))ac#̙ca XҰyK*GB1L[)] /=ԬA,c Cٕx!H G@JXEHw ܓdG% %j xPΉQ"; mGJXݑ nU>x#Jܹs7g$%,@8QrFR9N<ߖ0\QW@ Y*R@Э[MH7Xrp3ewڬH Qv )~^QVk׮66ѹx0,qIVLYInf-2JWܑ@G}dKe캥<\V)ac#F$;A"I,ajO$7&Mz+3ܑ2zs;A++(arIf)R2B U5x#9:uAq 2(7a+Qn3ّ9uڑ%.)_2$I0䏀1"yk `/;8_~ڌ|Ä BI̚n΄/6`"tg:uUVU?vqaE(0qvSY{s;:uYRġ;cō!\<=퐨' ]E\˴ peV3ryy1 JfJ(Ѣd/iB(!QTR ,IIiEIT"4f^0y?{S/ށ#HT|+67ĸzd~5 /p2ɒN C7l2v˖-Ř1cTXߺu/L2ԅ~JǙ(L{S^= Y]12ኢ%" 01`is;k BTRE`|CN.&S !00JUJ}G>"J3<6EU(hSđ +p67G.N?)X% pM7M{NFRp[0`0wJMbv7o >s B%DsU9묳ҥK*"b٬Y3"M `6`ѢE 1n8Vp¼L8as!2D{챇СC&NsQ!F!DAc1zeE mGuTIK[?Y_tA4.;a.\+`ֽ{4%zr2FjժEm,Ȉ(C ."4 yvDeBKr0`af08Kᄏm= 6,WHC ;0Qt )BqGC.miݺѣw}mO;{[Ԕ|| 8ǀ!œ*U)9ZN1`tjjq+n f͒4r,)Ӟ]Zϸׁw̵C A఑5KZaЖD/Kyɯ|5Ik͌somG(+dVuE!t8f5a+F~e4 O8?裃a.Y7w ovn/yr c|ҦA /^]&vd){Y^#8B >ܖ^\H6:Zdu" ĺuR7 M6i-$#F7.'9N6D;N;|&bc6ܳgO3AšFNs9G,[,9+3gΔb~ }(gUχrR\40c103D d)^{%}Oq,@i Vz Ne|*BfeSwBӦMwQԩuѡ(}ym QX!Ra?痵 4/=cґV{{u/ޡsN8Ar{@aY{C. X:acv[iX\4SRL]Vb2n D`1 8]S@ [`A93%\"7$FAx≙j^u0={ccǎx:Xj"ھ[%3L|s9N=ä(]+IP`9C(1c5]w<}WQF%4П۶mRJ.+F#VX۶m,:jֹsHʋal'NpNHb}r e/ڼytdC%{&:p @Q X:u̓!JRtn9GX)X(FFDtg5p-0f rA'&kwO7>JBгqELA$3t<}<1`Rwy<"H0طo_ILaTWUϲ %Kd(S#jmѢEڥ4{Rq믏p/5b^BmMrPu(Cȑ#gmCTbUO/YTmY۸q3@IDATϜ'oD 7rwۉ k.>4¿MA3Π:v,Xݝwy$`=lpqaXi2$_ ca;¢Ki(q䘔>ϫ8R ,W^-gIEƬ),󸉰,}/wpc'k}!?30DN Dn : SעB]cudމ0, 7n,y"J+֬Y#-"Qhj7=֐$pŠ(C\a`-(zxމ0p^{M*rTxGť^*>׿π=c{ڿ)bR#,+&d @xġFG%6ZË Gg϶J=5_:EU9aQӔ,I >7C)"!7?,z)݂Wa=$Ko߾ey׋W1 u_|yaܸyPu#ȲSNbԩ[o *_y@*߿ DOv9_t[4C!",62+n瞓ku䘓7)?Q5qbƍcȐ!v3a;-';U xE~Gr̘1&!>l,gVN!x1}m#NiaݰaC`Twq裏v`N|S4DeXj1c "M iOe5!)ՋDf>NVZ ,{gF jgspKԨQC뮻e@?O?$*z~;{mkNlgW;+#E2~]pQ.^~U[|]r%?9p@QN߸Ie?miqڣGqzf!qw?/ǁۼ0!Xyڡ'vEFk> 'L Pk5seyt {1 U qpn"=BD*]tiUI\K,KK(8*`"*FMG!9,]toġ*'=f .TYI"0q(V\-ZmkG`{ ')! "9O:$i;w,.r./8ʸHQ&#FS"wa#)bѠA_ܰ_xy衇5CORv= T̏Bl_l uV;VWXzɒ%]t)jժI)^~eɑ] ίS?NbM'8Қ6o0 F ;[;:qѫW/9qʩYa uV{.շ~+A6Le/O x6!"OX9,{Y  5k&ۈ~ͺuc=&OmP wU>@Ć3"̭8<$%_8GlJp5(^ ~_Jy=Av;[Cݬof1sLyHC$+$I qu/W$ fe}~`c΅@ B;p^L" $cL뮻N^2q}pppuE*5#pHLx'ptDWp-; ݻg(#0ʄG?Ni^uIC Y&>E:c0p(5V?rXpr  c~(8 ?:Nj$ H)R7 ]b]vڈ*9[T:^4??e[,ڦ-^Y YPeNj.ı#yCT!R~vY誔UQ]BqKNr >:I " 0O>8~"ڴi#ca1Xu?W_( D+V^tGLvn꒔q0f8`bx; ڍsFcLag@eO'kӵJRʂSXj%pG'_7p^fQa> %oĐaVqhP)CKU % vZr 7 k5\#Oo1'4:R>" ŋ$'1d!r&ӣGztof9|& Ĩ¯ﲞn+,Muq 숯^Yd;@ƕ5t;8ai^B!5DXo{燕 X:@p2[h8:xxK5j,-DQw-"[PhA Bb n%Jٽ{ ˧z|3[/Prq:% 4ă :=S9h"쒎y `pww(C 8@^h\|,ZA 7pqU$XXp Y*/ <\3tg^c'{D_}UX٧6_C:?8\LzI!h^bHMfMSċeNQ:F?6 o9 $ աCJ'ʣX=R. &!@ջ+գRO%JuSItpv0SO="OSJE|͚5 0Ӹze T*O*,kHIX9;qtpI'(8_AT$P/qrWg K1ܘBuobtAC"(XP|gX )00jq3zIaxSVaUρ@|ɞK4,OOmJG(8>ƈT!B2(1@lGEFT9*gNn+UO8֥8n,K= s'᥆9!N*qd0./n.'E&YQrM=I&aa; Aּqyj ۓ@qPųr+gE{ B0 `b=/S0DN\`I`Q$q Zk `*089vOd@ "l'.paߡ#*PkaJ7SUݻロh" :Oa=/A$u֙FsM t֊:E*cpd@"Z9 nۉ_Wґ_e :%ytܭ}/xa &'^9A0 Uq3tW氀rպ-JTf,X "SQ8b= \qde,"Lf8bO+:W}< E>d! F1]jJW+! 8@"I3࢛8Q+$a!4pFO9-;sYSN49U "* ^zF1_džkt !8h;Vo޵G}/e3A}@|y $. I:q=.k~Ү];1c h4^eĉfR~]Shm$hBQQ'E Ÿ]4mTUI{r 'i88v+ mZGCY0Gw -bH;v3'gwR 3`đ{%dl gD)k׮o/!'' wcƌf+p=z8I 4e \_K+"V% egÆ XT0EVZ5XavGznN/^`uץB|MQBRex 7 ֠Aą^(T~TPj[kȐ!#VGN.,N8gӥAF"{W ɓeCc=Qz2 .*;H,z-}FSjlݺUWlذa*z`"b"\u]zYׯ4`كիW˗/Q`" /`0=T~}i%o͈܈/䒢$5 I&o߾umۤrzR\M(@,|7$z {1j( y1D"HmٲE3r1열ˎƤ)8a3D8A$ 1=eQF n LN~+NV5{;6p;nsOytxT&GL7ξ q)o=00_Eݺue/NIǍQM|T bĝ4IGkM^brD%#_1}MSj=Uzk}߫kQO]JޡT-FԥOhE蓑9wLŋp*'tRy;/n`:oW ~ñ#M/^a^7L{G8!NUTlp/f4)+|N„_8pEL1$NE/XFo[7.|L8+<֭%1gy6hG",!3?Z(0a^9a^䟐&FRR[ZH  ;3ΐCתd# .*V\)."i-RaØ9!y  I믯$B[c7 <<{ tZz.[LZbGla nNk~߸j*hQFM=KE?U'U_d0RNU,Ba`+AB@M$7,za:(20#;#h׮KIT#WqqXSN- F,ѤGXp>S [=D @/X^u <g&`1 xRn$~ cW_nƒ\c>3f̐ams3O`b!/.rUwZye:o y8y<6:rGO", dHM9ՁH~!O%HÀq'9)s'>h$ eI>CNn$qQm֬oY hFV,zYgIK'*҇G(},!;n0pȺGtN1!Yl?^}2~~Dw tVWDzÓ>.+aQ&g^~e`v3<#Xh C)G:9ͪVZ>ڙ }K!|]<18cO>)>[֫WO,wJ$iÙg)9ap ]CU"KJ1 ׬YS5Jhw}ЛA֝UrJ̗b4G:bsܤIgYR҆ ,1N0NܣիW: KKt ?ڄJ؀cu0t 'lG" 0<(GqQGTΎwf0g"X3xfϞ-O6",JH~Xm֗$tHtKqaO=T~Ǭ6U  ]&~gl Yf6)^(d%Ŗ-[d@[b6iҤrRK35sLq-ƥwAH\s5bĉ(_GO/pU7Rza*C|I"6*0 f7H,a~7ebf.otg:1OO)㆐8YxN/q$G8͛7/\NEpx 'SqToZF'#|F0aa*C$`iwMA?Q>Zw\3GF-+ \qbٲeכ Qp~⼔eg,B( x3DX D0|DYB" j$ESŇ &u&Y*1YL[9w3E!)cXrWaafđjG (l`E ISp*zC ptǜ):,R^x3`aa#9>" scDEݰQZƎES(#09Yf F1_oVqRO(Ν;KO(|<0 >q> H&ܨ`(kFc",(KQGNH( )˖ƍ+D{Ka&L̴#,N-bȰ%a'QצMg}6j^vb+R vnڴIl۶M]{3t+xXu *_L91GeTɻ8%#FUN.,frRibozu͝;Z3 -RqCANlpU^zI5ş$Vuүq]?`N}a1qNMw'Gy@0F4jԨ$ߢE if|wqIlG)~71cƈ6m2F%cRrU./޼y^s ҥ _~֭+9hYar^> c#u|XJᬱ̒,ċ{+ ^t^{ T[n-֮]+ݟgap|M#Q])2e ac ‹VgȓO>)Au}y!d!=+5"0pKÙ;MV&3t"̩ -s=730CYC+BY ,naY? )NhyŞw{8Q,DW_-}]gVmAqc^? AU?!&O8 MQ1SϽp(~bh+ہdCE00g X8w_ qշXXE0+i_ֱcMwމUV-qQGF6ZRH&yG۷o|j׮]2- p'r饗 8ZlYTLma//Gcp@N:Ius֭@ܰaC4gψ#HU:W0ecLEIltz#Eq·RewA,%1~6;y:B5lq8i>H+ ;f'|s <[ 7}w0FK/BM'"+T>/1*=yd颅xpXmV*s͢ ‚G>b5ug̸B ds̑"Pﱐ~_K/|8rG",Q)$Խ;䞋.{DWb%=o_@8`e1l>J4։N+ũSjժIa=z%Zth׮l!EI]3iǏ/% .3}Hi22mD`Twp;|ʕV; N%7Y愡 1,M#6bħt>qn1\(m]8_p!6?C5Ź\xq;.CO] 7 (F?8qW-@[3`tŒNXcS`wIfkZ!+ts"'D N#F?K  ~M?.mbZVfJ+&{Nx'n8)C)"IǵtRf@dP$Á(Ok"4$Y C,BYׇzH,R>B#6,u bQN]c$O?--eYσKI#`0C:va8If" DS[,-F1^Pz4VE}k0N6gi #N.XS#~5kt/4sL s=*/0>N!BL :If"PE8PdJit:s„ "_ Bɘ'P_"> UU~QǭEq r 0tz{=?G@ i`>W7!꥙ˢ>,]zVq,髳=w^~T,K]9[ož'7Ap{_" cMi c#W_}Ue YB/@*0t 5Ȃb>;Ƙ57}pܹK kՆB~:utzf]hBf>9Qx'ݖ-[l˅8p" ;SA\ 8rGo",Q姟~X ,G \0!͜0z|*!ԩSdNN:,'y~D{bᄜCƍ,|BRxا|`+A JŲ aAax9眂m AIӼ0DXPZNphFrC4AVli]DF-n֒ppaG+ 9Q .OC0!8_~ʄ7n$Hw׊nMV^ӦMa,y,ն tT8]f4Qϒ.[^1z"LF@ETH^X'GZZ*EYm߾}RZ:v( >Hjƻᄏg"lڵhmC]C=Ta()NoSLa5ELO4I4o\ZBA274&?0:$~@9?`0 (+PFOVV!m$9\p٪U+)LL懀YZwUT]vYAY8F3HQ$i8=͛7OJ2f+ZGI ,AAÆ e&+TAgx0DXEIMJI* a-ʌQNj.RA <F8@ %I܎{75!ਣ4%H" %Ҵ= `8a;za֤ZEdÇeEM",H/nWEQkbc.Y$5) whÆ Rl=H؃( ݩ-[ʃ("L8Hi+E 8A }_C2DX+uak8Y!ܸqc8+vp2[_r l`ݶm[)/bIDNEUҥ@G,p:סCJA;cDzd\SyFK1$J008vaK:W0!vKzG~XE&C~!AԠtP^@xť:{>VX!u< (p' SO=%wY\JV^]!b9!f$tH7E/|SW\]w (3 zL caEEs QdZ ^d.jt@fϞ-o"8kN(zIY/0P8CC:6Em|HC P1zA,? C",1 &tp3Dd j8a n#Ǐw]>~Е΋Tgk[|8﬏Yrm/C4dYF')JK(pR<E3tn ;IN7,},q$$š$Q!a/+u/(awD, #H,{ɷvo8aE" z߾}%Z_s_ /E a믿ۋ08ZHpaNNX Qo~F$d7ėuH8S;Cr,bNGΝ; 3H^0I:pU F1) bś~ANPF @"lG"I ˜aF͚5y,:)҂c(,,ۦuРAI&8t[4G'ONs3 ݭNئMћ `E+Hȑ#7,.Zn]^4iQ/{ +9iYg+Pv6Dpea?m!I8Tt VDalOK*W_ntŠO%16rtsr=Ғq˖-S:JeÇ[iB OCH1mPp faIUX s06`!tS ݻw/$w"%_GgQ^/[LL8Q;@-DӧO[nHq;u2,Qdx/XbHѭ[7 ˗Kcžn?kppiI.^' B>|&' i傩f'U/,/`R̟5k~Eԛj{9hvxFKWC8_M^wc\A߹'S9& ^>2q29h% g ssM>̋_*60cS2CKWf9#,+Ttt&t\3I*@QM2,j !Cȱ{M7E,E,xT sJ:a@ z!'裱&8gX!ti>*Sҕ }&GrY/(T; p#Q?eZָqdt, {1zaD,tĈ7C@a&1 F4o[I(`E,Ꟗ9Eq(#*NCXڷoJkDtJ@]V G5R9"5{DQoHG+ a\9`XD*(#~((Yt"M *arԡVZGI"lGO0/#| aI:q'oa/SN}1Xm۶VHNXT?+ -pᄡ|X*}4,h1 hܰK:*=gyFm>2-;" "걊?c䴚@o#OHpb>ʸ?@#8" ݜ:@(oItc'7QpJ!9+"M6W1[@=8y[A>0 >XBK"pF@`ckԨx㯌8QgaRb7Ņʕ+u0 *1-\W CƍJ=\T 3D'b~hx]~}fD)u O˘1c M H>[UjU"\C'0FW5[M4p!C&`=C(Z~q?(+'̈# z̽p&\WptSIEIyє}m6!6[n%r=!(qhm2 ߴ@yo\HBFзI|&-["kxUP~Eǎ3hOq[qʰ9_ujZ _x`dmҭ qzn6gaqA$gPo : k Ia.{YTTue=-T(N^0Oԭ[7Q\ X(7mT$|ND(h-]1l@u8 \hڵ}$fd-Wt"%4DXTq0ڨ H8E+{*n> Ʊʕ+#+iAp,$!‚t D~Hܾ}uYpPL_y[ ^ի'/_^3 t"Dk>"ނk իWK/47mKa-? CT'x(F)%F% @۴i#AIPnX9!f$F"GsxRQ%7\/86wVO=2eD'W]ubgucRxQ0ȊQ` \^A\'+(>#)^ ;S9]Z7s< 2BD G,)aa'k|WN U0X;W\!?xwz26'>stR }6R͛'=BK.:ۋ/X֭O4BIuJծ?7F+18EL" <k֬ 68  Jacǎg$@!NzXAaT b v`}([=6-*kF^ zC806B 'ߔ'^*+Ie5ܾ1>®g"a#Eo@3@IDATP$Qnj3d^zI]}Alw1ub5aXm lv8aA8`.47sBtJGz0o&kx@tp֍Z SXt ˖O xmSf9!Š.:DW'"I3*lq$͛7;'kĎ,r27L "Ә0E]$ƌ# V!,t7`¡-:^]:!ꕒ qƉ38C@Og`Q̙3+}_CQs1РA8'VQK.RG~ZUHH0֥zK:蓍;VYe9Iv n_X6Vw2BqG@IS_ )&Y S=䢮Ia:F̵S FX+|\(8a(#{K?zAbaĴTS3ĉեe65sԦMJe}t R=f#M~VZIA=(}W~Vy&K XWIǼS {`C? oq;VWNAPUV%ToxM8D_az q`a)SNL ˖-C͚5;wJڋݢo<3by"3]Iݚ.{đl:@A y\Cw/<eFavX){Y~^#Ee5\/.cN+0v<6Ec^:Xs4j@Ekpqgx>qڸ`mڴRT!*caQ~GXꃁbQ$@լY3,%F<2p@8ʙdxlW_̀<ap NUZl膅A0tSV̚[E ˛[_ .PR(Jnm`'t0DH@OOsNXYO<80;b*tgfg OXLF{ ~H,#UyI8a;+X\}vkeHJ^  ZV_n nځ {o$\À@Ph߾<<yVGGG&%_52!lzQdlY/2( ҧOʇ]AbUd0 {!cNP~k+~.?~%g 5|Wi(WF!*CNr)NG\4{+Ozp²BqؿɽQ8[h 3UKURX}mVL0'LmȄ00 +nA@`)B߾}-H\&%/\,vwBR,rٴ9 a8t ,*zpNwWrN2s먣 (pَ7 f?|ɕ>}z c+7\ޒW_-K 1A1" `vy4D6:euGhOyy' dvsAٽ}; 8^3gpV " !r͚_5;~Mdz\Y&`o\d * TgmWP,d &Rk׮Kw" q>X2ꃌz:M7c ,nXn*-KgX׶kΖDڊ}+)%KHaV1ͳ> '+%,d0n޼ٖˊzϳ^zK/4GF< @ .L3' Ǐpt¢ nW(tM-k:GR?$!_^~啞aZC~ : tcǬW/zKr8z" g zk  M6+'Jkbxzb,Irg9<^x4DNlp"D1pv`#A) }0_X!f e]a2C3Hw ~M$TB!^{%7|t]T*$:fQzj: #1Np ;0`#d,͆Xyj4jHRO=XuY kE2E/̢f%7Sjܹ%9?\a U~:z衢A Ng]h ><HX8ڵkd b n K1"w2L,d0U-G8l]s _̞=[\R&iTج 80O;\-Q֑)QD š{') ի'0ڂ cVtڷoܤ1avv5`6LZW\)NN'UЍ?VpX,ZpCN{ww4#lޢ aQ }9 Jm}O:ik|n:dv`I*=i?3@l/ϣnQkJ&:z\*q~q}*jgܣXb8U9*{=~yPc-]x;aW'. ]٥L_a@O?]:nN#Q }3CڬU8P*\_+K/I TO'jK,BKG]_5>ۂ_28nt?+ Y|yһ{/JYX_BШS $W <#s}wJּJݗ_1GP8T_|Ey_}͚5E^e=Rc 1ONzGH+V0*e$}Q&͌Y5n6LڠƷ:gt[YL=?~mMc-z_,wsgD:m`HÙA6 ,c ApOx jߡ]\+Xyoo]vi>S|Wk9\Mk,;Ű,^ogV_(t !Dp🃟;^F5y ɡ\j2e ?%a |cޓsNM6isMx#%q$%s='u 9 T[ ,f͚I".iV)(iӦ:[ttY$S3}&v^˶p# !uA ? NƒrdC_~eM6M K(/X4n865,jժ˵O?-.rCXd5_1Wp.altN)pJj  >iՏ y,N_c̰fP&F_54 :2p0JPD tg]w+yA`ʾAtH_纇p͈s{=?0>QS3gNd"v%?!qA0aN`au:o}3נ^mQ!^cc [hݺtƍ?Ȱ/@0'| Cz^Kd30C`%Lzz-}奆 ~BV :-p G+#¼:n#" a _{ƀ0H?KǷ,C// |P `1@$CSǹᄽv0r 11b'|Q?,/"ϪUUDnG m۶M~ի{|~ `0L9/)Փ׌(Ě 6l`"ܲ|YBU='̋8%[N?~=uz [|ϞIN "9525 ÂM6rR ObY?\TO@1F ffy^pԒOAԐ L ;3y0Oף\a^`tFWbQGN Jf jvu +<s`+%_JDY0?`l/ < pwa4:a,-cuZP K ]s5׫N 7\G}Tz'˃!w%n٨Qd@Y/c6hРy._\{k- Fǎ[#;.,^X|2`vo~gI^)-cJ8qH͢[ ]={ }إYׂqfQu:mڴkVCeoa=Æ կI*;YoVq"Re=rWHY , &`M6O?袋c׿8{?˷_7n`͚5gy4){衇 ^[W_}5jcAoݤg A k _`3am߾]|>O?J$i{:SLLqիj7|#|AP%3/ לڳaI[SAT?#'=So߾!{ŝwYgob:tTtMC},XƟ;5a {r18JM쳏3{&U[T8wuQi@z1e|q(G._9e͏9Ⱥs0 y[ {v^w>}HI}ggPظk)˺^U=coѣk.*N]g:DY[trkNNG(ы_`,Zx 0NV_FM ʅ W^ ŠH*)ÆxL&brOy~&O,GE^dEIdժU1z'IΝ_(K@{mB)iDbG^jMzҥVZ+ Ӂ}Nơk" ڑ,NnOL wq.7o$ AP̘1n[1-wޜw`p'mtBSeyR'L95u'upA"QQ$C@}fM>LbeٳgS Mfn1v(ϸf!-j}oӁ/-a׿h7|\6E< o->-L3f͚C֭j&L09؝wy2- 2D]Vn|̟vvp ' C:օ^(MV@]&M,`#DGX~`/`g,*_-["awi 㬻c裏Ĝ9skss7N]@0GgƯz_|D &¢amH` Ib4P VX,r tuL4E}grkN'[Ř @uW,/N-Ƀ큘jР$$t"+p Ky:vR60 pN/'ӗEfݺu@:1@2Fp ?%+@XqSd. 0@ i]vk F3s;>\X،8W\!J;ZSXb Tʽl2!d7J EWo‡fmb osTJ/[xSOe﹆S5m4I p rs08yCNBȭNr^!z`c 3 BIN 'Nm99 Q{6nQZ$ ςp"@ɛ4FIFR'(?yb[^RL/c{Xgnǁ.qʘo }NJNT%>=P! ~K5_9JAc]A?p8T\o;,p;ysZQu 7W|;)>Nkq@nbݷ S.؂" J>nJke}n0?H?6Pj8Pco!:Xk #,H~%Dj?XuD|,_Q?bQ3t8jq"F?"" 0ív'.uF}yms͉ *3!UmE,\'^bH6jV`~0؁[XI t˜r`Yf-pӲحv__xګOy3eyE`amW#b9{-)8TamG1>\dDZ%rj zE>U8X(CC?Đؑ+8$ 8M+`R-l]9!ncaT:uJY K:&mժU+ R#3{4@ix8ɣ< 6#c l(1`G::-p1н*.wZI.O1h >e!|a˽"'6D])⍻+eARs- K3HltP"ἢF('?+CosX׹%8z^ΈP'f<:h1ĵ#NpN3000I$K4@w5~sMa3~HKUW`[0fecCw~0nIޔ$FIRօATԐ[|đ~0gԝgʋQvsNkiL:`lu5UQuF]a` Ri-sK!W`E ZO]` ]x&nř;cCg]LbB),i`5!/4U\a~8a(s{!9na'tI\ `ah+565|3jUp0nZQ\aJnG;f0DW̥;8ap٠ dx =GXy~N tϞ5 : g0O ?`0?KǷ3z7QYSKOq)09)p&(#p nH5-D_|SN9EYF@0@0P~}F1$eH ]Lv)@D.0x8OlբPSF4PAȃ+d鳸J$.٨ d_}lqg8B1P .iq/6DVAl Yf_1 9}8/= ~l_nj4FaYG .@P`00ydSpÔ8( (^|b̘1sǀz}=ZuM dy9e6?-$+1; "s0.3U'*KIrIѡCU]DPDj5D>CH\v.c._ViUY;ִ>ޓF=?~mޚ^/tMy՟Qt+ƍ*eNv~0i<0`O*k~/֯x(IZSϨ#յዏu/ibTZ~׳V 3 + 6֭[?~eWVlj ϭ銽#-P**',ӟTΚW{>/ػB89p@Qr1 ,?U"I̫`+)np_PXy0NXQԽzq T~BviU9oյW/*?1O8TBrajDU=~Aglqla "شi0EO-*hidI 0|dLTa}AT|ذa[nN ";G*x9B4pE< WI_.kp1v˖-X u;"M_W_2lkCDǫ!d`0`0.{9ѰaCs͹#~ZJ$`J.R)]uYp/B&M1.GB J`̙ұcTIN<$Xtʕ+jժkG۷G \$mk׮UUxSh si(UzA춣GOO8!=z'|2tN;@\1ӧOv9(p崰S${J_0g9p#ay>JJU2j(y뭷U9dʔ)'Oe 2Upaٺut]7o.ӦM5kJŊoSرcejCTC塇R݀СCرcRn])U\~2lذxik9(7#V/\s 8H pTy@ĻcyWo;wGJ*'|"rRܹseܸqҷo_yܹsr)iyC6mȁ'ѣG ,_{nMF)w.]( 37 -anIHg7pJD7NZ?C&O,GQ9sD`Ν_~R@te`ٳRQm6㏥rҭ[7ɞ=Rʗ/~7~iF?ݸq4ku%L.2yהA,b91 p`$)X`RL2k_eʔ ]AZҼwۛx-n7C$]pjB̙;V-[ڢ |re˖DS` 'o͚5 ,@Sa͂%̙3)uiӦa7oYf)*f.GZ Փ !+?4I%̑L)]=G KǏ{Le˦b(QBʕ+'6mJW_}r߿R{ >YdINo ~ v/\PST4 ;HĿ.C  !(`񂂤-+f͚U{小]-J{AW}`dvC?>lP/"Ac[ B$@V@իޯ6Yt[ėxBM%)љ5LVo>F a%{O~̭0t8/ wg v X%b س*TH^z%y7?TA`<8#eHGŊL r$_~EE·oX`uԉX~R2ZX5,D$lT[SZA p3Kϊ'):|Kf̘~ΥHg퓀 `w:Kq)a:DJ쮩oo'zJHI$P.<v M-[VjԨHK(d„ 0H p9 >w ۯxwy 0@Âg}&Ha4qDKP{ذa1)8Fܛ.:uj(7" BKf}$o@n;/oPw!^!:;-a !XVTc!zxJ CJ XmEJoD hEI(|nZ>C xO-PI pJ# ?'M#bh aACBZ',4HvJƚhG fb۶m; .D[Y95BP 6r!k׮8K.ra4iJ޼ysA*ev"vI4 V/_.ƍsG Cq99pݻUZ!(`PALh KxA/^,ՓCp1#]9- x~* 6T?n=? pJ#4qʱ;~]J>}h˖-Uo$oԨr- '7VKym?vPąo•Mv]"b`R=u7pˑPΟ?`Yzu7qFeł L}2 r qU{* Paq$@@nKsr0,G`fRBP2gάB':҇`K/yI$@@&" %00(J$7e˖Iɒ%[G7ceɒEΝ;p6LzQ 1 @*)R!haS` *w / }3v\E04T\Ubg"4Y.ƹ"駟ȑ#BZ+WL;༣%,8s͑fϞ-_Mn 1JX<0D\c\0k֬ ;_˗Ov*@18p+WN)׿;wVcԩSe*o],Y B酾$@$XIJ[Y§kjI;TH8b$D .!!_"'5N\TR%"22,_P`4ě?YxH<@.400Q&ˠAj$LK.UKbKF"-3ʃ5rj4~?iDaxoݺu2m4)QV\t$jsUt1pm\ 0Xe$==Zz<0‘sE9++P1W5яk}gI#pBꪫws`˩_M/T_|QUasPB*7,b_%Șqi@IDAT1 π⥘Qp D*6PD#ɞ={\@Q ^\a%|뼦zpsO!H $)G;Iir_Fb Ҩ\sy9a0|F)7AOQW)`(PxqUf%̫c @"`/UDl 0,9Xc۶mSV9,y HV"OJ>w@ X0%y_}kN3,- 8I-Dc:?=,? HX GǍ׼F `HF>@*aiwԻ#@t-wGxϩ%Όw@P $R3Q?@*aE>͛oYz%e˖"EO<^j'0u[Ͽr9S`C\0xeR GZRۖYCyϐ j檏GHR5@ HE"{Ս7bgT°/JjaɩYS)#Hz c$5kmۖ}0R % !( OtTF EJ;F\/R&&*"cǎʕ+UV^6@ 0%,ƓahJ2rΝ;mMm- xyYf/R ˙3'-ay{*}dy|4ï.XH#h";1a 5;tvljNoKN& loAq2b>&~aXK4VTt}&ח]v%JNW$DNxlnrG&$X0w\yGhMZM 0@s~Y-\+`VigTͶH[L2Rxqou@`087qAb!"zIEޟh#+WP@hx͝~aپ};;@R]M)%$F+znoˉ\F>Wuˑq]$w3gΔ;}_`0XN<ɷz$Gv (?~<:)\t>&شi|8t3dɒRR%^]6t ǵkV-J*%#FD1cH:u#6Iayh)w\*aXg2"DTP8“o+ dzgϖ7|S)T? }8rU¸iݳI),9-AZxzwP׫c47o^cVߡC\c HK6m*ٲe , ˑAm y]H%ڽ^{MΜ9#+H PB Jݻˏ}QMX^^Z p5SNŋsu?9V % <{bՔ5kV2d )7|~mi޼y"^/_^^|EUu2lذIAnZ8+ O`SA2T/0λQFĩ0B N:%6 gT\rp 3+V펟{ǤwRlYbW̒1c`=!v.5` `7H!+WTF *86@+aX.: 8E %]vI'+u 5^ rS>a_@ eKѢE}0!UT¨ !FB"5ވ0C,2g,mڴXu֒%KysYgԩ߆@ J|°;mJb9\$Fˑbi*Q%YW~[@ JX|( 'JqZj Ģ H>$ u!(&x% ( o q4"r921^,M^#k._aÆ^:k1I-nsI2iѢ(Q"tq8(`T;HK`CKfS_]?~A2UJ|*T %K 1Q'@ lx xÇe͚5IT.Ğ8Xnt"3fT85'1Zi:k$J$%Κ5?QDJE|% 5iD-Ib)ǔ1?9n@E,YD^,ܑK>1CsfK.-JR}1%y"&ώw |gRn]dߜ#@%"{X6n,Dij֭[';w]6SQ/uXXk!Fsʄ{M[;zkګ8|LZyje+@!3kZ97r9My.\ ϗ#FW)k*aŽ=ޥKTvmiԨ 'SL _ڗxWT)1c9Ր5HC=k+k^sΝ޽{eǎk5kÇmy ŋU… opidǏ%ׯ_/o 8P*VhakˤՓ^zɦM͛'{u@ ~\͚5KAq$vk\ԩSKf (‚o֭[宻J6^AJ,TK/_7|&ƸՖ˚Ӗ -n.Lm nO7Cz!XQO=z穀>j@3XCv ksC ̜9S:vZ%;5Xe[o]YEʈ<~+RUi'1k% oj5ny& % K>4mTƏǛ]"Vxq9rtrtw>& V0F*aͬJԩSH"rwrƍ'^z`:;=l!$ݻwS!k=*aM>aO?ɐ!C|u`9\8%tw<% ֮];&6oki #>,0 r.=>Qp*6(K$!~=4gn*f0-[.2 Uq*aNPg$`>$n޼0Ql\#fJv(aYhtϕ־}{Atk'NPg$`.rǏ뮻܊Y P M1lٳGŪ\Ͱ`.|cnː!Ͳ=  ̝;W%Fo $BJV_՝1>j2b֘ nGk GV1 RMTt %jKخ]L2Vy̍Bnl%_hyC!D P bŊZ`Nژ>^ '$ Dݝ:uFK;,a,YZdϞ]o{WJ B`.̔XIP yB,_̝;d̘Q*@%\HW9@(eJk."E ,I%EBq@ lꪫ⼃H =*a:&9r ?tg;eذa2mڴt8~6ztR=q}ɶmRFc  A`l۷oz UӡsV8 9YŐ@̜94JXXXe$eeyH|;3Z'R,{uߗ)SH]N x.xYeNY fٴb?,cnKR*aa]paKcKJɒ%رcjժ4|Rzu%4Xɢq4u=z4{ LєXu7رCV~ҭ[&7R? d,(1?ł$<@*уf%,O~ 7.M 6_Ɗ*UHѢEi~ᐒVN{+^{6]yi6%` J*%ʕԭIv7n,O=TZU.iР.]:͒Ν;eʕһwoU%,}^Bg|i&b|Ux㍒'OUWVRES\m w TH=u+*aa3c\̷Pںwﮔz vsj;(%;wVK'NTEΟ?/ˀ[nX+͓#GZNi\ȅ/(,ZPRb? 8P~i$+6}t)[R(P;\YW\冷 &XڄkU#X^QDx }`USz͢}dHD4 Qȑ#L#HpV?pO[#/(]ҲeK~`(e &s(&ȸq" KرcI&=vz衔-8k|`i%H%TdbaISS|֯__^}UrnڧOBsbB1ݦyH 6?\q`Bff@JެD">@Iڵr:npoȀB;s=EzeAH,N0Aݗl; 6m(;0cV+XFS= +M4IZn bcXQ;s@) >k~0?.OkrHS ⰲ`٬CzH9^ =(62!-6f8E;va)8mQW8J]͛7+^&{X>G!FTAB;XMof˖S]:718裏YH} DV,Z,G,]}ZuEr7Xb/3g6TP =TRDdZ9/F}IQH"pR7ڵ&+'6&!C &@%̀X3h L6cD#ypX [OhΜ9ҔnOcSu;`*" WD!P @Qj`PU\`N07v 7hIm@_3QwX2IT"%̊0| 짟~esbUv4#3r"$ P Ç[N9K9rHy.B$@ؽ`K2. N8p4[my$,0̉B|=Mj6-,A!wӧNsZQ=댃cZjGip9|R!pԩS*^(fUu 8Pf̘AE,XuoQ1v8az<&g eʔq#l=0Ejgf/=W-Z'd&Nƌ#CUs`f݉ԅ_T!Ʋ$`-3g2EY{*aa@oiI};»KELƢȑ#s.0RH'vZeVaCJX.^eˑfOYf)g3g/`:a38(|/, ߓ3`ΰrT¢>"%gϞ]R̔KH裏J̙;PqĘ;u`? R1XAaپ} x@F;b[0,Z-PnFiذ7> HZlٲ  ;_˖-+ʿo\MaHVG3𘩄Ř|q#}Qc42ڄرc]|ӧOWIo׮]RRPcXy+aeΣѱ\2>h״W9qxdƍRdIŢt*ψ#\1JX8'{ `<>;lol.1Be˖7o^HԮh֭S.kV,tMnNs96l"@\0 J&=TXۍgӦMmZϞ=套^R?wAZ`4 'dpa :LE.rS3fuU_8?>P_NxHش[&궇7[IOJXz&id˖Mrdp 7oڵk'2eRovd[ Q$}t9pBtsְݻwQқE 0@+Y~oI|N/rR W tyO=?B 2}Q]v6Kء4qD9~xp *aq*:uJy+y_rܸq%hy'D+VP9jͯ5@%¼kV0-~-a1 XG6l(9r䰮LI4/Iz "K~I8gȐA?< L̙3Lm2SVg*a z"Dt5( a Q= ʕW^b=ot P K`«´~ m+i0.G&EI Lԝ4b+*a |¼e_0)%Kd"90B?)$FT|͛s f4M^/ݑFs$`genx& P K)~Q/ lqY4^F%LIV^+W6rH& H)a~iT´Y+ OV;_1k$ P Kה0?숌5E/E0/k~y%uV9zԭ[7[Yl%oq{)VvDƚ;CcuupT#>#:7lb}8*a Njg? JXYtiW[‚jӦ2a R;~vSg{~%?\ڷo!r\>$@%,I-P={VN<t+F7͛˸qdڵmrw٘ @Yϟ#DJXYL矓ۺۂnӓ֧OꫥYfr)1l${*,E!$'JDFݦ,=ha&M^,Y"ٳgO gfd6{K.(QcEJX۷oOnknfC ?К]\k4EɠAJ*iF}H)|8$'ܴ ѣG'9Zo?СC7o^HгaXjd˖M*UQq(A!@%,ə.XrߒۢY҈#k1QYUCK&8)$}٩S'? c *a)L[a%9sf^z|r7F HkW_Ɋ+R_ʕslz6HOt ]OFafnXcիԭ[7zV C=dIGvޭ}嗪83gNHX/($TLI2V~}j'|R-թSG "x[ni?lӦM*d׮]CYF &+P@|Z K{}TU K.1PH&lRbſO>C~I!믿.r[NfϞ}9ɓ'H?DY&(fPuv;.ZL{X-8 ϟ7-z3,Ej3m޼YYS4Bh,_'A3N1K/ f 0 E, v)ٳ<-gϞU#RiaÆRXtU{ |1i&cFzY4˗75}Ѿ}"*aKҲeKzj@ː6nܘW/Qr^zT\9mPի[ֹsgC;>8j\ WѢEC= N:%/w_ P 3qf$'p @ĉE[Ţ%J(~*hz5jBƭ!< E`-| LJ 'y3vH"رcB=v]uUs<Ž ȇ~(_PR.]HܹU͛7rK*0y Q~ାY(p")!|"QZ5y7 (@lI&ɨQB1 ϙ3' r($@@r$H8Y0z|ATdUU즛nR9%;u꤬b_|>}:ѪB/`LC%QH<xs;vC=Ï_ j-}^!(ܫIv?|Nr]7zܲe`k6R!y4 u:#߯ǫvX` ȓ'ּg_c׮]Pxg Ʈ4k۶m)S0!ilCu/]CX 6Ũ,kǑPEYꃢ_P!"~SH +WTK*TpyIi+I6 *A/R7L2/< L…C14bBǸ}?2W:q/#(>zPscj% 9,c&,|`wf#F~w+pbKdʔIJ_ v % U00zA/9ԗEzTV_VvUk3b M/x*ޯT~ܬbp$4nӦb`ؓiu |acfׯ_b~eV8CGl-Z(֭u_"a9qpHhnP 3C0 &رc3(po,.rԮ];JyI];pR54h)g6r"y o6=|07κ; 0u֕ƍC XL5J}Y[o̙3[Io&ܹs*I>}R|_W^y}7P4ZtܸqT͐G!͛ޓ_~ő^Ç jPμ)k[c M5kFlU¦M&G 럓:K؀Tq;SO]?3,l)$`īC(",ERH hS,sժUү_1xeLUL?pD MtJ,Y 8B݈H~|8CJ!] NGdAqWXQ嚴rH*v(TJ/folO`C:1 J|ʖ-de]|0bW1*F8@2ri$JȑW9cٳg$R<ԩS6|C):tp $,ەg!?}gE02eʤ,N1"U!e>lW̔2fL?iyz(g3ܽ*`]w ^@FmW{y뭷^zҭ[4?~\{1)VNs-7w_\T[/_^p&oѢ,Y2t>zlܸQ(-۷tג/]Eq@7!Yr|*hܹzҿB C^{MyÑΝ;o߾ד9yH.'N L~ay'駟,YU!yc^ $\f 3$0`Ly˖- /|jb hB$ %,DZt1k45V /aX`K`%:/… NP瞓.]ӣGv)>"-^>=&-Ōv߆ TxK/ 4A\[V.6Əj~>ax`,7b0x@Q&)ڳ jYh_U81: s% *믿V~g5+۫0x h8(n'+?},\[n?K#ەD,DW|JKs+TVL}ItX~Ҭ >>,Okǎ da—և~T*`',nPB|`yK:"Զm[9xܹsI/ V>,B!dvM7*@Y@ [GJi}PRlRzm23gtVtӝHfuuHP~`I.P^_!xd ?,k>bG,`% s+Ew珺P~Ĉi\R+cX0'x^PCG[H/?\2ex@5jTZZbu=}Dщg /ؠA,Cۼysx3 Ӥ_~R(h7^%,^R,X [ @ x' %#X|/_^9Qʅ/5M`قe ˀz+|.MŢ&ױ\v QN [< @y+X*.tҡ0 c2',eTO9G\_3}"m:#R3P!X/b>Kg\S*aYkw}7{6p, d5Tv=p"|Xmϓ@` n K4X>O` `o$a+*$^`i&l,Mjm ҡvҥKU8ڵkUTՋP gώ"1CBOmzJ4.8/ADk&k׮5hnVf$5g<#UVwI63>^ek0C0T :H =,GڵZRіuC~=}|嗫%,QBOսX26|`(yMPˠ~554i 'q,b<ڎ8YhMP`ym|cJA> ㇟]x%/R)MXoQpX9z2aCL)DSОm5Fvnb 6F=isv-ڳHýzZ]fE6 M}`eF!0&`nM2@Fָ:Xv7 |!Hn c+a2No=2;m:RgF}^?׌BFG -ϥ D&`H 5, ڰf`7>>m SHW7o\ۈϑlW_O0Hcyna_ ` &B$JvhE M^"D7sG>OFT?M ַ@鱐K>$ە0C$D|Bhb/d(W5@c*E@d @l+aU~w{%l'b0HƎsvb^)$'_|H!M% -Z&Yv쮲 D KX>=-7rƝ:urs77pG0D2ܹs]1#|ƍͩ0F-mڴw}̸ְ/Zs ,_\ ,>yL$#Jӹsg~;^$)S/iShp i,XE)BzDz"<&SaÆ*unW`k:(j.zȯjkAj l[n Ұ98[N\uUqf cJ:߁'NȄ v!!a ݥK9r 4\Ί` LO$?G0ɇ~Xky)Q( gرcwͮoQ*U$H?z { vw߮I&.!4$s< &~wjLȚaϠg]x7yM!H0K]XX iݺԭ[ו_Zzy.(w۳gt=s l2U`YTJ{nݪ|G|0Yf5KTLGHPNyTG:FI\iLdʔ)0#yp TN|/cܫI\Z95uӷ:G:׫_+WV.X'/\ Pݫ_Ph+/ @w(JR̙34 ] i,_qOsc$y5RYyXEzEYʃ !9]1_MJUdwڴi_~6HG\iŁ7w]e Z/(u1B/K[vNjT:z{hGO^;ƫ&H E+O<*M y]Б#Gѣ&Y0zFsZ]ګV>k2FkFP.\x/_>ee.Yh20H:W>!aĹy`' C ,_@T9;H13l0)^x8y |W X'C6fSee$@n%ݣ૫Vrk7/exΜ9LQaE0o!eoI٥hѢ/ mB)řBZP!ׄ_^'7%ͳþ6|pA1c0>T5j#H uTRgH AmoSC!HS3cDϓP ڌ$`2|'BF>ci߾%#H}’[H'Rܹ~b~Ƅeȵ^B-H@O0= @ (QBqپ};}tp1QF$`*apd-$ ݚRTQMai֬YAp$`2*a&eu$XΝU<1aVŶjJdɒ|%H C$@:ucǎU~b]v.5 jٲe*\@amh 5"o@<1O瞓SNys uR>S_Iݓo"H97QHO }]oUldN"8{y9{qjIQ)$@HY 3;Hky7_~J |ge:tɑ |6M^$8Q99zhGlyq"c($@O5\Y+ @ҥ駟[ʨQ'نI˔)qH$T1 x=*b')3gJǎE P K&@}޽K=0?k 6PTV(H҉aHK4?g}V<޽;ļ4a}5kp agHlM@@ h~bMG?1>DHN!0kv9r#<~yp#}dzpe9cI'v-HnݔRA72Q7`9p#AT}矅~bn}3gnZ2gΜߑ XB0KR 'ĉ`ZrgC *a>HJ W\i֮_HݠA&SMJ @ʕ+HCF *|=#O#"X%gQ~bpއFqEzR`Ag;I `h ؄s$4RJ)?͛7NrKnyONuI'?1,R ޽{NĹ/[LCɒ%[&ЉIiMCW93#HQDΰg$@0>$@'F?1c͚51cFZ}% ZB(x@$-'vvgϞ-:u Ԙ9Xp*an L@'Oga۶mrA&궘3'hEk$@ĺv*>|wƐC IX]G>avH^zj޳>+;vPO̼bi&;ͫ5 $L( HH'O{Nd"ClٲЪ8q˗O:wr$b\tk$tz?~b&<HԽ|riݺ  TP K%ĒGT7' Eo}, :TY맬djѢ̞=[dɒRvܩr5nڴIZn"A`!CO.]:f kP +k%p)OlR`Ay'UL-t:`z믿ʛowA7o.3gNToR +{h{m J˓ x,bp\߳gOWXQ͛'JJs>ڛsɬY}S߲ 2DE]!A8gϪ2_ X҄⊸b}!uт ?6mHǎS ca*apH@oHӫW/DZfP栴!XlPVZ%+W8g̘Q!?ŋR(]ڒ(1(e ځ؅ }:uԆhM] $W]uUIJI1|Y|R~PRJ)/b93-[@ի'Z2 Yun͚5/W 8GJs2 nF裏"&ֺtWeyjBzrw+߲E"Ϙ1C딃~4Kքs̑r)K^СC`#|׬a.ϑ %᱋ߑ  <#vKF(Xf͚)-8Z9sLso`m{W ɲ,57X 8~_XD~-[(߱v_P ˙Nm ch,B%@Ko#DY?9Bhpf5kTpvBB#%?V)E7*k9E߱d8hԩSΟx fTH!@%H#gyv!’ L@{Wwu7kCH /=fW\P"@8qhrJWrڴikܹsUtxHT,JIHIDD+VPCV"XM[n醆kܸt=u7@ /RExp9r@|G{0Z o@* 䜄o,dÇWNd„ JA.H7 b .jJ_]*{s Hp0 @g tC_nG%ʾ]w {ꩧT.>yϯ^I!Osa-$@$?pX$0_Ϝ9Sa /|GkU8pZ\zu%'00: r宻R) aQB{IbTc W >BlH:T¬c˚I`H`D!00X& 駟$s.x`qs1b^` 0 GJy,Y ,YD5jf̞=0iw>z6`D!00X& 7|# 4H3$Ν; 悏 \EBbԩS']EoןNwOJ%V}np~a`]T"iH-aI@-$@!tDG(#}B 0RU($@%,uH~7ɕ+W?~\ *\2oV/#y7ҝ?ɓr%K]PAUСCR@?at9s`z+0 N *\ ;uZrX:uTԈƍ J~!E瞓.]@!j߾/_^)Y-ZP˅Z?WX!e˖U% .,x`9,B NJX Y ;wN2fL(%C3-X@FO,AAX-[&wVK۶mYfkE2qDɞ=rǒ2e>3_UGh˧`[;W IE$@%E&SLiN4?Q6yɑ#\}R^=A*pA˗˼yq2|AqM'5kTo;uꤔ?Z2` B$:*a3d $@&&\12SJ޽#׭A4d9x_97mTƏ/'#&#\ҮZ+C Vr$@Qp92 ^"?̙3+'wH׭8kBU<#*XUT*#~e! r~ߓ GJX|XH +x`.\P.kԨf96a 8q"uL$@rdXH!\ E,M &-[7|S펄c ˗zl(I @qѩoc $@$)ӦMSv7=ssn9NI8#Z)}1cƤGݗp޽Mn.Gb9H H:g}-4D{8v@Za`֌lNpc$FT8+ m04; X1 \{HN E O/+WuOcX(QB$@I$8F$QQdtQT"6l  QHR'@%,uH/\<WP!B1+ 9Ñ x,>7nL3DU,Y$ kM]ǟWBr~q[$%r 1ˆ! %%QLK;_0r_utg3oi^{;߳6i$'d|*C6|k׮it_^%KX}' )3B1$cǎut2ǏqF铋jٲeSU͙3j׮{.ynܸԩO DX8\! KZǎmԩi-ԩ"&L~H:`;w rD%mfW^5mVBJ !PaA5 [z5nyUM' UH),FeÆ 5jƅ!ݩSرc.Ǘv-d#… M2 Pa˛ 'Olg+aǏ][nو#_~ִi@*ŋĉN|Y6mҮs[vn,.dZLtB t WgϞ{:Kj=NI$5ksjժ޽k 'u+IjR+lĉ6xR @ p2+ % [~۞6m5iҤDuJQE\b}y$Zhae `͚5<-PKt={ .}^cTVHŻwSsN{K4+/ X@ (va=zK+jjqqK !a|pKKML/ZKbM/hذaꔾDѣG-77FYa|G",G@j׮]nP:XQq:/h 3A"!CȐrs,C q1`UGЖbN>m=z0Weql(w:s挝?ޥP Q.p,mLAw޹ڏZwieY@@˓ %pR@bTsοׯso߾?9o߾bSD",O ĈN*J<ݻwϴu|C|/:tpbW~UaYuc, PY HdyƔm_i)$$nݺ֨Q#'* < DXf @!@@02  @Ȍ",3^ @@I @@f?''IENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/normalparsing.dot0000664000175000017500000000262315110656147023670 0ustar memedigraph { compound=true fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" ranksep=0.2 penwidth=0.5 node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] { node [shape=record, fontsize="8", margin="0.04", height=0.2, color=gray] normaljson [label="\{|\"|m|s|g|\"|:|\"|H|e|l|l|o|\\|n|W|o|r|l|d|!|\"|,|\"|\\|u|0|0|7|3|t|a|r|s\"|:|1|0|\}"] { rank = same msgstring [label="m|s|g|\\0"] helloworldstring [label="H|e|l|l|o|\\n|W|o|r|l|d|!|\\0"] starsstring [label="s|t|a|r|s\\0"] } } subgraph cluster1 { margin="10,10" labeljust="left" label = "Document by Normal Parsing" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] root [label="{object|}", fillcolor=3] { msg [label="{string|}", fillcolor=5] helloworld [label="{string|}", fillcolor=5] stars [label="{string|}", fillcolor=5] ten [label="{number|10}", fillcolor=6] } } normaljson -> root [label=" Parse()" lhead="cluster1"] edge [arrowhead=vee] root -> { msg; stars } edge [arrowhead="none"] msg -> helloworld stars -> ten edge [arrowhead=vee, arrowtail=dot, arrowsize=0.5, dir=both, tailclip=false] msg:a:c -> msgstring:w helloworld:a:c -> helloworldstring:w stars:a:c -> starsstring:w msgstring -> helloworldstring -> starsstring [style=invis] }opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/tutorial.png0000664000175000017500000012713215110656147022660 0ustar memePNG  IHDRPZs2sRGB@IDATx|$^C"{C 6D@#  H{ &q&w~~go7o2$! B@#0;(YX! B@$aX! B@% pVܸB@!  ! B@,ƅB@! Dg@! B ` 0U/7.B@! °<B@! K@ိzq! B@B@! X" lˍ ! B@0,πB@! @a8`^n\! B@ayB@! @w>{lG녀B@! + } sѣG4e[! B@?á|sP)b! B@!`'-[yEf85B@! " HEm ! B@& pj&#B@! DB@! HMt49x Əo:!88˗G˖-AB@! ; U3|)3wXL8Dzey.kͨZҗB@! GagϮbȐ!x'{nΝ[S`>qˇlٲ7(Hsɛ7e:::aaaȔ)իș3', X+W O_L׎7NnTXժUo>| رcQdI(Qmڴױw^tMKT)AB@! :^! S0z>}?0[:t{Q&N&M Ƒ#GPN%X\\Ih~RR㹩W\Ν;mÆ A!k׮8s >|\ ^O8{,^z85˜d߾}XfJoҥT,Xp;wNiO! B+ x0L2Ҏ+- *4-.M~)9k֬4h FJ3LjiSO)3֭[&Lu*S{GiÇ~L-jժl_B@! |Gl ڷo_lܸQi{ϡ) ǎS&HŚbP`An̙Qxq%3ú)ѣG~i~ѭ[MeˬЦY@qB@! ~^! oذA<Ԯ][J<$f ~iUPkܿ5NhyBy̙Y栽M?7 q@CveC! BgxL,Μ9}oLڵki@ +VP6ܧy=1|I-]:7&&Fu;bJ0uWFFp…t QZt/B@! 㒒T<LG?7֏1:=~+m! @@a8 ]nZ nRͅZ݊YQc8}Mz}RM?A&qϯqeS GD㏑`|5i:vD;';};Ao}U Gz ƻxgrӬuN^An_<~mbFr'aLyfd[ 8g=! OaاO /8]_OG#{}&ʖ̋+>DR+MEZf?~\ӎ f';oREF~@UqqعUL oR}}|yؿz 8o~܀ycGhﺦ<3s3r\#i" tIᅀMPlwב@j^)SxNX7U\Yh5FTX_lG:D35m3VsX0Yp\iT_4e3vZ6, HB@K@sW! mIHԨ֬\ĔV"Y|IݒjVI<Д":6a* d ժRԴ /W(wF%j" {uHᄀp6'/OGal|*{Ou1m3Z})Ӹ#s oq߀9Qlu({M?rp>-jHvwb&5,'ĄXn sb/Ôy[Ѯyy ƊuܹsXd׶Yy\~S 㧮c( W('W8`Wg G,* \N=H4;%43'ejL!D3&B eɜ+-//gjrY+<t"xmr;P`.:vtNI͵لa9xeJ`׽^{2gQ(D&XZ%vcWc!{PDhzrB0 $eB¼ jǑoa0L2SFM쉳i(&vk[CAC롦6A2m)a;]16G97co:\,5" ZIyp@hs=^a(7lj58Msn͛=D!,{(T+ZPD^8}Qz::$D zJ@lf径JZOjx$f~'agK! \DZEHB@o"1xL4{ofWrlaxX! hF~H5i\4^wa%?! }gΜQkn\Ο?$ߣi`@]R3uDEEiSEgA…-B H" ,`yd\"V̋*C"@jqqqJ{I8qBms}.]f0$$$Q`kԨ@7ose#5U*? k*.\0]D+BrbPhQ}2B 0셕"ED]ǎS !44$hQYYdddג9lڧ9%'O%Pj׮0ey"|{oWbx>a ĺl.,s^*B 0u+wM?-Mv@r..כ}!s&[zRuר[6Ma8EicU ,, m۶U OtCc޼y1b2y GrI={79s*52 = 'LNB~ٲeQ|y}xJȤ sAll,z葱*! 2L?]ԧЫ & ٳgW/u /tW/ uV^+lUф7] kܸ1rȑmq߫/)LQB-~6m*  7g*XG,݃#Jbs8|/ԼoܸQiY+L 1=mcǛD \Eaqj(ٳvR _/P}uRرcaʋAjՔۺuk.hozѷt`S2 =q݃>%Mt1cZZ}/ t!Y?pir`ʕ++3p?|$S]ʽx?B4q N'_ۥ/B.VvkO+ppֻԹΈCFxkHڅﯕ}ѷ?p!~Ç?H(R(lӦMM/vrkl臙l8 P 0l *SR i/7] ( |WXK1\X@.^y! 1s-;=NziÏ+W*Zlc>P±LyNX %ܟ0' °9 n$@m_\ [&O]Hig8 :Ο?~[xPX7Q k4j\~1d\Ghos9 -zcu-2l4'`\F E_0aT pf65m֋nR@zaUƮe˖_RȬ[I!'o}QS?Tӯg{i|^tV75DEތPT\h[h1o\j{hE ߯Á\r.!@m۔O[;%X"g)̅ m}ӵ4%b׶(ڷ(»eMضwML_*]gэ@1SNjgx;=UXYyO?k׮ zd B_0/5)B_ ~PK(Ψ<2^lGMҥK (Eo:tP 5>ٳ5c2ڊ qD\H w1׾CJXWpM;aN:=R\Q." {W}Hi}jDߔݗg5]d^uph"%`PH(\p_vDVKԲkɎ,.[hc5c :TL0o\y1񴵥-mԔC_||dɒ%K8WxYW5):w(-' ]{)Cv)m) {|.[DY%O!`֨H\@еzPa7!-t iivvZP^d_ǎL0(p„ Jأ@4vX#:7oyP tچLFfԴ}dGl ҵ 6ά'A-yy@ll2umݟoM8Јv/V3%רQ#% O>hwߩ%mJ;m0fcƌ{~QKOF=#1 oL? P8n֬BBB `NT@&hӼ ֦Pvk D>xRd/' °W/c(ц/ O_ jl`FD>cjE,X^ɗV_¼;xVٻw}P8fІk׮j=7zyӚ8 m DTZ2lM̥,D'/ݗ\ؐ~}Q_)뎛 R]_zqt=' pB%gϞ]i9diqJ]0{l<la0߀'?BG?X 6?~$S ܾ}{C "ض'99@TL֘6mtw*Ad%'y׫٥K#9C~IeGo2x05ݻwW |YC z{YCO?>'W^'gڴiʖ@3~X h` BO?Ç?ēO>n }ڲ\rp *s fD0FE|~ts7z>`pL70! 8C!9E8Dպkj~9؍UF¯ڏ9"=@+>/4V>sWj½%P FWg"9K_Uݨe}3-m |ؠ9~4u@ t\=zTЌ1Mg$8N1G)ӝ$_l8!MAa[]Fp.svKS̑f qy\5~UܛoiS}#ͣ?=_趤)G@ax+VP(v˕=*±눍Q /8PӸ!—հ>(q|a1 aw2_PHsKFfnR:"41%$_q둞%(t;Wum2  l|鲔m_ZJ xdc;r{Ǽp"jSɑm* *uF4b0h=Ox}!wiҐ^cq1Z0``Nf1auѣAOk]㋉/(vs-Q c#mL"C=~˗WfHO`Я5|9rDB -_K/~i}15 7`F?sen^YF3 l>~KZG(Yb'@)#Ilnib3(z*RaZ~p.maLʕվ?֗/3r^_׬߯#)Ȱ6h3g(/;@8-ks?s;o_{9I{2C]x*SQMAI0&=lllOh"=g*8. F 0=Iߧ4ồ '˱8 {~||rAGKEbpuD* (A՞Io6w̏"AA/Ѳs@:}뭷7ߘrg754?`/6~L;ShDf wEv< 3os̷yl\ś4.] u)s=37^֜؃H$A|wP{tf(}oD&9J9*B88Jy*-́4'i0n3c;дu1kP m'eg9tk=ۯХKt=6 &4ED ~Gрצ/#ߵ|K]Gqz0M_Ӈ.%SIւFŶ8zQ?_wFd7DӲoۯaxj8b# Odhvo{4;v%1%i/z@\ʞyߡ.eo~w# raGQO! 3ByHJ~j4A [X ք0;wɒ%sJosk!)sю<so;C: Ѕ /GR8Hr^m9QA̜wi 5?(Ƣj)R ]nW)N)+ Y,F(ϥJ6piߒoW gZiOG;.]`СjC1)Ox]>A,]qVwΖ%JPSnfazkN=ի)}?뽙1鶅.8x('C3M3O^wR4|C:uMXM(lp|GܧRN  ƍj.Fˆe[O3z}vΞc0s0f ~pԯ6^G_@/֠JQkvq=YPN;Z@hY&]Fkɟ U^CΣ/0?hoqUx3~ʼ?mMvl&ՇF7T=l:&o 7&K|ߘ7f#lqDqoG4Fx! B@!N (]zv}L^gN_qyY1b߰ N8"! c68"A8oG;(F{D3L#i À\[gXoG;(!ٸwe<1wq0L#i À\[gXoG;(!ٸwe<1wq0L_u23Vqyc68"A8oG><" S`L|\@#@IDATWquAٸ7 ƥt5Wo|gq|\oP6.xDhذL']'WɭY+l:5N+q_mǰl,yKZu|[ 8 ":p.i*t"\[=# `ru(s,Έ{Zz\|:}#ָ:ԓQ>ƻ;Z?fm}bzGܾ{^F\wFkk4]N>a8>. ڦ0}qfOݨص8(g$,RoʥKJ ޺HZ|_Eqq6|`˜5Ό•1*W w\rמ[Tއņ._?gqY2ROiaFk贸c.6t9OyyNaȹoT>V,3cs<3Ȋ\ROpMf F,!Ҽv59"(o˗qG" Ϧ!sf2%`e#>.ٳeręQ4'ݡ&kGio9SW.\OK=Iq "=Y5#c0s.io9qyra|kPK5Ru<+N߈k6MV'[9<⯿3/<9l{_O??z [wl?=Zk=hZ]Tdwߜh3#А.,-A;;}钍_?96/MÀW"I<|XS.kmm1P} ªQ5lSr:Om$V÷@5.cg?϶07?|Oocӆ'C%WpEb8m߯511M=S)IΝko}V،>m+6 J#KeH֮uG=jk=V߷?:khKIYڏ*O'TYtmpÍUa@ 5w*dG}utvӵ)7^?x eRk¥ 7P|A[P..;߿2ʖu0 uJ˪T/XS )Ho_g3xaYPV ԭ_=pxwg,=F_?7zG7hRV ١K T^WktI.':uf L9Qͤ`?:kõj6mS)8AO#6ULj]'^BHHfZLOEGZ{ UTqv鹒EF>#=[a|Al!Ǻc`w+ %KWHꩵNXr^б0z54ɑjs2zD_>s4wv2pۖ78"d[rOղb[t]e̛`:jpQhM kt'Sr}G;}ە^X7&(JV?((/q+|\h] hiJ-NpNة>Xs}aȞ=7a7e. SlxD E])Tµ+^N{~2̔t95_Q?O hѦo8@{łCܩWeTO7*ifzN3kzN>offar: KkU\z b><" 0:v7@\wQy1BXƋSQ[hTe0*T.g^jc=mRWRI66&&΁wRJLv7RwQ /=5 ?dt[[ڐ O.kCҁĿRe4!oG#Gɂ \rJ o۱ʔ+z4'M?+.^o<}Y꿸9s 66=zdL~z,Y9>lk4Xm0G+Aojwi5XBgH,`_Cܹo~a%Ik2.-i [(:H9 ilm|U?mY>mam@o~zϞژ+g7yZvc֒5n4,a&MPh=Ldl`z>zDI h&cGmēVAS'[;8J-g3+馟v .z d\šOSP~䟲lY'[o,ӹзMQfMܯyL|ww_D$ػ Aւ77'qa8$$_"*aƏ=jm׳Et/ I  @O?/B%mzfؼ oߌDl<ҿCә{wﶡ[жm;(l5.|u."m=Wwp`ݻW+r:ga&q5.DpH4ζM.=́b~ڂ\sBz]= {h9\|(sړ𷇖1]z5:tpahQ={3fvibנgk2?:WpOnҥ6q]{jw&iI̜yw&MrghRq8qk֬-#[$ʥ \(”;$M{_j夤dqgeJͷcz3H-`9Ǯ#wD!4n͛7wrwI8%gH z_+gH z_+gH vz~/ 8y$\rOF|0`8)qt ׷ݻ1c :Ԕݾ0m}k==/10͛DPD DDD$߳'? =bG 韴?nm7_3ളEg ǏGݝ_'=[_{gs_{gs=Z(9! B@DEbB@! PrB@!  0l ! B@B@! a:B@! " @%- ! B@X' °u.+B@!DJ[B@! N@a\$V! B 0,(B@!`ֹHB@! @a8*YnQ! B:sX! B@ pTܢB@! u" ["B@! @@dE! B@G{_'pI\zNB޼yDdʔT;w}%%%xn3^Vysa_,O\\"##?~DEE!""B?g߳U'? H#'~ x<]ʤ qwD;s3gbccѣG;t>k֬-#{ʉy&"#SP&$&$ZR`o6SSg77֏q8kyWͷc\W׌RAw YC2#˭+RGh׬\kW!gR,JȌ޺aY{?$1H3yOyͷy\KGiZk!Yp&pF"r*h {] VD1(=aq'4As=o:2P\{k])11|̪^U2eɂ !A8tK@n]Ѽ_Jid\7#OfZw IiIx;uz07q3 :N#Gkeχ Z{,f@PA)D =z#cwӲtPeꭁOU[!@d-V!9|W})$ێȘxgGϚ<Ƿm@BѸDˑ[tu ~nAM/Oht[O8v3oG\~8G8A%w.ƃ5tkݻz/=kw֬8~|ڷ&M(s%r,OamP\;j߿~' Sd",^6 Bvѱ𞫯]ł9;qxo~+W{ /Zu\=⟠ qע\e yyd,i<-*+W5ٌ='S~< x,3-c4W>=!c.dTa x-h"L[0[FrJ~?z,v, ' ϽugŊݻ:vիј6m-vト'xΩ3i!տw^ǣbŊMt]BBF9݀hՊUC͚5kC"i GeL ,=Y2rt ,DeCF(Z,!(V(#0u4gɉ2^ TH\ի7kPԭ[ŋ/'"sիѡCG*pW_x?2!=vIRDE0s"IȦ=}!O?5!C]ɑ cݒȯ /Y_يZF`[ng) fŴ% >,&yq}[27H:B1{ͯȔ/ei_FHmЧXpAZPIRl<Z/"q@OZ*^0sD@}pgYÆ Ez1lؓGFoyqaxı*Ǟij^.m[S&s[~Q usfon~m_7@ܖdD1?q취/kX)X<'gnkiV݆iϾk _[⁼E]}v?!~Y]cmW"Ll7?vXf4I;7W 3MfĉQJ8^xؤ@|<]cϿ9i ׯDZѽOu29u{è_m4/war|? \rYOs6AH9{X3ri_# }+w=^6cVplȡ'vOu[kEbȏ<%-jO&ρm{Ұ\@Pp{C|`jtfKQvΦEkn/Lz53,g_?:!ܹ1i߿)1a8&&3fGSR,#{e#!1Ը"Z=9)kv (Y- oޟ(R0^y?-_ԯ|qZ鑏x ~œ vm@3UN@0QyDU-G>hS5TmY-_#.]yV(bŜ@ۭ`yP=o+…!O8}"J{>F6~: \Q?& _u#F<Νjn&ȁc덯~ z9Gݽ{C NV̳ KS/G2t}zj05ZPj' Z}FxJȽ}5K+1\g3hfQP%(ұ?M6kI ].f]T'Ek} X?!{kZ>Q\396}S #O``|Z8я[^] gĞ7;ᢖnA7?RhDH ,sg;Tד3V]4mhܼQ/fHSHն&Fn!sa-}S>~\7EWKo#iQMT ~njY% ĉٚέ٣n8M0a&F5x|l M̙o]w_K:Cym÷ѩS 3zKhLơ<" oڴ +G WDv _ʎњ-f/{E?jݭW͕X ըl.xx6%\SOs}or0|7/]{6in;@WÊ 8[g$gsE?~ 7-BD+^Q3]SYƵӺc94!}՟¿ZDDdK7@g4mh@+?^gii)ӊK+ 6^|<ߤVk#si۳߲L~ٸ-KFF໦$ӞsG{B%եG_1>\[G̉9ommؽj['L;jۘ&XŢ[4/ .꭪xbej4נk=%ʗ n4W^hfhu[*A!," :U;MUZC-̤e6Hg|rش=O]_zmM]ZijyU[w1>)M\OsӧfοT9r SwKFH>*)3̙V\ұāVjb^׿lt۶oDUmbgZb엫sQRa | :vi5UXgdw;>yJ峸V䗛E; R\.mݨ_)}?6#.OIӨ{Cւ 3X/Aq0ǧ߱X?u&ņt9C͎bZw;()$$@M7]颢_,XQR-(*("𾠀4%j %rs% vgf|w<&D )x׽L~xKO#\Y(69V=K^߻,˴ #7i_ 'B^"4"*n0F}!I> Yv7\y%$oI1vrZ  Zs+V7h߆uZֲ"N 5ʩB+6a'\rq{/ٙ! ?/gCǎ|FQq?bE\ШqcC,QAcGܭ[ ,_Chm}OatXuUXO|j>w> nOeQЪ]mipFp^_40@gѹA\…yFp@|E=Ax#;T>t4J5~M-¾N|.`-xS&5- )7.]ī~ωSJ?~Ûao% -:rAq?׿)FB^azb۶iSL.ipD8^]*\y4B+`㥍0 dj"4iRM?jzz$W(+ã1LL#ABG%m+ @E CرcQy]xeWYrs[5$Q!NܸKBhlܑ[wr2׉95;v=)2~>4Ou,[a•Vg+LBk2$P —b#Y]adqdOa +YղR(vkla{ ~1ɏ9EvE&TBsLiy]{ƒkbkn\EDj=$K'N*mG'krU&i, )&ϥK[ n:+'&݋¾xs Z4}i&=`iUFU Kjс-gfIbiu##c |R$.&M%L#<<9&ANM<m ;7fo=$H*=_nhKy[Z~g|Mk2M[|ƭߨ6.LC_>מA4m-o'&X%1wH忏?d k7h 7 Uhm>^s TraUkaCz8 e >^Dc-"MiuTn?',Az4'< {]FWw$EGOOz /Ɗi3u[%b|bޯ7fS`v每;W[,_>zPa$v<',#&H'ϗ3IgiZm_>"U'rObW˼dӑH\EO⛥۶UR5DmbgcO3Ϗ૩-mjuzt8ܺPhf w {>O|";[ "bz-5.U!%|ݰ~n gF$zb *&E'ƿc?Z;sđadWQx^kbN aHhUirǧK9 VFtQ>"l  /e l-(jDυ1V+3wAOx}FmcK^$|l  ƹ<]?-.+' c4ulԨ0s0ܫE 5*_04iJ S.Np8A*T3f,6DM)΢<nҤ߅Y;ӧO7\Z=W zz8Yթ[ITh4tk5 _~Z-K?ss1v Cr&"5ܙ > ٢u+dO '1#u-tii}VS(YV])2ZhࡸM?eΧFy 34QZhꅖaI|=̿ૢ?nbm@sM1&iI &{oY&}k~UkWۿEPFRb6y;ۿ^a`TP $(cud-@T6idWe+|)22(OT_yZ X-u@5>OHù?9kMV1')_5NZd߬2._emQ/#/Dw }ԙmbL ԫxo^TF9{~)بܪ.ߑx_AN;P~BM 2'dMS]sο[]sXBN#m_rw iaLG=QGGwNbyuR3)W^ 볕)^5;<h4œƦ}vʹbOsTܵܪ._뇳*|)ph0(OT_yU+v[p9&~at &O(ZgP>ݠy57S*6$$?yEZJ:cۏ#6*(=3#.sՙQ[G:hOOC߫g!&";wk $'&cznތȑ3j;בkBddt39}v&a22n\{qe$'嗭qT -ןw[ܙH?ߔHz+MKK7%yd.oA؆' jo$A!HSeHdz|bjo:eƜW"Ggi-:a>Z 16_kgbh݈#=vdj|[OyfH"70:<.;-2< SkU ­kѿܲ` iE  ;>V] eڡMJ-}gˌa"= 툾,^>aVm߬9**e˖H(\;v|[Y9$Ov}{9F-ǛCwѻg[i$(-`|][N>v(ط#ѠxEٟϴvN[Qi}T-SSeU}'=~j'g]5+' ar͎)mR)-p<)w2Eڸ!&[iݬ{\?! nU ”\ F!-YF;-%~~RST):P".D4[ 7n^ _ ߲F#K9xih?Q؂і-Ѽp4h0L=1yB # X6Y2/|Ght ֦LumЏ'å m-˧Q^([ kWo z9d MqψC~d!voUaN0Ђ$(^8n TYϿrxΘ>;7Vš0wKh,plLFzUP4|D9\t ՂT/] 7nGar(cZL$7Epv*|Ai#TxҸڽ ) &Sګ4E o*†Ȭ7ĥWN|FǻVy?{L)QcMh{kѨRߨm>(*st23F?R%׎0# QD"rYYoʠmFfF'x` ^u+AA?I#O;>G`ذ<nΛ7*rc"x 5|;\'#8^xKUϡOHGq=|s0*T6B6%K;wmW_ǰp71ON_vE3ܼY?mkl;t hf( dr9 5*jHjkF7ɎU">˜?Z82k?njw-$ҷ#аZMvѱf1gn/<'KY[t鄝w7piBtɫߦE[,3괨mXM]f4ŸccԂ"6d0q'hб>4-QF+4R/* G/bҝxj8*~ ! 7r@WI $NsFLt+ll \xn~D~lAJYo.?~܌ a/K>= T q>R0|jinbQ[ik5Wi< {rƗ ]0(;J AP EcK<,0JNNs. l9$ѣЪ(hSO},&{aΜ5LRLK|- JꐐB=_ !^&4t\zE~ԯ_(.fK_^vD]fǷZf2 4UmaI0ܽW#*5ش.5[cB&˰ưa9yx7,?9'ROl7v}"{e 2T Qz@UCf~8Ojl.#L!2:Fb֬UHHH ףG>;E~IIF[9֯^<}2Xlzg)Vg0}1jz\ઁE:^\ȆBaqBM1&bbܐY[71xGG]^+& a_YWY‹߆ߏ~6 -bI>o]-IDAT,]QQ `I8Wҩ]ܾc֮@ۨVVgl+ODvmz|2*vDJ۸U4f>]1r"*&'%/a5ːa / Z#a֓{hӨS/߭6-p~ZKh;2 }Gl^IBkڭ zZV{1cCEZ5󞿥JysڍRmӦ48Z6O>I~Sghviu& !V}DuQA#QY2XKhl׷B~7S~7$%% i3w7z(h2ϸp. !hAh ҞV6Ōcg`Y-Z/5c1;J; 7ϜO;n)VE b79c&uߊp+c;`x7jJ'?̵99g׻o PZu&i Yzy?io)[)zFX@Lŋmؓ-K0eho5)M#?Í4r]WskQ&N\-;ӧO/JӖLBkqPj+,qe_Sn",AFjSɪpbB fOۍyӦ"ܾRnC I8~.+녹s]"$S°؅ay WÞnzaf^]{n @jTDH0k^|>KۊOaܯUl lo*#&[*U \Ka t- d1e * Sc{Es E ^mvb &Y/ ǽk+K4STM6o o7ϼ dVK]N/r|n*Ԫ  {Vz[{ xn_|EIǢ ]NsZ|Cz=p3 ׄ߯@Uwޙ+m0oBX`qjð_K{.w E˜P?.0:<3۶U]4 @JDJ/Sq=z9>CijoƒR`V.<*>hYeرcgشiߛ,_6m#GU" ןq H&?Ξ*ā}po!Xk o) kغu#o;?w-Yx!3 C1itɲ?cCSǴ$6L7Ҵ 2l33"5%CWq%<e*}@PMXof#ݧRJ!%9 >o0T^ryl M䥆kiA'CVw=i(%f{D=jV)=ߺq e2P evr szrvp`k(/QmLDkN^*@OZꢿ¥$bB&̓}|u3=y:ٰ^Q%!Y01F z0#/_-hu-/w(w7\w$^; ի[j/߸y#vَ^wWOw$J{7ud-B'mZf|6gm<&`XEQw=+N=sF?/7olUV=%4/E.@$<<5j+Y&!DK.fKiζ\9!+ûLcTiry埜gGtm,du좓@.5.\E Æʕ+¦ bbbp5Jy) gdd_-/ 2`t~TK|bӂᾖ~zs)AAAX"W}5e5e5+Цa՘?>'V-T~᭷BJrNȵwKR9rD? K,ɓ`(S:h5¯7-8؇wV毑/wV毑ϯ+'ى'/H!ܹsRFa͚5ѿkjs_sP,^,2A2T]t  T;va w 0&9ӧOK+VСCHIIm>m۶G1Nٯ m&G&l*da.7 0&=soZSret] -[60~iƍE6p 0\8#`L 7nܐޗ4GvܢҥK^ -o6&LwAp 0lG\5`L 0&[Kv%`R)h/ ۷+=$ fO·UcaIԩEYs/at/?~8Vz5Z$M{g4U îrL 0& i49?^j:֗4y\K͛7 ͚5Cƍ޲f ?lҥKey4h@j{֭[d]2#1bBbb";ď?cԩ&Y˯,Uq_ǘ1c裏wz1k̞=[}s'c1& i!Zt\oi> R$ѧhm>CM uѧjm+Y?K$h-Kiދi4:pҨ\[#-6E_㑐` EEEIᗄ\7䒻oxUrI%jՒQU3fHs L;w  8aÆ5 Aðh"7NFQ:-JaWW&0"@ f7oޔG헄4hݸ8>'!XoQ`KRIԄdM_Q}Bv t[׎6i~I6?\XvPlZ͐M4&K e*T\?cyot: b̙ݻ7FLv@Ç 4#'Q3AMs°]q/`LE.7 DFy  f-((ry݀O|}}KxyyTRrӴiX)&~ 7M_'RsJyshh#T|}j ŚL[@`ziz Y #]%6$$h#X:Zx:d7&=sR'NsZath5< zkN6dnjaW_&@1"@M61%`l*>Ҥ*UHA< P: $;hB4 ؎HPkaaa[yl%i"LB--mm5~Ig k׮ phmB/~)+*mݺuZgkDiiF0maWg&رYzj0FH={l,bI4y"-$ clY0D" }&Mdե}Ikx)8pL$ҋbdK[ڵ%0f'%%7ߔ}%Hz"d$+p@&˖-Mbz@'z îvŹL 0" &C&CZ&PjZHs@Z\Zh{Lz agPeGL+KE =߽{\bϞ=r <jJh^*jSL$&DvYВ2̡iӦh۶L/>Z #GJS -Ε~Yv}eL ل>#RhE4@B-KÇFZ_zHӄH6sZ6G=2R reY.۾};h!K){hӦԨ(-FA +bA4!~i:rFUi򜳿\~' \jߵFK]Z,`L/SlLBط~kVdְo>D$pAӚ4iݻwޑd*^S!^Ä,$ ߐ!C =&ر[lȼ\xiaK!800в{^{h̑v& ^xHP$r&zA#_4VSɾ\WL, C2&\%K [!٪+@Z49% $}~'!>ɓ|ɮ4Td@v &0o<ɋ4 4+?IN +F酃grFi9bW|#M>4Nsze63iiEh/bn1 .uL 0ÒO>]H.H%>ۓ0A/m$(@LK⒍ヒ-[JL" )Nŀ6 ţk1i~7l 7O~{xӥK-81f_hяߨHDD{5c$i3  }Mٕ î|L D_rE>8ȫ 'q3ΑVi4.iT7wztL󴠕O!?nBJqK}b駱uV"VꢉW$e}~%WTCt\:GԯHH#<]ftE֤nx -aoqOgЂ*ȝ=#_H७aÆv}Pş^VX!M=/_.ox1ܧD[dl_ qbAh(NqFسwK݅2pqGrR2|}0ZǪ;$ giwV:)%ϱ8s+O4x#F'RJxSyܜ ȯ,M"^vpXE#; b$kWOZu&'$M.ta֭%dJv^HK/o$;؂FZ"ã\Jvw 9)#+2?ZfL lCijL`e~gv п 7P &?L5d&ѳgO)uUҬ!d A&7 $Rl a9z_m♿! &!HXg>t QQQW^%'am:j+|M)8g͚QFɤI&lR^<>X+0,于CnAX1)kK"[0kP o9;kYRUoiA* ʹf`J_|>ku^֠*܃jתmtHׯq&TBj)&꧟~H .ȫQ:? 4ِ>rװi+ ϝ;W E5eENr^ E|6`NN^_".׫==*~A~ڤ V/%JN:''gyy6lX)LJhR! d?LB1gn`9-GI/$4!6-P`LsvOHײsʙ[Cfa+l. 0!@GtDiZw;Ƃ ЯOBK$mӧv)8qu>kKrꫯZƟV=#/&7d.WѣhbTc6<0 0{lULFG^ t^4껾3,~1WCf V(zi3 4ɎV5#Av0'0/ڵkM/ osۗuqTiL 0bN`Ν{fjP=ݳzn٤M>V6kZ}6s&1kF9 @0׮߾s_'sc&=Z-֏gxO˴r~lcokw2 PL!d`\p*W fgq3{>^"0O1}Txxz2g=귯vC=Ƒuj4ŃvO 1:TgAxySDW"~,R%{n^wOwt}l-0ih ܻf?BUӓԿF])qtNQ21cz̟ǿ{LÚak䲘px43UhңYmIfxX;{Rt2E~RC`g1y8!Ăٯ૿H6nϩ=7I[~I L!h;]Ϧ[yHG zvy(]j`?/ByDža  iXa3p`{A@ ?+mv+Iv@iZeH'y~(p;T'\fU-**RlG@+iMVR/^/<(518(2`J'zMGSq_xl-svB!;u[݃=GLT?]?c3 kphBz,j̤+VBj}KS [-йY #NyZBt(&b€OopU 7gL.C?ujV^?.'.ֈea &@ؙ0f'r dKn0(7ܸ|#.Eiy~#b-t|U5RY%m]^%g-QP ' _J0,6<#|ɟ5bL &@rZ2ȼ\ tM|p`AYaۢ?tiKyVmU~Ą{q[x>)hG_Ʒ/ľ,輠*8Q>Qņǿ/?Q1/5L 89w/<[0[P֯jgͰgL 0&(X3\/1w 0CQ/X_rJ)每볱E e:l_v x&%Ym13}M_a=2@$/+O gco u0}6H~0G(X3YY3)`llb z`aX 3&P, f˪ 㧪棺|'\p UQ]~sT{aa 0+p4]'q'S;3s棺|Ss㈍54TQ]qr)D0SGuBJO=MY~{ao.)#HOwfv ߽:NE)Rvo+#GgXŰOB^-z \ !,1P­J.7;傃 ,H45?oY1, x&% G/kq9bJ :ӓʾk_gwaU/l}^wOw|Lx;~gұWX_s8wvh_[ z-x& J5H2hڭ;Smt)_8V}ZfY&~.q7 32Wݞ RpssCPpCKm\G\/ߵ  Y6L {J#16ѮT":>hbvֵh ;ڪǎm^ڵrj7j_+q )L"=aطv?:CvѮh ~D؎p5ق^ X#L KjדZbٹ"vֹh{ϽE,?i57 p#[a0(᎔)6 \pUmTMo\0ڂ^X#L KmZE䉨bٷtEqѰaâS蹎ҿ0Dx¸cPiBۮ2'0>4 ? >ŽL ,U|Kwn:G.H~fB7X#L K[FxK!9N=MZ''R#XrO>U_=s M6|unr+TJ LjV #$yyj+zmaaX 3&P, xzzo~8X%m0 KN7Giӱc[M 54ړ+7"qXRX\?-0X;{Fuw:| G.ӕ rۚ~M)!.- 0p氬'|`F`Ν|6|tjKF?iLGٲeh? OȋR+YZynU+7k;v,Onbf[jїo[8 0&`;:t@:w~U`5 ] b oT& \Uӧ BP1a!ٓ03&2^|EIǢ Lfn0h֬m_m: *o߰, }&\f͘bϊ(՘SqKNgf@>v1xۃzN?E Æ4x 0#@z&y6N\9u[fnǔ SN+|_("R Ed %Nj`iMaaaHOOGz,-cL ؝;:InX552Jf|H9Ѓo:ѯFVk߾3Tlق^z aM(\&,XBH`lVe8m?o9=`@ _} g$`5I0lͫe1&Pl\|7oĎ=qI E {诡*ڜ ֚;=b5)7w$FFT:@Ν.G-V`"iY92n:NaYqN&\bbbp5//!##G1 Tc}Jc Z=Z]2dbŊ^:쳪&k2kP o9;k h aؽ*8 0&V 8؇wV毑/WϝIg50&`L 8(p`L 0&`aX=c 0&`LA 0`L 0& s L 0&`Ja0,&`L 0XVϘk`L 0&pP, ;f1&`L '°z\`L 0&`aA/ 7 0&`L@=3`L 0&{Q۵|3&`L 0 ݻsrP$axc&`L 0&`ӦM+r=%PR&`L 0&|Ͱ]4n1`L 0&0l%\ `L 0&`a 0&`LJXH. 0&`L0|׌[`L 0&`%, [ $`L 0&|Xvk-fL 0&aL 0&p>, ;53&`L X V0&`L 8q`L 0&Da+b`L 0& w͸L 0&`V"°@r1L 0&`GaטeIENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/simpledom.dot0000664000175000017500000000263315110656147023006 0ustar memedigraph { compound=true fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" ranksep=0.2 penwidth=0.5 node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] { node [shape=record, fontsize="8", margin="0.04", height=0.2, color=gray] srcjson [label="\{|\"|p|r|o|j|e|c|t|\"|:|\"|r|a|p|i|d|j|s|o|n|\"|,|\"|s|t|a|r|s|\"|:|1|0|\}"] dstjson [label="\{|\"|p|r|o|j|e|c|t|\"|:|\"|r|a|p|i|d|j|s|o|n|\"|,|\"|s|t|a|r|s|\"|:|1|1|\}"] } { node [shape="box", style="filled", fillcolor="gray95"] Document2 [label="(Modified) Document"] Writer } subgraph cluster1 { margin="10,10" labeljust="left" label = "Document" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] root [label="{object|}", fillcolor=3] { project [label="{string|\"project\"}", fillcolor=5] rapidjson [label="{string|\"rapidjson\"}", fillcolor=5] stars [label="{string|\"stars\"}", fillcolor=5] ten [label="{number|10}", fillcolor=6] } edge [arrowhead=vee] root -> { project; stars } edge [arrowhead="none"] project -> rapidjson stars -> ten } srcjson -> root [label=" Parse()", lhead="cluster1"] ten -> Document2 [label=" Increase \"stars\"", ltail="cluster1" ] Document2 -> Writer [label=" Traverse DOM by Accept()"] Writer -> dstjson [label=" Output to StringBuffer"] }opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/simpledom.png0000664000175000017500000012522615110656147023010 0ustar memePNG  IHDRtۥsRGB@IDATx|@ %)kR`DAPDEIi{;Hi$/rײy'̛7۷3f6 )`L 0&2\V 0&(`L@#(k!X &`l`L 0&ڵkP3899deS|5P+VjhXy5eSZ(d͚0cGE SA.rԴa>sʢj)MO(NF 뭆R ӱk5Q{_Ss R:|GɽI̊J5 qև.E*tHH<<<4%.]%IQ/66ˉFLL ,촾09!W˗b'&-޽ nܸ!~tjt^*ب(ÇŃa3< 'Oib(Ȝ<&zEmO#88XQZ?""B< ϟ*j&L*ݺu tMȑCakt=[IPs ޿ٳgG\NJsmxyy-霒5wbKdjLO0J{rҴKOJӛ:P=S,>'3#L9'P]Ŕ/UƲL9} JBz]u)e*%(+UtPSo5ieNz>2^Һ)CMZY47JI]_P7G=6=5c`L 86`.`L e-׌ 0&bL__5̙cÇ9dZ'8rfΜ95ǏMӘ>}j;vL\vܩ&&6 6L4 .\0m…ؽ{t@͛7#6E~|f]'2y|̛7v2|ヒC)JD͛G{gǞ={C1)0ga3P?~g)z0R 'SI>iM@AJAҥKd.ҵO? u{Yixѫ&oqS8p ׫&|5j ={e˖K>bLҧt+ hW +ɷl2 2k6ֹ}Gxw͛+:X%=RRZh aNqڵ&oڶm+n*}1y.UI&ڄځzio"}h-$5htoڴ)Tb*xOq-_dFtzz?FV7⧞@7O?;"WI~Gs/$hu{zQ^WJdRZJXZcL5iC zHWa^zH}wkT!YJ6%QtHH)S`妊k-}CZa2ƴ.\/WD+W,Ν[I2s0UᅨO)`MD4'D.ZjoQ cƌTbRϦM6"ɓ'?u֦*I(2Ю]toBqԓ:wwwhHh%?Dj֬iTa=( Ji*T z>+UiӦ P {=GF{С8EsgJnrr~S$nb6mk`*z_ "DCdA @=:}tוFuE3M# S~s4da @Fpb%SiԆK,Qc (/~A2}H"IEn4d~ŊKWX_=]Ca)4jKCN Cs1 # z-[O7zJ.^5@i{đ &(HT$On̆~juKO6őQ^5~7Me)Ljoy''2TsРATDFN:ԏpPPx]hIihC%aСbʂ˔)q@IS"uj?(ѣJ2"f1׫d\JAHa˖-@ՖCC$M~5j$zK4GEiN4 <FCd4$E - !QD/-'] 4K ǏñTo%yۧ ca1^8{D5iZ;Hɝ:u=Ao(0}Eƍ2BP2|V9b2&+&=%(=e֠h(QB ٘!hڡE"[.h) =ʽ)kE # +Syz!H *M%͵=U[31{%^,7,F'9ZF~t9f%S?DEz 4lOdi]%n4O3rL@-zp-ϵfYQ& E⩇M#Liio|zik҇D7M0a@>r,E f͚ g5rTsAнdɒPqdJW.-չ3f8qb S7vBjTQz!-y!]/'TtBrH|Z' ə!Ґ וfՃcғ)qlLB!J/0O꽿d>osEA%f:* c$I:T8u% Kq 9Խn/ĵ% i1"pk|ּZȒɗH,/ii4,J|!/i&Jg%8ZHIվ)Z:kEky*CvXzM՗7됧&! 9"UCzZ{z&@#4Vj+6ʲ"-ՑӨgTW[zgvZhN`i5l4ʴ$B04L4aI |5uˡz(ͧFY>}OfFyM9C?塺(WSP+̅>$Un}ʷxbIJPn)ͧ4=~F 뭆R eIߓ{3WJ:|GV/P@JQeMRLȩ(R%L 0%uoH4L 0&E([33&6cɒ`L XDE83`L 0`l=, 0&E([33&6cɒ`L XDE83`L 0`l=, 0&E([33&6cɒ`L XD ͷDY$33&`[&=ï2$e!C0h L:}|||$0&LDzeX#Fpˆ@yYlق˗/kFĹ 6VBrE? M͛7#22+WL&ꈈZ{%}7Sشi +V &&'NDVp}O%Kc?2씏`C2; !\ %Kыm%ĹwysdihܹsIJONbW\ sϞ=oׯHK=:XhB٬gϞ8}tR2eʈaG<2&`Sg. $SR5::Z>}$œadr"ohùcJT|ya  tR1t]R%lذ@k֬Zʓ.^___V) Q?s. %@W{Z ziyNbXSN!b?Sҥ#Gܼy3ػwq Q?s. %@X a2 GEv# kӜKDVZD<k֬ -Pчô| LHd yQO>K"BsqI>AAAb8z4G য়~J_2@ܹ1f̘W5`)QNQoOVZhҤdқ80-`VQx]_7)ID`ԩv*QKɉg2!W}8s)L 0&L`l'`L 0&`lÙKaL 0&`e8`L 0`G/pR+&+@߾}=zI"F)ȘSW3;w3'JgϞ+WĬY.6 Ñ#GpMr)Yf ^{5 ĉ'Ϗ%K$ B?&` e+5Ǜ5kV!z3)nuV7|S5mY|7mkHg2z|sz˗/dWđ1k.DFF|Ի^9 C1&);pYCu2hժUnܸ~%KC4RJ%k׮]2)lSi9USϘzO6MQ&C.z 2dL pO[əjٲe R'KچӢLq4?lhΝ;4,NR:qw),]T?S$-L e6lZբ.yN!=x Zh! a޽E-[bI[lki.^׋/n!h%Ϝ93MsEtt+&>}(2s|`JЛuW֑!%'CMH5EK]!csr},Rijg4>ō;e&̞5k7E-gO&em8V[=;w`}to4-=H a9*Eۦ@tL<=17Nö-ȑ#ₙ)[Əs;۷a7_I'qx#4,GvthYFD×#իI 0"=ej,V<s+1aP-xv3OsqL4@``ƵfHI{)w]X̘# h[l`Uat;k?0plXE,?#Ԧ~Ȗ-yB0WonL}?z'OtL `y^5߻w.=߬ -,aFm{ s0B;̝9aaa(`L (["p,[<C{ׄu 2^h4~_yh܋++8xVIRiʖ,`_&Eڃ?OK{n~$K~xDF%f L69C $݈dQ 9_Z6*W~ss&Lsq%pMܸr8nrWXtgϞKQWSi4:|1gBOc2tH>-ޝa1IrVo>|UǠ\Ӊ &څ3.: ɰ$t{ ֬]`Kr^&lFв xŎ]4'ri\3 ok%v3\TԋGn=ิi̝"Ĥ^qk  Ó_`~OtlHGc:6ύÜ0Hv+_9=Wh>N_|L(6e[4 |]hZ[/ZWNyԦdy^Ԧ26G ly= ?^_CK> ~]+|{pxz8oG2 `#`}/Ց5$.\"K{X[`iy+G8yUFAǛE+)$#kH J-ǑqG([|Z|vx8kۚBGZԚGhh(o, 0k3eD ,.}^BKUhӤ,\w Qv^į7 #㓒A^ nrZ.Wf.>(^' g=$1ꭒSk9#9zs6Pb\ga-)ێ-K3P8]#CPՉs~ܹz*z;^$:41:K]+UguV''1hrĜe;Y(͓+~N8U.[P}j~[FHN_dA"R'6i B9rJ=Le2| (?sF$oνH%Uy><@ އܳ.9~1/mHR`:{vg\1[ ^9\E[Ozw&Cu8(g,Ӗa8/>| &'\N]X!`Cl90&yQBb"ay\ + ggQ 0 ``U,#)r8vet{TlÚqQW{f4jv Dhԭ[43&Al5(jժ9q],C&quQ_fݵ7fuO>,.^}瞠uv`@#먊@ŊQBc_qJU>%~.>w{|777UtI.+^8w;;~ S %P?9mPZLɀ+eGl5$WWW -~_{k3^O yszׅ e7q 96ZcنDlSL[t%APչz"FYOuIE.r΋ogGD~_pB~/<KSL h@62KaѲ^~n\ n. H#xy\?&el7Z%zpiYP:vrj+jWʃZ doxzdW)1c?Ii-qsh4ihflpLbl-F@޼y~fPg,S[vgwa>ˁ 0$F1ۍ57kL%j޽25M]3ƃ911Mؾ/G9&Mݾ\;3DEE?kaΚ5+͛`9Ҍp&eGk1 8L:e*hѢ{_a&Feh&V .Znm"1qDZϞ=gL 8 6T ?~R-v'?>_L0Au~c`Zژ yf1wKZ oF<489_va5 Q&M_Cuj}Zl=zÇ֕cL@=6q#?7e :z"E`ԨQ)5N%|Qh֭[F9 0m`vaH`֭bիWSIFǎÜ9s{aKKfϞN;v,VX*O0&ml>9sFHqF2iOƸqD|~tp lXK PQ]k5kþ́;wx3PdL heM7+g)ʥ)e… s~%]2ՙ 6G:jXo&) QΔ͞y*-W\Z~ܥЦMKxӃ(;K~0`T9rhrMöx"F- n,YPX1Ԯ][8 &LGǔ`'FYmZ@6o#LgϞM5-[6dϞ_}h-o ^/I z+eslSRL@(kmX3 NW E=c dY&'0G?rڽ{wQGCL#F)Gna=`Z<՗$Lr*/1<:R:[┤ˡ7hBh0.(R7 D2PlYy;X4ҐKy˺8{>|"{e5<"eZNr[qp=,Yy*UrR֑3sQ6Z&C6gPJ>.U煛4zp,;e ˔Һ3JI%FD>~۷Nbm1+Mvm۶a@Tꍲ5r#_p թCzl9Ґ1ŧ=8cX}7 ϞƓE͝6k^$xmѴiS7+35H? i3@PPxܹBD54o[H2zDcض! FAbkcmyS?gxiW4j^Kvvw}rWǣK^bS[)ѣGhy6[՛jFpmXz.ڽU ^yM:YC hze4jZ+<ekȷXm&nGQcQD߭a?sދz~,{㘀kS.V!0gغkm,d} =k#{h~*(6C幉Z b (-կr濃Exxa43]`浼r Ŵ?bfn@KP94-9յi޾1^`sQ cY;5Q KE86ʎNsp vz~ Q΅{a7'qI;|HtQ}7K@gZV ap`z'FY-lf݃vHjҒ!~ 6S{˳̓~_0Z a?\5tzRϚmѲMeT̎3$-36 hemOhGsx+ /m|ͱ=hQqp(Y ^k_LGqisD_ƛ]joG_Cr#B[ѩ:b90=`5nCL ɳ=GTd\t^8e֮_εwo{W.&VC|M߿߰(knVm^d2y xqsj7ErO&;B}U^ux5}jȈX|<#br3֙;%j'o^?]ɰԬSD)|2Uc}BԩSǮ7lUXoƒ)Zw|r@Sg()N\nAӥ%bj鿨R0ʔD[xωua:;Vi[F{Zk -аY!8v-Wڈ'~/?b6֫!ZƖzkd0:pl˞4yJ:)o~ߛ#zA5iEKkgsxޱo4iY\ ӶKN{q$*ƍ\2dW~gҮg0k6F#vT[ڼ4v͌#6zlU3DCݍK)!y6',8"vwC-YQD߆}uzJ"ˆR_ʸv>^yL悮=dwM cԩS/|rZ ڸ])H[:K3mrC205]M{ysL5 vCr=5j}izE17V9D"Pګ|a8<b @nEzjsz&(;D3Gаp)iˤ͘2ap̢ˊU I=͗u&v{dSWŹwe)ыݰtQ&y(?xp~yL47]r"/k~E+(:tG&wK7vrHs dJIU  Mg1#^Ҵ*ƛ]ڂ|߷ψ08#0Lpn1'Yz\t=r=?ѐ%sشnzOnoKK| ZbzNhu޳#h,ڬr}S-z#.eg[[iW73ژ_pfOR© 2q!jA&䴴q <ލ8s&I=hG~9{diK7z7fXpL@ VCKi<(U%C?LE/Bih}!2d6؞mX&`ecd2y"KvB@daڪ\{e:ث-/aZ#Zkև R`FkLF6|^.#J;j`>pc`3&𜲎ҪDG`zzYVר]ٜ6r7;[+ל q5+A5(Dn␚}]7-xnk3}]mY;&`&6gcL 0&`mlMԁz c DMg&G|eUN Ktь\ &eUD߾$*}>%Qeٓe{xYYx42Ϧ4 ߺ'ıJlGm9fN9cT+qp}k4c?8yuŭaժ2TsʖW5 #fU#4d#FR;m)uqj&`?lZ%= ؁lٲbcE$:{6CdI/=}&?,Jzhnq)W[T'O5>IvN4w>zCҕ|Wɧ rʥ$i KCK\ ۀd2,޳YdDlRr}p/2;xz=w7k'Y oB=nWIrq/\v: -T2Rӯݐ# :FMaԳR0~ [7FX[W0 d76o~𧋱q d:˜?B3d~.L][aݢ>ܽxx LjCTdrέ6Y;fn K2?`> A݆^M0d5YrV2Q"-v-|% %9;h w% !Rzrޣ=S[n K@@Xc]$?w>>1"y./79gA^oO#M{ 0ibtY_"*e0JX߯4z>S)KsdO\d?6K$cELKo@IDATGEURB҈sl;w[v=m;n.7r%nBA F^qF8Yė33kLU\9?*nJZHVr,/Fɠt Ha9ޱtĚeGDO 2J[5EoXNOk |ػ]z=3~OǴwS/R©;qm+R%Z\qxEҪ/T ԛ]3/F 2m^Oai4*\emVX`z"ߝTO⺘EE7"ș]z$-(݄,C :jINbQ:dß-i>nrak_ymԬUd)RZ5,]1 47o$ozizH3Ţ]5*t Ob;k''a0.^x~L]ǎX 4-Zj̱0ӡŲX"2-j:ٕ@ƭc5ˌ~A4\wx^|)H1Ұ8P埈$'޸4,dX%&:g?Dݺu-*/9)U\VTi*ؕ0<'Is)é77yK?q!7A#"C>#ٵ sECêcPPHg}5q$!2s͡>6ۙݢ,rҼ59d-'Kx~3rvE|\S$Jh:WO3_n?w鮹jg|#۴3XQOh)R!^'=$FKV)3%^vvJnׯMaS?4iYNb$FٖtYxlt6"'sO&xY(H"F9 h_k5^BeS^F"FY_ڰQ^SҼ2e k/l՞ )ڃ'6lWׄdyNY{e}njÎ^kJS^F#FYmqOY{=e k?lצS^3rOY{me.jkFv^F#FYmqOY{=e k?lצ)kMQ}vaES7ឲ{kH(MuQ#S^3ڄ56kS]Ԉ׌S^F#FYmQ^3ڄ56kS]Ԉ电׌S^F#MUY̙3|n޾Ǐ#xzz"^x,Y]j> u$$0p<}!>I:$J:!L}eP:9s='TPW舁FΝ;s+|7PЯh+NrIAf)ߣQzE|:ӤvLk(Gf.#O1"-r̅B~QD03LCrij}6oXc'H OMy .%edL$!A;ũ='w"ϓu^T'On={1(TR[G@@%| Ú`۾mp/yKzjvCqpC?sPRNNp n铂ar/4^:N:\rЦ0Д,(gVPh,s!څ- >.狦 ؿ87@޴ke+mPn}ݳkQU:- c0ax*&s&Y@qC=*!((s=BkTٳ1k"j5F316ƏzЛ֞]as*Ud,i~:&<Yz몒1v=lY×qֱZ4oaze˖YM b"=ekt`9;wnNJ5sa@-߁kb]ijR#G܊mixܔoPC9*WQ[*$Ja8uhQMW˴{dZ v@0`DC6F.EaWj,ڵH*>y$կꝫ Dsy&sv7~|?. Qe*ݻfȩ,S&M+F} #GΝ;2ӧ1axZ9 d.8k4#_9ػx. ؍eVAXt>> ̥)b0zR%rʁy>_ŵkZxx8&Ni nڜy#<;2256ʙܣb䣇VF?}So$-10"| ƻ+ ~ўai(=NCEdXdzI%tKfq`z#FYo->6m@~'VԖ% #K~NHHģyz idi==M֖_*U- "X~-I&*7,/#'SVGN8^>$+<9"CyGi$OwSrD0w\%9 p(}Peuee]˅cɂ} \g^% wn;E2PS6݂QrX_T^F:X0{7 e3x,}QDwn?'(:Vǁ=f Y>ra[2h/D6UQQE\{wÑ-;LՊcMu>KȌb毐@nPR5Jxp3E+AM?0~|oN_xzyG6ԏgJ) z=v!.LYDrzDׯl҈xͿ;VQ`@Šå* X@{sĬ*^]B1{vl;<goMB>bȕ(SK} ĝߢXV/jH&iJ/oEǷDz_"v~>~=bbqt] ֱ06w=꧵P2?_>(Qq|0;JK˟_.\6oP1 F!zJ; @#wtJ[ heI ޱn2v)CEV:bOfJC^=0tL{VF@o[l^ν;Q80rui({nw{+Sc]7|DW+PtmC1Ε/^!Wѽk5bRO4P^Y Ʃ]gx;_*w+]#4/}Cd?&Cp 4nn^K4W^4T.D|Ksde qM-[V>mOZrx0K.V4-2))A}HGsl#x EI3<|@^pOYJDFFJ;edNڌ*K~CdDeN*/ vo٤]f E!Vհ_gW i[jYV*߆1 h?V3N0ҒOgI^ynJ^ޣ[ >𶦵֏G;+4K٦I_1syf܋܌k/Ju} y2)dpi/OV}Sp/>J}~K*\؎,%QY.ʱЍdiJ[jI/BQiq=L29"9Z+@DN) Rw&^`2{SzB&K7$/7FzeeDɎQi Zڊe[ev!PD,-C+r22 =tI.5J!t6zvEx6`-m\_SD1&R(gppL 0&d<,NNNX4olE6{Qf6+8pd#cѱEGG` Ph_k껢v݊?G;YFU=ۿjӎجVSre83 X&`/ QDWy Ӌ8X1T6FUa%5MNJ!F=~y#3%u,4J4cUdblr4?X!m͓D\$*}>x(;^1 pl9=HC&@N|Q :$6l+MFDiVz|Jޏµ+&_0O$߃Wm6NCsgyc:qUC#7wxgݗ|=g1p,_>|)Q6OD6 eA)g * 7g{dgvd͚-*29sȈ8rOvO>U2=%ع3Y 9ki_<ظgXw:D#Od q2yE֘dLsvy={&{X0Sț7TGʕ /u3_ ^~ .:\Gv@DEaLq(]40 r|| Z 2mH}Ֆ~ ΝKttƊl:'nD_!KoGB"Рhԯ<[6c^G݊հVr( GF\S[A_1ʑ_R|x}ot~Rʎ@*_j!8J *Lh;T*:־ܽ,Z(J܁ xDDK#.. YU 9sT\u:2Ogi,N(Q8Z}y<-v:5D19sxb8儥ŪO޹U L@(kel?܊R,PZ׃^ xgM݆nb}Y~vÙɀ3tzd '8r;?Gl1wG8pzt)"Ŋt4/Ii۱h5 =q#W?ņ5q8~ 71Ophذ{(_{ֿ|H3f|PooF! E\D|uUhTOFK )V (DcBbQpC 846|s֜bXIΒ}@=sz޻D^oO:P S_} 9e=mEoԧ`n)+ۧ:׺}U4n^^|krTOZ ۃ'c"͟GՓ*=L-gdn\0j_{DCUzHoR"MAEDTE} "``AT, ETW)Ri"HGMnMnro?sNfϜ33eC\eF풺vP. +y2d UW}'/>oފ:oj6San_YՏ>5)nIWT~9Ɨ#a$kL?\YnixF[Z5Rm đ^S-_+,UGbJ?r\Ihղ,_z0bˋ|uvH-Kޡ5jƐQujt٤cȑ,2.ko6+ڇ.;~7߼iʒ0'p/[r@Zj/J7n,'wƂ.SH~wCv&+YKza\ڔ(e@RN1ԱQPBffj,2ڿrKꍉ uՓؾ;[lVm%)kk矬QWL&/I"}nxU/[ۺ} j|{O꾺cmFTQWmfF:̖-tiUg; #-kj:51tTiT)Krp篒LiҤIx0 (*0ѬVn=d}Ln9E}Y+?"/}謳e,," ԹvGHFdI2*[ZFsIGJ8[ry+;{.01Hjprw77o";;˭~On~KvD+crs.oO@ oV2{g2d-K{;J%عK]5x ẌR@:eXX> 7 SL dɲ2ɶkC&PHgg5_Ɂ$z缎V5 E^D~O0qY'eWԚL:NUƳ*^x=E`K^P5!W]>9bÆ 269kf _씬ϊ٫|J2x[ַo_5kV限Erxm۶].^Z5 aݯu %H!#^)YR/|R)Ւ{,af8fJcWJL?u]Sڵ*YͯvҡٵkS&r,*YD*MK6u _[I uP)(cruWN ʩf^^)aXf~/2D+翖敻V|{Si '/ l[/(;$O>p.@J:%iG亯MUz3vduPn5=mԕw{&yn W_J^M%K .)jy|=)qXF8h)C-nTM:uҏTX*/E}ٺe4j(k\ njȄ7'Ȯ廥Zp^qzA}ҭAfԱSt %gE 6ZjɼyO~**Zu IkhyMc?m9(NI]scz?z,ZH>dȟ^ W+(%ܗf||&/Crhaiݴ hT^C)Ic)I;eEjL_.^(V5k?oQo:^Iz?<&~Ƹ4l"ߕy&?{@a朿)rV)3ʹ?/J9|jRNcA%S޶}_N}crYe}:?4&!Jg3f֭[*Je$@$@${VI$@$@Q%@U̜HHRvϊ1IHH  {TY1& DrT2s  pOJ=+$  R*^fN$@$@ P)gŘ$@$@$UTQI \tI "۶mKƾKzjOe̙ҾǏk/^ÇS,4eP) gB KJygϞk_|!V˗/ھիŋ %-d̘Qwѣ$g8#@g%x"p1,+IϞ=+GInN,ZKeZջwoOY1cX7xTW֭-5>jٲ?[mfiF>Qi)%n)%jHӨ_֭g9WHk޼yE9)SX~ͬYf:>X+t(u$.ӽ`KHb=#UVչ[%ҧO)Y>KJ}馛dx Y= O>ȭު-nD$3UG@M>,[L kyM +y*TH, 4nnXvQ}hwgȐA{=m׮][[x[m)޽[[;vx 3мys}8 8% $l @ 3-[6-1\={ĮZj]Q;.pgCv)O=l޼Y E+e)ˤIDYZyCC  c_l۷p9+]6mw9xQ>:pPT)TR8sҥc@L6e$@)L ]t%b|ȁd„ k Ş?~QnQcfQ.pmBo^OiݵkVMgNcrk|<8 :5kV\IbFr ڸqFyGı=r_c&O? &[?LPh]~1 3{1m;epUWi0ذ;t*Kn]X8| {NF]Ű11 y@cBɓEYkR3QNu1aƣE &`mV[L2dT1 .qlP'Wg K` @ғ>w7{lK`Z'}1XK|0 ^7BRƣ=0KjժYj&/[jVƌ-YU9-bLf+_|R؞s@۱cZ#`)e'COB5m)wƜ-LR3-0 պuf^jF@uDNH&pM#H *ܪ2&=X*a%Xl˟, kϚ5z 2&aM-5JW=>1peMc]1&x4 XÌ1w{Z_񸟸ڲe$lXoXaL1B$|SN>C@ KK)dXjo¶ #&@K9<3$.L*_|%$@K96 kE1C}Ma nI ϐ9@BP,57Z} dRN&@&'D'Jh"x-&H r#ǒ9@իW`%CQcH r#ǒ9@P/Hb)u KETʩE@ x>IqCJ9n.+JK_s82245H #˓@ԩdɒE+fl *T"I  pY]P "ϔ911cʕ+$%p)9Eէ' lٲE,X@$ab"HPgϞzHV-dM`Ȑ!.'0i$@$@EJ9kK$@$i$@$@EJ9kK$@$i$@$@EJ9kK$@$i$@$@EJ9kK$@$i$@$@EJ9kK$@$i$@$@EJ9kK$@$i$@$@EJ9kK$@$i$@$@EJ9kK$@$i$@$@EJ9k=*O#GJ:uҥKn:믿\iK&MgcǎFO:Uo.3ftɲeˤSN2tP=y *"r믿Ӿ%J,յkz°FbYWe޽~ٟ=zЊ Hn$G2|)X Ny(/^Dg˖Mt"/'s='~̛7O^u={W@mD?ӧO{Eב@3gN!"@RYNBeyWK .l(KpXX=@lRʗ//_|'/R*T ŋ6mhkN+ҳgOsB~QNoذTTIJ.-=XbRL78[W:I6{PlYݩR:ݘ1c[o{)<++YkN"Mj ߮9|׮]ȑ#:o %K,W-ZTFϡvLJ9ʀ}bؼyVVBR6V1FF,ط~[㏲{n0aO:"8p>;5]pA0?HAS,E E*s(%(<(XL2E>SOTXtC cXLRbKAaZͧZj:-5RbE" *^n]M6bnut~ه2әA89\F0D+cᚇ5QwxDPH 5P)u2gάpBvk֬Fq0Fbvٴi4o߾$ƍyl'[nճcfS\ʀ.U([ Ч`j=+fl7~ drcm6±|B)A߮cJ2H `)1A9 1QFy6q1꣏>.X@B)a5oةIfۺuk= =: pC:t-y/r 9]āk۷o;F)j#ډf``E'la%O<^5pS/&aH1VFrpF$0P)IH&JnPL)3c!IOPpYRŸ0&m"k8qve,&AP> Ƙ,&CaXŸ?yGcHw2 Ȟ=NX=zh=Jttv0΍ Y[͚5%TW_}U@=+Ws3;)= p}th2jE1c}P d;C$',N(,Yxey`>y+aBcy]GJWb㮰+zɖo9(T` 9VэI( Oc <X@xOpJbH@bńo1*UJ$5N݄AW񡜜ƌ(Z HH6E%XC$8n@&ɚ5k8ɘ"@uXؘ%1JXʉ$Xsr;Ąm! @K9%(&xSV" YSHR-gIHH# @RNy,HH P);ba <*gIHH# @RNy,HH P);ba <*gIHH# @RNy,HH P);ba <*gIHH# @S9Ka.\ˁbZ,xb,WuqT1~X%ЬY35kdȐ!e v%p8(V ,%.1 3<#3f̐[ @pL9 P% CJ9jLC$@$@Q @̒HH!@5!  (RTfI$@$@RӐ @P)G*$  pP)CiHHH Y @8á4$@$@$|u2KH+/^,}OjԨ|<п{OUs%uԑkFe ѣGeɲj*9~Ԯ][:w,m۶{뭷dƍ2p@T';K,O?TZi׮9w^1cx{?kG'|R.?|U1*̔:ҥKo2~x9s',^h"%cƌoϞ=r}W\5;wJƍ?MJ޽?Z+sG̙3Gsr(\0\r)!wy'Ix .Ԋ?3l"G!Cx#OΜ9ug^ O?UJǨʚmÇ;W~ F25d]o0o9ON8s,Չp#6qDɗ/+_ڃ8tI*V( `!N$O<۷ՙSLnFtW˗k 5iD|MOpVD)V/nk+T' W3؁hτxH޼ye޼y$ ,kk;(_v*W+XTVM~y겔 vd psвeK)UoD0kרQ#O'*Se==#7ocw}vm sGg.͝;T yY(-FXYf ,ݻw ,Z(6Xaݻwr(VH T 1n{Yece#(e˖9ĴiӴbU\Z`[ɐ!N IP馛EgoF+j^veҬY3A:ٳ~$\K„2x ek L2NM>}y\L-Z9&S6m * Gl9Nԝ0H vqROe'W}2jU+ra7,5Xqem2D~F +ZjnRLjg(n#pF~'=Kke.PV+]588@`X 5+VLX'Zjymsvab nGjG/".l1jժ:Cg{(m۶dJD]uF[{i=N"{G&Bk 'j" 7%,?`m"E%CaAAs;Ŋ4pbjG24 _|;u PP°ޱ5q{:x̌b3`a3W2`_ò7~zOa"XpccW%pWX0 FPLr 8 &Lpncqi_ɜ9>C] }$&QpCA`C\/,Zf7nNkܶX Kcpz_(۷ Ld Z=9Rucp3*;c1۷o@3`abJW_u(,-+=,` 7 p+P3f}o\ymذAw^oVڸnXB4P{ァ'[d֟9&z3 (mt{1=),xƚ!Hm@e3~ HB@cR5Nj)OIueZNKuceYui)&XY ˃'RM&cy°F)i QLK)KN]R^F-5SRt|-a9l)9)w$J)cYrI{EU K-5L]-l,콖!ئ"rZj֓RT~Dagai+%l nR+Y 3j(²+0$: uzuQ)ŬaYJq:` `@LոaIchTF]KR1{PBC EK^ -u+10sT?fMÕw"au9XMҲqkm w<Ł҉zƌqrp . x#<p` a<pr[ΐlj,Tʉ|u6H&⁊@aYMZX = gMX"F)nNED='pL9ѯ0G l,fb|0 :XR kiRW jlw Ni{C,>7o^X@%@K9Lowl)I /^\֬YCH N pL9N/M$@$xE$@$@qJJ9N/M$@$xE$@$@qJJ9N/M$@$xE$@$@qJJ9N/M$@$xE$@$@qJJ9N/M$@$xE$@$@qJJ9N/M$@$xE$@$@qJJ9N/M$@$xE$@$@qJJ9N/M$@$xE$@$@qJJ9N/MX~ 6L.\IzQ{°kӽp`?>9 $r2 9sI&ڵk=]h{뭷+'@[NoԠAYt9- @2 P)' @P/^\/_… I& e پ}?~\ZjͿ'NHbd-[6xvرc&ɓ $NJ9l"EkW_xe<Ν;g5jxC$rR& !&+V2ԯn+Ɖ˗//cƌcU ,(͛7czرVwH 8y0 @RϤTRڽl޶m[9s\y&(6SLriCzݘ v}$@ RΈ1H \qz|I.KҧO/2d wܡcNtMz-뮻LqrM788~駵$ǏKMִɭ+1 Ӎ pC@=*UxE}衇ʕ+[R ǁiXkΜ9”9sfK==ZKY|͘1ܹ><qtҖuZ_'iKR,r[JYJQXSNS9RV…-qTgºxW:sp+o￷՚z)Ei)f͝;RkKY:]Æ -EY:rA[}>:(ρn•ڪQ檼:שS'K)Y}@i-ZJ[A&L`ɓʞ=<_ÝڤO_DHDra&$@iRС,b/Z5n8KYZؕUW]eYv<+VL+CiEsPuk >܂2r 7:"EX3rġ  lٲVXaȑBwRVxF4kL?ԩ! ˔)-r[S2qDK[۷olذA$ ccgvGY9RE)U.GFF;N{jժE|-WH;}'+V zð.oeooWƐ^|yQ^O^ĝ pL9ddL@$`'Nwy׮]X@w@ 領04I*X>Ne# ᾳdeZy*7O޽I_NYF0a ƸPdsSޱcVjH+9pL[pIf 䕈!.F&%кuk?()K(3RF]viK/I/#  y G`cI]֯_/Jy1 ˈqpڵ4m4l p1y-&a:;1 \MvNx=(f K]TI .`,;g}VkU ̙35Yf٧N2zofU/0}o(n(`BX%XX %5pm;nӝ,˲o9;O,o:kAv^zI7&n ^ځnX{+_͸k cG`eV dɒ%E0&vf뗿N ;Ni0O,2Y1 D"1qHi^ >l炉a~&aV(ŋ˴iӤI& >jk٪Z:{98Ehs0+_ To\ǿnX "ri5 _믿m-Pj6 6Y@g MW~hܮ];E't8:$j)x,yjOƧIt gpb? G\!uYD ]r[>۝FK9l?  Fpb|ٟ`MO$@RvljH M"LŒb'x'&]QH"CJ92 $,11W1B$Tʑ\H a 3N_ $aDJ9Xd ;1 M!*ȱdN$`]Ppk;}7a!a$8: H3E-˗/O1s[ RS@#Vv%*唠2H pa%P :L# $,UO!H =Ln*4c!#u&^2%,> #9s V+VA R`qf={M;*|X}vex=%_Ax0|!tx| &-nݺo$naνJM wd҆r M(m o(y 'J;pfϞT62p7yn㦲x. tr|0EUOR *[n 7B(RJڳgoAC)#{$2{l2fリ"&J߿Uy=}Q1AK,Gzwe!Wq޼yMRЊkQ7NLZ}}Iu0[9},q5jt[d,VSVIžyiڴ|2~$}.\(|^rwJ7j7Jp'5s=` CGW^KHTrP%YUW] ܹsR6mdGuօ.)q˗OP IӖ2~ntr^ 85}|$7$Qw ~\C <˗mK:xO?z+h:?Iڅ戀PX!?Pt#>@3gK W茡~n%ZPQ+Jcǎ:M7ݤ{n~x!~aAfVHN\ 9K.w`F w kz)c1TXQ{ r &^t~`hn >\? ŅkOG7p`uUVI2eEct`׭8tDM`!O&MVZ$ΛCt X-[͛koI?暛ha< &` cM^:X݉GGlٲ hy+ ŭ~5ްa+Nn$VX SVV3ZoSqRJ!]WK͍k0/,Z(ڵkH!\PD:#(dX5k֔gyFI5[V?vAJcʎ @FNyb|/N my?]}勱w͍ܳ! XK5k& &ہC9n<)k_?lgPǘ'ܾJe7!xO- 9~p`RV$ct1XR$끉xKO¸^0q/ =ΪInl㞁_~}A$`r Xaf͚.' V=`$:\(KtH³QYN8k=b6:pGJs`ZiY~Nx9I&LZT8M3wZe˖-'}KU/RNw_O[Vu,JR@V7 {5ꥬ$ Nƻm.HuRu^5A\>tw_Byw=a*nǜC]}}^?il/L|`-a0Ulg_|CC w|5ߞ]머vW/{룆̡V){ {Ы AZu~,zVv {4F[ˊݛܲᦃhƖ 0nf R(Hz`Ȃd,Q׌}is'x\BOo&X/%-1˨B?/{a<_ǘS(m=ffriKv_*gxDlnE2 X D)KXN!0"OGy BwƪP!_Xx/fTFpPBi܃JM{p}'pH(m %.5>Ҙߝq`s?$z!.?=&iFϥp;4J: Lc6?DlD$@$?`4]辒foyL$@$@FcʱvEX  4KJ9^z6HH P)a}HH,*4{p  X#[&IENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/insituparsing.dot0000664000175000017500000000427715110656146023721 0ustar memedigraph { compound=true fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" ranksep=0.2 penwidth=0.5 node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] { node [shape=record, fontsize="8", margin="0.04", height=0.2, color=gray] oldjson [label="\{|\"|m|s|g|\"|:|\"|H|e|l|l|o|\\|n|W|o|r|l|d|!|\"|,|\"|\\|u|0|0|7|3|t|a|r|s|\"|:|1|0|\}", xlabel="Before Parsing"] //newjson [label="\{|\"|m|s|g|\\0|:|\"|H|e|l|l|o|\\n|W|o|r|l|d|!|\\0|\"|,|\"|s|t|a|r|s|\\0|t|a|r|s|:|1|0|\}", xlabel="After Parsing"] newjson [shape=plaintext, label=<
{ "msg\\0 : "Hello\\nWorld!\\0" , "stars\\0tars : 10 }
>, xlabel="After Parsing"] } subgraph cluster1 { margin="10,10" labeljust="left" label = "Document by In situ Parsing" style=filled fillcolor=gray95 node [shape=Mrecord, style=filled, colorscheme=spectral7] root [label="{object|}", fillcolor=3] { msg [label="{string|
}", fillcolor=5] helloworld [label="{string|}", fillcolor=5] stars [label="{string|}", fillcolor=5] ten [label="{number|10}", fillcolor=6] } } oldjson -> root [label=" ParseInsitu()" lhead="cluster1"] edge [arrowhead=vee] root -> { msg; stars } edge [arrowhead="none"] msg -> helloworld stars -> ten { edge [arrowhead=vee, arrowtail=dot, arrowsize=0.5, dir=both, tailclip=false] msg:a:c -> newjson:a helloworld:a:c -> newjson:b stars:a:c -> newjson:c } //oldjson -> newjson [style=invis] }opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/move3.png0000664000175000017500000010702315110656147022043 0ustar memePNG  IHDR΢sRGB@IDATx`E@ $@!;{@TE)"Ҥ*w:I ͛^rze^nwʛ,nf̤yJcL 0&]GDٺh|\2&`@Z`L@XYi|L 0&=+~`L@g62&++`L@XYiL 0'>VVo#.!`L rG0&OۈKpx`L 0`e62&++`L@XYiL 0'@G;v CEj̖ fL 0mx"TҬ8p :t萪Brb&]GNuy0Y`L ++sfL 0&jR0& 27a`&*Y`L Y[3g`ٲediҤAѪU+(P@b I\?dٯ_?-ZTOցyf4k 6 K͞={pm|Iq`vG@S=ׯc…pwwMnJѺuk̙S>D)m6/Xxq< ӧOaNǏ͛Iq`vI@S=+"!C|7:ؤ*V7J@DDÇeW_}iaK/y+e!J\wٲen:7N}TR:?"r(ݻ>q]HH7aaaa`L 0otV͛7ABd _|>} O<(\0ڷoOʞWeK=1JGTzdM68<==eϨx⠡;cSLsI];t~ ]v>|^%J`9s&}]F{OShs„ t[lA"Ed]itРA4`@@sիW;v|hӦ V*ȑ##w9zМܹ#-Z${d.\s?f͒sL4DJ0** 4`1&XCSm۶Xv. ]w_%TN:IŤ' R Hmذ-Z'E5ydٓ |ĉ]C:w%Kz``@@sʊ" uӧw^p,{.l  ^x=(.r!èǑ;wn=H16nX'G) ҐA%W\BQȑ#2իA2!IE}W\eЍ7dKF׮]&W\fL Yo8eO(XT~) Cݻz\$4_|rJ@[^)c=<<$z#(ڴʕ+Y EN3gGC\f^ʍzh9E)bveSVRψg.bVZ^֤$H )ќԵk^87Ɏ_O.콑rP(K=z,ܽQJ"=rTCќձcL  RCsᅲ &[lQc 54G^4Cj4TFnΝrV'j*=zTatX94h h-mݺUrzz`B@s=@59R@>>>҈A3gz)P ϟ?`{1Bi,p޼yrb\sqq&ԋ*ťz:YQ/+zbdlѿȤQFMۓ+3&@ ɼ0qWCl>ϊ,hxYa^*C $uVM:9HL:hΎh3ȃ^:-_3&Mv1cFjYURjCJ+)ɢ>IqɕJCIoA@gϖdxYs_Iq`Z"9+-Q !rk*bo+r2&ɞ8? ч`L p[3A:L 0{$[3A:L 0{$[3A:L 0{$[3A:L 0{$[3A:L 0{$[3A:L 0{$[3A:L 0{$[3A:L 0{$[36R+Wpa\>O=Ca\H6 bb^o^~4i҈@(rMMlpuqWAP+ϐ!bPV5k!{|@^~'O̙vBQQȜ9|gӦM+YzcP't]0ũ+~N\uZZO\?%G},2!$$9QlUTZ  TIeEcǰ~";vEOr JGcZūC(PT˙JQQQرcv܈BzBիJ: s綫zreb XLhOT)ƒB*}֬9q ^V Yj=ہat?;cЪBQxiS^<' Q>ݻwS.SjsqLڇZS:VXp8`",FKlgnjw\P8X# @5GNIeo-=7\֟*nb)I"Ph^C$J,Pt1Yߩ7MFQ)PZEprGL7O>UF ,^%JǤIh _rb܏9AvC@ʊ)hjGU9E|+61׳P 1ټ~ tn_fMȃeY^~mV)U1}d`LxDЬ!#c -;8t#@5Y#&Yd9|#Xo/i`۶o=MxEuk,Z(QVƍ=zg#_|[qgϞ-ß={f0<~aD0 ʑڔGRdemڴuFy$իp1d0/Q0DDD xHg",Qh=~ ۤQ N}ȡu^oÍIah|YyhVYтڕHr?o(Tg ND펳[E@s0}6=GSQDd.33nY~-^?i2g1z6?~gYce} -X pRn;{6wԫ۶nQLd[ MHEGG eHʖ- ݦM܍ 2I)k 6?6ZQ>> Ƒ#Х/M3di-@j {I |}?=Ů =P`ܹHM\ΝCȔ%wVB?<~G Kmo(^}Æ͖Gba@" ɩWo1R%SPrueU̙kѵxDٻwҤO?y<ŋ'6o>"R>p?ĉX&߳. ޽&6lX'om&˗Yg -!xXOڤuR(["O,{BșUk1O.?/їRFHhZZų3cqv0,]-;&{_">pvcpF F.UuKEph ̓"G^ʛ7oʪuX|:[lV O];gϞb1xC}-Zy;@ɻsؾ0 /%6w\]XB~Icȑ4 3j|IϹfnK6rC3d}E&]f)|S Wޏ6mj eUX~$/uPY~ͥQc"2|-rq-h{w~.BgC|ZēC#FtG&Ud;4kE.ݕ+PzCQLA_n ij^Q?~CCs/q6ȭ^O\\˂2H5j)?X*B2zRjC *ȞU̙pS}1TE }(67̀ƍKP܎;Yz_H '%R$ GDD{z/ho+Y2.Dib"02&QE$V1O vÆCG]MF(4i ߖv-+sㆿ|)XwGMFtiگ\º[vJ` #ݹs=nE!jՊ ũ?oH }K0cY>ɥ9dA hA/̥;0P{oE2(oό{Gҙ"?yаH<]qcb>|z`]&%h3cg^|)(,HŢ.k*ouE;g+xHL4IߑBP;MQ0r}9=\7NR )XёH,2e "z}}QI.^B?/bT uhkZ8_y*z?-{gP\l[7fqPUAEaˑU)-^R<2GP`0]Jҫ#{U#Ddxx`&l84^%l> Q-c.CGPXwV F˖_bSۿXPQ|ʗ*,X0x>'ȸ X΃"^<( E0 a@왓Z}p. L]2Q ̄NNƣ_tn=E~1EGyG?=WBepc~Kq#Asd4F/M-_}o'+'9*" XTG Ӣ^z9&"Cő% "+>_TtJ)3K|||la?+Kn݄%'q&^zR<nH{)I0nnngz6|'OsN⸋ir2#L\ч#߉W]:S?dC))=1cz spOa%!#{nUrHkKty|eOaVX˲Q]Po-T^[+{wߗ=Iʷ_Vzs_4٠Aa +Mo%Fcf( MԓbB}=LU7(dz>u~<rB~c""_(e2^ɜN3 Ƒ r!MS#K)W|Z)Qˍ{MC׮}2e gQ^rdDOrMN* m< JÀfԟ)fE=bhqwS, VOIyA |С"NKn9DF qɡ+J"Y҄t)`\g|VߓeumhR"8{59 SK5 3}f 92(=Xɓ'j^&l9ɉO5UȔmФm)͛7(2( SpgggVCKǬSΕ 0&&&@3o=z$-/?^H13W ?!@C,[u0`4AJԬX@͕bMsf2Tx#JW-6$KnDVzIUZ<'q&eCYN^MB9zj\8 sФ9+l3dH/w@.]1c5PT7e<{'ׁ 0&Фbs_y5]M8y8hngg=ln=qsfDl^0jĴ8VSo6 rw;&Ȋm䜕!5lӯ\23qDy! U2mJJj[iU*`g4 Y6fӕUrА*mJv%ܶl87&`[xжڋKkCga@EJKIe28VʞѢ`>{=ԍ O 0 %HM*tNlfU_E]lc|!ֿ̾ۄNΚUTkO>M*W1 O ɯEٽS{{{~b;5?SW&jD_޽w0px$#[ִ"s_p=[d2w\0QӀ62_1|Ԕsn߯矏 ~h^YjOOO; Ƕ쇾F1k@ٲл| aR֮jU~ɒ]8{6;v Sf9 `ʊP*T_ :aCx &.Y  4ˆ/ǀOM61` Q3gomChhJeE{*zRi7v+%7J@Y+F$Ynň)KPR4[93Kb3Qyz٘fZ.eSǏիW]hҤ22f̐2MiћzNރRp*۔fF˖ĩm|=oxfAY NNi*@Gj+Ⴔi]N܍{S: W߫)>ݓS7<)8:5PmJ{/'. s|hԬ\GPk#[b˖{w<ʼn=,?ЄXvFT߾M\իWիWС%Gſv.^{._CB?∆xT(S>WQ4T.VI.q\͓G잞[lJ[BGbuE\zŕ+ѽ{O{6gPVCH*~tm̟=~]z[-S).(ኟMnܸ-ԨQYlTNᅼy \rq =3fHMAW\*nmG).5dHC{ӧ0`N> Æ e[+WnYЬpصkjԨ>*,ś1Nf ,WC}ċX@|۷oMk֬{1i$n>'6Ɖc1aQFcǎ_>:jժ=_g@'ǏQfMl޼Y++eqL %h޵aÆ?0w\,X3۾kJXKC8p@z쉡C",7VV֢\# ֺ‚ &@gƢE mLF)++ocF  ׇ~(ve߉…4Y>|XNCkRq&ʆn=*9r7nζ[!+<_|غu/f9olH,3ahY5oe˖)-KyȑRi?^ZQݻ7e8 tpѢE L:UVXBm۶bQ&b@Z`h%Zj?-Ւ;vJ,)vE=Cb[ VXe֬YbĮMpD \t)xXYY?n.\(OMh5Bzv¾}PtiպhѢr>zHW>Mji&|駠4Ztd~gJVC @`ee]_| gnt(ٳSN/ӧ#Cm-[6iC9K-CCm{5:':՞Gl cup9tpĞ 0A z E"_B#?h 4Hn~{t]|Yi.L2]:Xs״ĉz,z?~<^9i1 wۆ%``ee윩-=h rqmJ}4OVj-Pܹmti˖-¢6F¥aMr}Mԉ IsW4HÖ͚5Ô)StӣDt)E?6i++ HhMZ4OB&鶼!CU% ɵi2,gΜh߾޶pwC~mK/sl2[Õ59W85hGtЯozE- П-o@K%9SW^޽+ h6K GkU6ĥ!ATo[JY$jW:%$$E< *8ȺL}\ +zɓ'tvs⢺suuFAŮ]6ĥ98eO6Z;-6++m[ ڝ;w9m:aҐ_qEuL2)A6wC\TN'!ПYV dzT^MCc?ݘ1cdr\iJyy+4Tnm}رwmbˢik\{&0aG#ƳtUvo !)825{aܸqUQeKucDf̘!{Kd=|vRiؓ,9,Z+ge6hmC )6T\*FM6st/oаzP E毐oG#ր⩨/}GteEI"c ڌܡ^RkpY,Mh~@bZ3%RRZ4Hu$ŬX2b)iSk.k׮Њ~GAx cG|s =+mfKACOK#FQ)AԻoРooo]lk+aС4tRǘ)iL___g̙z)4'8y 2/_NdG_#;:uF`RVJKhJgc '6vMNڔe,_?wVVZh aʕhѢKf"e͚th` )[~| +M[:*S uX{17Zo8w6VVn qf"ށttZoΝ}M[@ 8hu:Ϝk sk`eeM͛vΑ#FKgbѱՂocK7;X++kh޴kܣ54ZT֛Q(t8]K7;X++kht;h'vÁ&eAZ9ĚXYYFvE 5 PT1-xbMI_yOhZ,s1|3AmP++l4sag( 6|-6H8^)YY9^'ZcZ02<17s7\3 x䷒9ט 0&`sXY\<'`1 љC,_ݹL 0"TsYg}fl,-ZԬfZ9ĚXYYF.]4 YX?aNӵs5 05sL 0&$ɱ"4.㹛?617oeft\̪Ut/_N;++Gjm+`&Ç;ue4ivءw rNb]y㠨gen>ovkR(_<^|+·~ (ٳʥ}s< aH 6|-6H8^)YY9^s`6G5 bLl4]"44ԬgZ9ĚXYYF3&oooÁ&eAZ9ĚXYYFj.>'O GLôs5 &}]P!|Gjt+_ڵkxTcq[E3K+D9nWWWy#6H:[`ѽ{wWo $*A<;wnyҥKqygɣk*#3ߡxWH :BZ#Yp!*T_~...f/1ߺk VVlЮ cǎ-t'|TΦM"E`ȑʩ?NCS^aZKm۶ sAٲe uG Ni2IB+VVڲf4Zlub֭r]sʅL2IcCHatxɠop)).n^}M鞜QxLLTJ)*5GER-ZWNk毕rX@8aW^W^Cf"@k׮Ikw" @+HVb%@ߊS)E!%↫IV ѵr`ɓGnD夽̽)cO#xjudL 0$& p,L 0$& p,L 0$& p,L 0$& p,L 0$& p,L 0$`AvL 0&h:*5kbʕ1&c0ũf="1k`&&LL1& 2=S`&&@Y`L ++3eL 0&`bL 1& 2=S`&&@Y`L ++3eL 0&`bL 1& 2=S`&&@Y`L ++3eL 0&`bL 1& 2=S`&&@Y`L ++3eL 0&`bL 1& 2=S`&&@Y`L ++3eL 0&`bNjժԊL 0Tȗ/jԨ*XRV^e˖Cڮ% 0&0l0>|خK"x۷GgLzN\7&&%Je}]F{t_P!ʕ &LХ۲e )yUV4h( ;&'`qeS"ԨQ#ɗ<O>hE} Νoz|S9^5kqIҦMmbڵ ر#^~8pT$).R*~ 8q"(eB;&'`qeb 9FCnRqER܂ d|&$@ʮ`R)IRɞ=P '0A%'GI)7'''9z];wv"ӧ?+W;#(ƍ2ҥK%)lR\EU'k&@[J.ɥK^>>>2ixxTR˗׉Grd@AJM6wEÇ6mš5ktqEIʏz_ԛ"Ev1&@|UVgΜK@YfՕz5"IP =^O.]:!)RV+R;GCoߖvy%O<1?c4lPUVRE&M^8t^ح['0&,: HzRJWA= )+Cz#2e˖ ce"# K#EǛԮpRIi2D&7F,Yd 9oF'嚔܅ @$4qFKJZp4Y)2(ȸaڴi8qD 0`:9s΁Ȃ(]bK̃z.OOO4GEV4D3fD̙A Kq;#3u`^VrĨؿȤ\]]+3&@>֪ m@IDATU%2t0yVAAARa!Gu4wEJ*SL)(yҒP 96cCzIzl,wr3h8cL hǛ3f$=Ǵ5,ֳJ eM3gN܎ cGJ ;h:ٳ06e2L&蜕@D9i !Ri4GȞ`L YdÎ 0&&=p(`L h++ 4 0&&*a>` JE`L 0 J2&@#p`L aáL 0&4\&H+p(`L h++ 4 0&&*a>` JE`L 0 J2&@#p`L aáL 0&4\&H+p(`L hݜ|}!((Ȟ=;ҦMIF͛7^~-(q):\}ŧtSSy"## :YdQ;wsgCyvg\_<=J(./_\͛Ӆ M++RP{v9C ͈,"x!f/4JDTVD5ĽNq{5+y)[EIRΫH:0,E欹P÷իwww Џ'Obq c\Qo>*itqIF<3o799S︘ +iq+iޫI.Ih  8+7m-[PBb5H2TV=p)4 ]g$WZkoyaŨ׸ڴm񏝝m8|0Ϛgtu&S3f}R[ꠈHYmD5ѽ?J\NXlJYȖ-}bti#kh E|>A/°l?zn0 ŊskKCs'OAc3zxLS,.bhatEo4{%`3*::3gLKyMxf,08!OV?DӦٳz]v ?D9G9F/Y" 777sd2[ W,** ONOjۥR/["~Q v;Z>| 4Ί*6䌾9x5 .ILWV~4Պ_4sG}T ݺCQ(Q}/V3gh)Y^~zw d"yeǂ_Q&_0P=LrYtƠg:vi)'`-#g1r4މrAQIjq0ߤ.,"׆*;< Ċ f?&#ieuA<}=۔Ow f xn<{H$VlpV7|޷#Gz"VPTmOƱٗhz|gdJ*&KBP_K~=IR&ѓzۋʛ+ o"̐RȊOhXc2 R{!@ďT 4p/Zʆ-4^ p`~=')gxYR{JBߡ'틨HDDk!Gaڢ#%s9ZL-}69G.7W>,׶ hg0QZ2=Nl3 }'vYs?@:޺OkB&"sјLw[RkA?}eLAjYa M]@To,w2OZL)b%vѮI \xHX\7/L]9{.Z;eJ4#RD݇fm ۠?BD~T?)r߿.tulEC[0e<'aݣ{oK"6: -OBv={(ފ'wDsAk|l PR[2m3d<;U x\BW8#.B".( WL$&;*M*+ڎh߮ ֲTۥ(Z0;ύC(V }LuR +;r{|5^9Sj-628\ @>Sxvf,nOedxکpe\<3"^LX?7zw*&右s:tjo^nj9Gϑ?C'F]=qV lP*en˗O`B_9vVkL ^ώgP?[N Ư 3KWGq7,[cEź8$闯(~yApԈ+'1H9mq")."}t_9(,<\+9!S/cWX.d;VDa>őeNyL8v=hTK@رc(_()zJגCNN0:rI1>)ĭT&.靅ɛ_+ʆPާ}-fGXc[\9Cy PXSUEH1Ej5aР߾b!"1z{*&^$pK(b. "+@aA̘Eߟ^WDd4så1o왾5la4ٳ.YTij8`\Л)+<2ez䌝=V@ K J,  i]'n}0'_C~*;+"]X:[,r tnYN*/2_'8 . yk嘜7^5:P!t ȏ%+ t썵`.>+XZZsMBYm|Dk=T|BQ0jɺqZ\VL/U _. 2 ̢e2rΪxFyVţp|wUvR%ƴX{^5f'SZ~'S8GO=[!!B ;45c))%=R:9oK+vwe^3c2EܳV%ܳZ_VϘ|/;&&'UH{V<,}=+grd@$v ԨX 'X@0?#vsd =xULMQ~`@@=\9K?U"8k*yx^2,st3LD6W3TVlj8K)bd Xgc60?e60?c[́{V6jܳnqsgek-FEVk5[=s g[EʊU%D/MUKEj|M49 ͭcGE2#x-ˉCgDl(ah5ٳ#F t`c7\Z nؙqxO̗K%TVaI;6ܖ@"1ıdp˔ e|"Eƌ͗KIW灡6 ܅ ;o@Ug#Q;"6V)`gI4 )%w)߹/Phɔ$4& P`AK1<hE\}`'IeURM[}:#aŕ!(SL *P4n*;& 74`h4WC"{Dr5nJ,+vïÓ'F ,&2CڵH@Ê/9`r\Zh8Z@͚5$') ~ߎǘ)ڑ,M*+۲Uwʫa-]>>>))MB ]?M"##p6w8AUV4չ{\|޹нgoȞ'Pn]ר/Y>s;ˑVZN B,Yv\SЬ "gZksrC3gȚ5͕^ L. sʄ/쵚ƗOմ4kj8$ieEH`uI8 ^wPbTH -[6 4\K>\Q lXs "yeEۮ̘+2{ΈmcCP|t|gZjP-UXb8y D9{2n3?Ό1SB X29\t++jr ·q3.?ȩGշQv]{(m¹sƈYӱ3=x0?RNQXV58 eEAD*]0#غݽh]/KO׵1дi3zmEń¿Ne yۯRP ziA(;n<+*C Mb“zX[E*հr"lv7ʅUv7qS_?=h;"@l#'NCb/ jqF1w߅&3!A8&:P>jȧ}7o^훀M)+)r̉ Ǟ;}Ȕ>#kt}ܑV(ׯȦ9/Rza;E깰iӦť2J"8ep8z yhI{p/roL[Db鿴C5pI^OWGa3)j(B$o64 P<%طZ^؉I8SG6m1e *TH@ ؤRj/_>߫>x@aySGP7R0eH(aҫS߫M-ZׯcH.vn 'T́ fgڛ ODDܹ{ZX8z&gQxs)ߊ ېVC߉S•q$}YJR @${~0rH<·JZ9g`ee3f\9j( 0&++ e̙1uT9g 0&++ cVЦM 2D1%m 1&XYYLL81^AAAxaÆxapd,rرc1k,;wN/+W7o `L x`-/_qJY2&KچI>}7pΜ9(ի:?`L 0?&C7vMnݻ' ^`L-VVoYJÆ ^.]x`O/^ +|}}1a=z111r0mŴc̛7d 86PH7`L- xxxõk?Ly9oE_|,t.++`L 𜕥}ԩS[244[l gE=-F:ZaL0[NLݼy\c;U)),r`u 3&x*Rqiԯ_ha8 0&@geΝ;7֮]_2e bήk&`eeѣ_it.JDD>|k&`e˱pB Yc# 4 0M{Dc}Ȗ-4)I&Iuqq!tׯ1h&"cJGq>n|rMiH&}OH6"͑#|||Х:_3&I9d?~{{3!sVgD9E"] >RB<HwӼ M72Ae_E"d^ S++VB1/!PN Ԯ][.nVk& 258bRx5ǟW@YIJ[e]Y5e.h޼nmՆK-`eeVڷo/-a‡-ΌYN4Ɗ-?Oq8`ܙr%᜘p=Rk3דz7¶]ojevCq۬0i0ngL RK0NzRTK{9=VIũ6(,aJ*ǍL 0UOHj)3~{aBС73&RMUr fǰQzZjX`s}]8~eb+&RIU**/^#7˻/|:~ K}eT/qț/P:H`L XYHOpW2@bZKvjP0tQc&u)!ለRn"MqSl^(֭28` <{Wh(bEL ]1wb fP:(#zuKaPJʃGob_0g{}b me&۷,EP~UkF(R,'>fa̶c[ľ-i26, 0#=+歫оk9HqS6{7vk2\i2fJf"S ƼYú==z!rڲ4,Og뉝0㇭_?_.?]b# MkP|ΝL!e0&XY/\,^ mȪPK8g!~j:UA)^ }ݚr &CؼT~U  !M^8pxݙR.bLJe{Id:EE!5PhXm=IK_A<; }uĖB1:XB-E5})-ׇV9uTZe36D̛ןDqH5[Fe8(ߦ n}%OyUcFtg=[|MkrGwibF@sXyh7 ش( Gj%FrBe_hSp/IGFR=:GNw\a T&Z< }6-v{ݸx-?X|@@  V%T F'n߇`LdbAXYc3mA@tڢxkFǎ8lC/! %1[槽9ٷDQbNx: e5QМZV9DT:<_?VYg0j YU|lB4<+u6vSJJ]ŢŁ0b-^?ʝ_a;oTbhK JMMb9^r!/6/Hټሴ364|\GI,r߭L[~?*[Z,V|r =ĩ]en-g><C>bN3DX^5}2e6 ,EMt  B؅2eS캩QoP *~KcX>@;N#Qh;d KЉK_P*4""n>jն6}͜+ݽDnO/ hF4B7|2a57㏝ciwHiD>ӱiہt%ZVtշ:u zkx[zzjӾ=׵מb"oP2I4  `"/VPN놠"Z8{/A^lBe!Z1iV'W\v8%X~$v 91.5\9S{9M<~tTgǶS4V?,Uf/|<^)VUpBTLCjN*UE:$n@'[]6S]p}xz6O^+[>+PD%r\rrj5lڃh-ܝ}.^ߕMPnyyM+-`x1rX5jԠq>K.;X.qK-TX)<g=%ӕ.ުwԈMv-Ow("Z{"gvenr"8AO= Y$/'ݳ]iCٲԦCZj/?{U'~B8x̛IzwQQm'Zeoݕ颣S!1NVj<_~9^$nU͟ ;T5;HE}oC-[qV3+3;iivɏN=Gۃ/BqjVwկ:.awᬅhCr <{B)b17O:aTT(jZ>JSpJz&ڲ56n!uc[ȡ]zRV.eOfi~ ?HaMQѢ4 8|׿7Ms51=z/|R)sq[Cax cdžN9Si=@zQPY\NSErÿy#Qpp4 MK8oH;v,͜9ې}@Jʪ^:5iԁ3[E`g;w.-\0=Lә+ǜOX aMCTFF5KRHHfKh  +gPadɒgdԩ3&B'.E@   `:Ă"X'   2@%   @A,@ P (u+|   `+T  `:Ă"X'   2@%   @A,@ P (u+|   `+T  `:Ă"X'   2@%   YFDEEQtt4ݸqbbbXbC+Wl&/c;LsVa2 |ΝIǏw]e/N?ޜDL@BbFccci(28iSuNŋo̙4jTԡCGʝ;C @4$fVSܕnڰ'ڵ=HveMn+!Cʈ.;tiVn **%%fBt}ŘO!'够… sS]ԪU+ A<@戺>,99|uJH8GH2ZfE?WƍF@4!cS'SF%tGr3 uSv}D 8Nb83w,Z4jC;YM牑,X׮_hx&@4}v=F{ihսL҈W^GiA KbQ$$$Pxb1VNΗȬ4sMIYm߼y{[84$=luZl`@bkarT\ ,pyM<җUKwdn|Mլُ~{y/l^NOrݘ%ȑ#Ըqz~9AJz?w$ >N9iƍ왛!J*ۼ[fr.XϞO ֡vrb$Jk^FbA& 焂X4qb7jɋVms.[ ̼Ȟ{u4VoM3]㐐`ڱf`@; `Jzϗ_+&:txBtޅݤw;CZ{zbk-sO$v(]իEh+Ƭn[(!.вKSr֭3wtP7ƏJ*J6(u]{xZ9B^ XiPUs;#O5 r$%1vBx]sR-OG -p[} ٱ"" \9Irnqw)vn  Vn5|Ç$&rM6@c Buǖm --+`!**#ܱee,e. -Q + вR:ĀOcVRMy5EaUzaD\-N>:@@ChYi@@C-+ *Uvm%3m= O RhYi`1 XJ5%,T.\ !@+{(H: @~+YDJͿhY"x V-LlAXYcWlRR]1O NNNG3hHb!Lz y|pXjIEAMb= Tu.LoZO䥱/^ҥC ZXi@Bb݋X<FQ*F{Mo'ԩSPPta @ @4~Ν>زݹs{oHu(3gɍs VߟfΜC …M} jzԮ]'9rt_-ZJ/_޽KE` … 8< ./[nQiذa2?&O"'?lٲ% 4HfϞmv]w7w5i҄.^(yr秡CʖբE/-.mRJ(@0feYd -]݄1TRNSL}]Nw|XV^Mjs#n}"x/u tmڴѹ4$&ޖ]}ܢ+͛W8 CL֭Şu } V:9ռEnF6mDuԑ\<  V:ձQƩ[zܲ=zZ_V/,3fPDD7xC16@  V:TƩ,!`W?\vnٲRݮhۗ'dOWtYʅAGb.\{Ylq*۵kG:tcD Vۥ l-/-e--eFsWRRʕ+冘< =9CIc!CXK8lXe\eœR neǜiwYPP\[(Fʕk9VA"??X8pkhJnٲ\(wժUtm{Nv?X IDATZl:w,y (m @4n좮~+,nyἱѣG JDIooe>"S 6[V_YÂ[NzV Qƍu @gf_צEw.w;v,(P4.]~'q``C[(n@vzÆ B>b猬pUTI:X)+!@ ]o4+E؛Zzy$w-:va޿_zy3Zz_뗽 y)1 .@aHcl X9K55O_R j܅ēi=-(QB|wĞ8=OVk7lݺ5m޼Ykrx}Hna)c{G=e\ *+&_"`Pss(|tYe5-+/iv0Ux(^Vc≦Tpaxnm?}r>hv6egJGѢEEY4i"p=}2F<^$E/kN@7-'.]*|R.;얟w?CvM8|g"Ӄf8nb{#<Ç'׺"9e;t%q `.bмQܶmArE<}s֬YaĮp `.b';B$uRsft]r  `.bųywsW8rygo[WAExʕ+[+UX+5ODU+;{]̞zP#Xv =֛:{W_+5ϑZ:|83:]WW1 jt+FP'l>ζd>QO(wE8۾3~,PB#Xy†Sq6gw&WWPF ] \+8?c\PB0] \8۾?O%E^__#@@./^z۹A͛7)--ͩEu௞;b@ҲR+ %J(A[TvZ|S9:^WW1 jtiYa_:H:W8?c<4o<{nZbEF,[xU~0] pPgWwsMq iڴiɧLB7n8.݀e^eW1kצ[ned4a< E-+CUGzat}"uH.$Ez:[:ǂ%>o`w6g۷dq݃J "Xᗫ I|h]_=wĀE߿V~=*PS9:^WW1 jt+ލƍje.];u F@R ={VL^}ܹsTjU2u௞;b@"Vק'NkxIdd$ըQé 2^W;X#X5nܘΟ?o69Z!%S82^W;X#XI˖-IСU\]q@]Ċ ծ];9nk^uVY&y3[t+իٛ ڂw|LL Իwo3Q?3wC@75mڔ*WLW&!!,YB >#ߥ@ *VW_3gФIOLL3fP۶mnݺחU t+={6.]Ze븸8ׯۗڷo_G ft+kՍ;hɓ'믿I;5AleEKʓ'tڵ4k,j޼EWyӻ 6n^gkFA@ v{݂t9&,?#:u5j$')RbZwȫRܹN>MZ;u2j ʥF{ffΜO`!JE6m]vɗ;kŞs>>>*{X{5%M599QҲxc%qzO }Ϋҕ+WThQj֬9}u _@ @f?A @ Z1(@&U& ʠbdXeA @ Z1(@&U& ʠbdXeA @ Z1(@&U& @\jA wG̑XukAC`ƌ) J9)%Q0'cV@p  `@+V   `Nbeg  $2`H   V { hello; t; f; n; i; pi; a } array -> { a1; a2; a3; a4 } edge [arrowhead=none] hello -> world t -> true f -> false n -> null i -> i1 pi -> pi1 a -> array } }opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/architecture.dot0000664000175000017500000000162015110656146023471 0ustar memedigraph { compound=true fontname="Inconsolata, Consolas" fontsize=10 margin="0,0" ranksep=0.2 nodesep=0.5 penwidth=0.5 colorscheme=spectral7 node [shape=box, fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, style=filled, fillcolor=white] edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] subgraph cluster1 { margin="10,10" labeljust="left" label = "SAX" style=filled fillcolor=6 Reader -> Writer [style=invis] } subgraph cluster2 { margin="10,10" labeljust="left" label = "DOM" style=filled fillcolor=7 Value Document } Handler [label="<>\nHandler"] { edge [arrowtail=onormal, dir=back] Value -> Document Handler -> Document Handler -> Writer } { edge [arrowhead=vee, style=dashed, constraint=false] Reader -> Handler [label="calls"] Value -> Handler [label="calls"] Document -> Reader [label="uses"] } }opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/insituparsing.png0000664000175000017500000011064115110656146023710 0ustar memePNG  IHDRqɉsRGB@IDATxTU_w)钒R$DZi%SDB)CJYb;.3;wfyٹq<HHHHH yJ$@$@$@$@"    (-bE     E>$@$@$@$@ P(Z‹$@$@$@$@!h۶ &ʕ+KVW7m$SLEIܸq k? 6lPcĈ!e˖͛K4i&a1'|"={tYϋ$@$@$@$ %Kȯ*%k׮Iv$~[oYeW8q޽{%eʔJ>}T,X ]ޤ"/^$H 5 GPD%KJ$IT˙3g͛7eرJ@B8ޏ?(NRnݺr=z`k&zF%Jȿ+},[L~gɜ9[BٟٳK M6<|P1cl޼Yߗ3ʊ+ߤk׮r97o̙S{=DPC?#/^K6l(M6UymݺUyGk׮-Ǐ>}$@$@$@$8}tem6y&M(A.ʕ+Upɕ/vҨQ#)\ ,Dسgd̘1dGtUұcGyԨQ";w(!wIꫯXbJ$B2NZo߾]t"AAA+V,֭9rjժ0s ۩S'ɐ!BleҲeKYx >bxHHH@B@E Y~\tIyu4iR%M' ( GQ ޼Zjɐ!CT1 "ٳg+a 41cJz+6lƸI7ԩSʕ+J;c8qb]EteG7xCy%nQ'NO?d7N;r< ?5Q1hnY.A@BRHi=x 4 BpK1 %umn2eRrk-ZHpp6,Y2)&󩏍 LxL (=:G su2;ݱώã ]ոW:uM&*THӿL*龭ta1BŇВc QV?x;oܸ!(fSGy**oܸQF$@$@$@D Jb|T0&}Kモx)11-U`"qZn-:tUV B,"}bx9M<?Vc{_~JaK͚5 -񉘼ѭN9U /ܑB ={Vrm5:prΒ%0&޽;hIԡqFlϞ=-[6U&l֬رHT!    G1b,_T"q1ЖHD8}_s oqD;ym䎁r=d [ڐXQ$ J0[J F[fOѓ'OԲFӯ#,x9`iyplb#ieLz<{ʦdH$ F<[(=éP/}lx3aax1{ƞy;Rķ'1I4;f˗5h,6Vpז1R@>\Ʒt+k0az,]i؁]_eҘ&0Lٴi!^&G۷O 6ZO6Y0ҎHe*nz&͹lٲElck^6"wY3m {ms3fLcg,.+wq&'Ģ6f`^j8ԎA6#G#CD9pa$dWDҥKMYL4I`믢 7Ѷ\]vKpe/NgfaCc}Y|aFeoںws^F?XOeڵ6eƌVǰ󘑸U$эښ6mjGeɒ%RZ5&a"c*U 3"c͍w,퀈/KL/QbG,,ۊz ,|!ܺu4jHz-[I~S|G5;wG()SFB QK2oB&*ߣ {wlٲjMM, B}]2|[3mVҰaCU5FEG|)ҥK/dlʬʕKn+`2'69 rjh{o+qc >16l RL)zq! ~쵵ĉg>m]hZ* h|2{u^+>;uꤞc[yόg>p3d˖-[&g6^bb=%I&j{_[x|+O×xѢEm 'IkԩSұcGi׮!~ak͍MtK1B7*;wڌfo ~)̖ծ][g٥KCk1"=>VF {Ad1̠/W#hx>ӟ 2h˹،  7VZ^Ext!&mD& *# #>:u _A}n 'NlhϏ>H ib&e>i s1%ՖB1[;>G55,~2G`z=K=zP"<%Nݺuպ|xmW ~ 7n,:ƭ^$ŮeFEǁ7>76 У5(!ïo1xhaLY0,D^|I~Tt[nI%o޼JxJG!g ,WQԜ75M~0|y0 NO/ ;*×x|ٽJ!)S.I[ts+WN BǏ $D|իWOU +'m Kۊ}öm۪/r9s/fPoe G3 Pc422&GC=# =9s)a„,^भy)h h^Y{õpRtgm‚c#SkQ76,\ezviު:j_"sHh鹱VJXΞ=k)zYEyovgouE.:&BXL'EMZYYF(勜sg@z^I{=-4QJY /0,YT~T܌HgQ JCEyP-W!g x\2jxBPf Y+w6jtK𹭉WpW;p$kjq&h_X@CO|T!+,`5 DC7+_+jFrF8˄8#/[ih_;d+;cf#1xkƳ7=<&Pkd{Ol!`{*F a24g'ygߑM{>uKC>Iҟ-KiZoG\#fHˑ+A#L)L '] @}Ji$@Gka ŋ= ֈ5)W3zav$ˠ$@^@C,bV/ϽAX  1^ , x EkHHHW4 A$@$@$@GBڄ%"    @B P6aHHHH+P(zE3$@$@$@$}(MX"    ^ , x EkHHHf`!H.7o4/iҤҨQ#)V隻{I0l,7l NN:9ƪU(Q"{4iġtH7УR xYСC;vl:wTZU}" B/:vU5عsdϞprߗ~IJ_CJHH H${(}Xb 7hР|5{l=z̟?_BCC#֭[yx.spO<_i!Nd{@[VT=pӧ2I&I%E AM9r G$(}Xd (W<|$֭['yHܹ#ח\rIԩ} \t+'KLSfM~)-qIӦM孷ޒiJ$Id Çlٲ2˟?^9rԨQCuN_`)3g*~7O^zgϞHP(W{6$@. 1ׇƍ?Vc! a aÆɍ72c ٿ&&L>@ƌ#ӧOW /_,x/% Bʕ+rQե!Cyս Ζ.]D'<#FGrN8QuݻwW]HCV,XP֯_/s %QV? ǸCOVW;wn%|wء_; PeuHO`رRH b/\f͔P%^xr1SA7o.]t4iH̘1ef5!?P?~, !u&6mE]tiiӦJÐ?! J!!lNt Ht7 ' ElW֊H !䍭[JU~+VYJ2eXbEȽp¦s?t/c2Lʔ)7Tųg* nl6l(6Fw%-N8J03gN\dW^5{DH̙$@EBѿړ!p3t14H0#̙3|r)V t_c,ŋe޽ֺ[h\pABY1bX1+yr5GsΪ[b_5zFWn&ۥK5c !@?mɚ x,`}?YK1'$»q7m4wuخ];&Uw߾}S 'ԩCܸq1aͯ!!Zu+U?~I#&HveHl1LVajus~3lLL'lٲ[o߾RregϞj1fc3f?ż3f̨1ky޼y=j׮At;cV5W0+f=LfCD7{3y /Y~]uY! xIdɒϛ-қ7oL0 /D(%o &` ҁ(47t!- **U*&`浥nMܛq.;Ru֓b@6;+M$qI0-*A$@NY? ڶm+c[ӇX̚F$@L@n}֝ ̑#E _0!F$@J@my֛H@gϮ1<# OcMCD>$$@NB1П֟Wrd [|$@$ppM X`|IYO~k$@$hQ g}I"$XT0XV-xB$(Yo w}tQZje: 2v=r$@@hhwŏ_]„ IH= @dɤv DF(H X^ixHJҡCT[3ff&&°q=Ly^ g̙V׷toi "7oޔ7|S:w "@(.\P]Ol Lɓ%$$BѳIDBф$@LKOj d; 8FQ'w    (# NBQ'w    (# NYt|'"pI>}Ę'ORdʔt_N>PϢ]9sH iӦNKO`ҤIR\9)U~ɡ5k͛Mq1_իKܸqMםq˙3g]vHi z!Y"p%o$<<\ >|S2elRP!U~,X={/^:7*]v?6lϟ_?~l3=]&N' Ò zYE%bG)R*- *SNm@de%QD#F Ν;4iRaTkzZXlݻwUݰ3 u'ċ/dbԩSMq7:jYd#F_|Y-*+Wf͚SLiglSY߸qCҥKg ֢E +EѣG9I=Ӗ {'ݓK*/^Tݔ  sΩ{b}3*]ʕ^C03Q?r6m$3gVDNuZZٳK ?7b:PhC`۷5kʢEd2c ~hė{ǎսYf)ԡCش%K*ማaaaKjz:@[~ԫWO0VlٲJB(]rE &nB}6۷oWyÐ&bG"]+V$ID᥌l6x1Uq7ސ˗,[Ldx?2 B`]zUq #^:܇@ ]ƨ>M>}ziXT.( ^>CrniҤ4#2W7tsQzPCe4b?ď\}1ί{JtUTIIE=f)mwC0SpM7oVy ~T 8Pׯ1[/"<iHУ]Ґ@l۶MuAyXDÇ`Cwn$!Mpb~1&=zTu?ǛMinax0<[{7Zplf;ܺuQ9[ٌ*{bƘO'&toGսt ؼyꆆGFlnϑu > EoC @xbս G $5b1’(\æ B]nD{QËI(Q'>e{Y[bK] ltk놮UV5O֑w]_D6x^1CBb&肧 ' ElW*@`j޼yՌW̮ݻ؀Ã.ItMbL^Wݣ%I,H%2bf*cIt Ca,.Pnݺو)j&6!vue O%5?۴ic nLlet&*0Ǭ֭[ Ƃ 6M&ݠ~bl۶;?Ab)%kСv-q1]jUE. I|@ u*|,% %tc*ƽ9-Doĉ8ܥū!*1]F{c+ 1LIkA:=,&ءCSQ/LI6mWP[kU)(ƛbDxa}1Y(4(?xR_jpR95,Ӄg81$CsTHDNP\KxH OG̖`l芇z~WbSS/|m~-:67xQ!8Gx/&ffH#?צ <n+Ojq"o̙jM %# P(z= `L$^4@W3^4 ',Ƭ! 8DB!lD$@$@$@OBۘ5$   P(:HHHH P($@$@$@$ E1 ? Eoc֐HHH"@6F"   '@m C(H$@$@$@$(YC   pCHHH1kH$@$@$@Pt# P6f IHHH!ac$   Ƭ! 8D CHLٳgN9|`\zY%%!!)8*I""xx.1c\V;h^d#H(zIC$@IteRPFn颙eʒ%>#9vX`<'  @B  ~G)MjO`lQt;|ٳg~$@$5()X ;v>CJiH{Vr逨3+I$;(}XRkG%s'ʸ~$(]`ك(޽|5n8q¯ۙ#-^,- %{wSH6$KN_U,{3B={n.$#j>|l DIB1Jߜ$;~o?ft ^VL|"G3 8Sq21 {`Q3'HYf1{}Klrg;{y?&v*ڽ/>uCG/+{U8x,O!y`\K//Rla9C =J&%dŌLy:zPLv9vdܽ{$HBB) #+ p7qarھ1Uz=UPnY"?~-\yɪMY^uKԾ9UDҲA1%pO{mR)(JgG5G#xI&X< !qP C$@N"p^Y<%+l*Y޸^* ln螾e)':?{_y!$[5*!NJL-;+:+IC$@vP# 8$\\ҨsYg&.^ U$UO{T%er~ǧa߼/+Ԛ0bh 'H&=sbGa*Ě $ El6@4Pkq]IBX ($@#9[^9s!y QJg/HlF /PVcIO.RRPԜ;H%,5 p EfF$@ +VL9Pn s6Wߕ^H/HNBș! N N8z庲zI56UߔXb xG3s u!r3^E< W4oοHZup P(z XlI&7 lZmߔI%QDσH_'VH P(o۲f$ʕ+'%K7ʄy%^d%8IXō-qԖyXjd`8 sc܏|3f epq Z> {&Woܓ˞o˳ͤM*ZG1 x EkH@ܸqvuY9|8qRnݼ*'Nӫ!!qW$L@o󱎸[D/߽w_֮CjT-5L r ə#$ON2g(W+$3gVH|/H$d͚UlO>ʕ+Kd<ʰW+eݺu)Cd.<' dd| 1b:uJ[ 4t#Ϙ1C8 Ǐ8HAB ܹS&N(#G9rx8_|2d5j- 3' 'PtD&A$ߗN:Ijդ]v)D\v*tQ?~.OIHP(V{$@f> S]!& 2tP @4 P(F xڵke٪9]t)\3e$ƍZlb%/ P6b I"u|ҢE iذaqoKsb)HH>bh / н{wy}WesdHH* EhxHS &Xj*6mu2KAZTdʔ)h"Y|r;2zhxHsܠAnݺ):fywE1 x3_ W`BA8*V({O?V0Ç]vI2e0rB, x EhH>}Z@7,f /X@/_f<ܹs˚5kd̙:ujaDxd?՛u!=f,1 % o"DbϞ=U7szz4i4fH?yDΜ9; x@A2/ @$*orNv$H[nJp4*Ktk"/ 1cpM?G)&fչy<9b"><|, frçgd.r-I,ܿ&AhZmYɘ1ϼ߬rwE@IDAT) =뭨?O}AaAIP(zY8$l Ӳyr)[)Xy?e,Mˉu,7-F{\"1_.KH-29_oFJ$`QR G>F $$Df͙.WF- HMD|*^ȱ$K4Uq19|?gdX\Z^:&*5 D$Qtk ][ "12kto+M-).aرcy= 8a2)M#F, @ASͣ)Lf%rO'3O WPtYK c|to|B}ə;J,KJZ5M븿/s$y?%p> E3e$Xo2pD-I!GL!Wo'_N('NH1߳=̔b42ݻ'_;Nw-!3H^9㑼i™n"2u8y[Aff$`(Y]$0o,)Y.ʛe5d j'_w EȢݑ)SM  Pt:R&H%<ءM(洌,wCEHy*)Ο$HW^/^˒;׀zҦcEi֪bԙlLٳ@7srX)Q&|=~wIa3dȨ Ұi)YvXrnęЎd\Z>%m!5qdN?5ȃ{e邝ry)yi` >etEoN$@&ܾO׆@95K.|1{-,s~*i'sjUW 91Lsw/7 2]pںhu\it3k;Mz yHY\)R2 -Ac1p]Wqud|XvR,' X %4-tU;$@"@,LHHH(Y:Ekj]Ηcz[yHY(E鐀d̘Nq|kmY>׺廷[:^w; 8E Peu.V-qAro9g^%p& EgdZ$@$@$@$G(1Y b{R-ooPvb)IHHH(ݎ wMqeyu6CDBїZe%HB܏t: r7Ե|_;+ 82=p#iRJX37;Y=z&һo;[ϝwHE n;$!H&\t[dK1Yi;RQqڻďew$Mtn9'[ӧeΌeߒ4YBi٦RP_sW "aYgxD(Xf}E٭']#{wE+{xыnI6+͓# nnߘbȲŻe´r]^ f@$ zC@>LHrn+Hl(lۺq屔ၽץHn9횧[ƵGdcJ$*ؠKFw;N̏b :7+&ߓ[374f>~$ׯՋaR`ȷz)* 'NWnݿX Ox / IOB 08qH dӺcnŝ()?suyZ:߰TRObzU8Y 5OV֌Sɳg/^+VL 뮼.&xIBO 8uέWMz>x"Ʈ evI%5<[Na)Q:=pn{H͚uܒ۪ &eΚJby͞+ԩ4Zk]6wwu}> : @X'4iRiܠnKw~VpLrt zf{;)S*6 : FGsw[%QDFE+-fWlRBRJ2hDcٴ{R~q/sFU#=ޟ%m˰1MZ$.C|&:vvMHMC$8SJiZgtx[ $gi X&@h @_ɪ%gdͪ˺wDbqeݶJpƔHS\/gO?m]Α ty\dO]eb2moDǗ`4I(! "/^<;f@~ZW[SYSj)_d|7(Rp%7˪$Yd-=Q*5 hҹG5=t,?R/ 4iJwVQטwI%,cxpNfV$LY ɕ?TKHk E_*K: q$(8龷l픜lQʔ)EUBXW["ϴ\|K*J1]͟Y^A $QI%W E&7n VJx`Tr-y'NlmYJS`=}DpM?7?Fϱ"ta1 DK37"=, u_vW._ն+DWm UTI̘1ƟB-I#!@=m0W Es0h~L ׍U#0#0gM$ Yn-jYp:$sg. (9s;~ DȚ5 s'Oѣeĉrm2e+4I'xqh$ !!!ҪU+ڵo^sHtF9,!CȖ-[R\9%?w|)%KD$ Ũ 8͛7+={dժUkxX|w@3g;Cӧ4nX\ufH P(r~G`ڵRB?w:B'e,ׯӧOk&˖-|X Pt:R&H'%fF%͚5:u(g-G kN5mJ$4440$(yY@ ٨7NƏ/ӦM ^cg=K QDj&ҥKՄ_]0yF$(Y%pe TL0Xr( VjԨ&M]fM%馑 6 En?> 9rDmcX x@4iXE_>|4iDnԶ] , Em:< k#G$K,u"؂gϞjW24 $@R0t17mT ˗KJ*&`F4τ;xkk\$`u6C^G`޼yҺuk:q2@$H,Zs„ W_IÆ ֭[m  Eh$@`Μ9ҥK裏ftHkN6l gΜQci fI4| H@$vU'C $@"Ed֭]0+z֬YH+ P(zeP$%KD⧟~ V?Cw﮶|䉏Ղ%"@XuIΝ՗*E5k@̘1eРApBYb` .X ˋ$@'@6` H"ݻwI+-Z?b^$_%Pn]Ǐ۷oժ$(yY9_%p9@TL+V,R!%!uf]V^xeHP(Z~OcCBBX'NeIK= :T[At߾}C$(X L8Q֬Y#gϖ3NAe˖Ҿ}{>ػ\YI/'@ ӧO+ʰaԘ9kJ3gTa]vwG$@n'#\3 I" _hQ{:9{޳S)׮]bP ΔFe}^aBh_p2#yspH 'EY4i2Vb5dRCr)4CP \=pB])ٲԩ.1 wI?E䒫\ YhΞi|9$88pUT,׮]ڵ}Wt*# bGE!۴i ;vLFXw+$ucGS^pٳgO 9~? HEE%_2\QjX6jҥjKӦM;H\OB X$ЫW/r̟?ijرC>[>P_rNo1_'t^Rjժ)9I>zI%}t,ʙAJ]\jFdj75kJhhɌH P P(j˳%rJ6mZ`8sN)ѣGe2pD-D}$[iĉ)5OYCu5{>2p̧.珉_:uJV*nP-  hgҋ\zU:t m۶U[9hݓ'S9#Ifzٿ@LZ7t:m<|YJM+%[TAΝnV]_gs/gʛQUHMv= ֭[H\05 N$&$MT lYR6 og%J:,}r]pU m  M ׋YsgJI%C?i] g\ͳzٳg?C¤bŊrEwU@@P fe=M`ԩoɼy󜶏3g؉ĎbNރnڃO"\Ok7ZBܿ܏>,.nK.E7)CǑRVQC#z1}4eCDBoh}(/^tK]{t/`5vDbiF6[lq ==F5*.GJ(j˱>Gjyk/yk) hy(:>*ov]fOߪ|=R&ߧ2wd2ո5Ҫ]iҲԩ8Zj),K nI$etٜ*u͗;,U6dʒJMWhs/jSXul%𧅒ZN^m-³jW#[ҵ:Ԑ7Փ+gMFɤ_HqTS>F'o,Z%s7$[R]57l/献$e慛2Hz暚ݤwCnܼxKbǍ-Uޮʀ_(oҳeY9,izX6g{K5RJJ8H#pzÑ@JتG֞XCJlZ3 ʁScŋpY2POt(ZY?vQB얲p6:%aaTϞ=ڽ?J2սK+!CF%G/OMK;,z+rGwݻwm}?t(f0yٿ\P4HVN)=ϵ/_J/kh"@Eo#[^5!ZS- yo#Y2'x9iRF1m ^e6lTjQAj`JAR9d].篗%UTqFI.`ElH#p Epd*$%QF-Νy߻w.)DT fw)ٴtMYi^v}~|KKo$k4/-¹3N O/GW4+Aw #]lCYܒIf:EtYҚ%Dohnsٓpm ߄(uK'䲥HB6l oqkJlc_%RJ/[HȚJ)dM~, hF[e_'0c3{g}}g9y>90#Eϱ㚌.oߖapUF=z-n3g;UP~h?Mt稡K=a4#Iat<Ѯ aN9k:Ny,pLw^31h~/G9>] f˙LLCyOw&ة[UKg=sqg=>.y\_˔%wͼPti 1AM=yBߡ͇(Hzmt%*Uxq8,%Kws]:O}w.3'`Z\hwJqةoЈw%Q{B EC(Ī[_RxT/YTqn/aui4o*V(=XlՇh\o bnE/Skb3iBBU?)v^FnyX]I!ըQ5j$f cTH#7R)F}zҊyf`ٲe uU^}5E.VbIވT\3o.1Ƌ KE,iX <׸lSã{Dž4釹)@ךK5;>].{=X(`.ʘ9 )?E9\bJW6|N-D_^=z'ݪ0R+"b*UhѢE!CʅWqFF=&Lݻw?$zӺyP{X %rBFI nb^Jz~yw* K/  SھP! |9sոxb>U=*WXBfqy &`Ej#CP}R sh5ʗ?ߞOHswҴyRD+y}'(q L|<L .S޽- + :[zn0!]v Ҁ !UHU$IX< FR)f§nݺ4ydz嗥0S&FGE}#/u։i<-NO۰j=`c4>E*D@۶m wNŋ\PCPCܼyzI۷Zj -_X#H?ԉF]E n!UlӦ s?աdy YF<#FX0A~KO=5nܘ"#'!<D] +;|p8p *仔zz=:{6Z5+,Wps^C^>c1QtB/I/\)!n( in'ӧ)RĐ4}+\8?ykW~ FBjXsC۰*sLȴ̙SF|E/B&FH+)1#[ Xܹsiٲe1c0cqaF+> ͘14iB+Vnݺ}nhQbM~HkצrZ5cz3̒p5mwy֮]+sB_1""l#`Yآh١cad޲eR ʛ/7ݺP뷨`'_J famKvߜ( r(Qf~6'E9ܺyot38q"!wN L+!Fe5+Wݻw˅,("_:y9b(_^c8?_Ѽ-#W.E{V:k6Dim]7,jŋԩS}ĕ0DEkK@FIחz(lxɋCyZl;m yΞH%>FC%Rl/#tTla\ 7`Ia]mrJ1lF^JM?kժEݻwgΜe,+>9r֬Yc ?VoKLC]:H5+ [}COWzuEp{w][B%9koO^:IV˜~X-2 :TT|7J~9n>XQ4ߘD&FoE$ Kj<tt颱6Rk[;͘Udڮ=}+M.pY..DS[T|y|JL(K̲,jtMʘ9y v_+ݑ,Yȩu}&F `E1G7nܠ)Somxj8U3d@5~֮}t͔7ǟߖ|~^ԍF_}i[Dzw'=ڟ t@,4!,?ĭQ{ng/njC4|p50Gŀ `fΜ)3dl[UjF1:|8Z2X"-WSGF> ؔ';b_}WӾgPP!߿?!jdd?6#b0;x!l۶ZnmvQeڱM;'мhܹK3npZx%!F i߾5ܠ]Kw=wEGn((sBGSW|U9o޼4tJ:{CS'bhH_qdk/>s*p:v5][ Wݻ\n}.X V4Z,HLLիW޶/^:U\Y2~1ϴWC ; P =9y'۷J"^taԴAsۣF5jЪUޙ`E`YOغu+BcX2f(GN4x'#OO>0UQ+.a]x1uǎ%Ë`TVׯO J~AŠJYQ5̏0yf<0`E>8 ӥKXQt@wFXk׮-pmF X`E1XFso.CTܽ>3dѣ\>lx[#oXQ7ܞe8x +W!X,( )7RJѸqゴܭPFP}K(/_e>0;u֍NJ/_f@BŠN/8p+y1A믿Nӧ'q/k+8g](22E]#0j׮M<a Vj83B@sJ}Œ0@#СC:tر#{ %XQ *#TwF*Rr.0@ժU)S6BŠN8qTӥK+̇`BXg͚Eo pPehTNdg\pAE:;oMYd1,I1djboߞG˗/_|QbL+&-p[Q,[,]xu˖5E֔\ryɍ*׮]ҥK[ ٜ,XeGG g{o3I?֭[2!w^z'ȑ#TLJ\BFEC !|<י2hc C Ɵ3@"ЬY3x{Æ IY b 5wTDPFHBi…Ix*(ZuXn3=<2XQ4 bf=M4 ZGŠbh2XQt9.0uԡcǎٳgCXV-9l,h$̛ UF3f=4:̽ ZXQ ڡy˗)mڴ3gNOYp=FqfJUTaE1į`>+0"G9r4i/3cBVZ(֐eoYQ aNypoXp]F`FO"UG6 d 3?3f@AFXV:r,a\zgnf0@ (xk/YQ ֑~y@BB+F *ho[VM>dԮ];ڱck׮͛׽5i$m_N{Kho>0͒%K@FF o߾&`E=gZnM0AR(bL:;sgg׮];wnҥ uQ~+HlŊ-lݸqò:|`@rѣ2S/HE]&OL?#o^Zkܹr%ݲes7|#=@̐!uܙ}Q:tTHL2ԨQ#K,IO?#wآwP-P|ɤ-[^xrE 4 ,hѢ,cQƍy)ڵ~GR6fqVAI"e>y/BQ詧 Ѐȑ#ԫW/Y#nڴuF{ZlYz73fѣm۶ROSnݒnvF x(V L`GmԩT^=:~M>]ª:(}Pj ?,XL0xb~z9l@Pa/m-iFb#2gά /ϟ/ۆ51c͛6Kظ~bccG 8PD`…'}ThQ? zXonH"rD_a̙3S iӦ2 qެŊhX @j%7X-f6mZb44QZDLò޻wFA={S):$w!\'p}O+W/-,##xgh=\v (tL"؋תU+ie?HE 1!`Z/mL?{Jӷo_j֬\ =!EՇ~(CkԻwoN?Eǃ3ݻKK,dPo&B1\ w SXOy$l(#_#3B '?ܳ`C O~*,PRB%[ PԔ:,*sNi1_LSB ޺uJU`oiRa?ێ;vL' [=F:Ү͜hRc\| E 9)(zAL[iԔDJ"c5?c\\$mU6U"kJ*%W]6m|v n2E>s21VB-V S{쑫hr>}Z*=z/"bY-[H7MXb*&F 1|aq4իC`EbW_}%ԩ8#'V#31I]$2/2-X zh#˕X{Å6IX~M"d"9ĶE&FjEj#B"Ũ@ S E9Dq ,4^x#VSU -[$?ʯ;ݑUaQij<]S59RU?zTg_W;eQT;CIDATܩ_/|q7 {";׏巗ɝ~Xfa[mCaVrGEE9 vEPTE-IJ~a_CvܑO,Y֋׵ʿo,3zfܑU)ZdѓǾʩiׯlj18#>,]cXۛ6>ڻwPD5;P]mUR\_*׋WCYR[_1 >ooܸFy2՞Vogwo#QC3#0#fF`F@V#0#(([2cʶmۜv@zE &8-gG$bD;z]tB.\ph[nt9cعu# Yҗ$lBq\h/8z裏dw}W_(kg6,^H -a( db"]KODѭDȍE(Bx"Ʀ;~8q"_PYư8bEԸN@@dxꩧʁk֭/+$Hdʣo!.֛lj}/!Clꧻ͟?]Eb>A rB],p%aѣD"DŽ(.?L4s&RD"|%=o'F+](އ_RusPtuωGG ΧvvId!d}+.}6(tѣG+;gzgxVU{j׮M}qU9VB'̊͛E\\DDDX DPv:t ֬YC 6Lƣsє<ɣ9 aZ+>K6l cK,#Gx6$`Ye` ՓNX?W~ N:|߰Rm۶4`:qnH+>%K0 z!5ՉN$tVNdwـn] Rҫ[w'GO?_&rW~۳ ?#"j{ ڥY``Th͛'l:O6_c*M#̘1Cփi,<S&J' ˪m;m"͚5/%V!Fb8p@,YR>~i`8FMg7x&h:%K2 7RmZ'oRۂRBsNb2Fb| xB1ǃ@0(}:#\zPթC"Xͪ(Hʴ4^+ \4b~V5밸|5/jH(DbVΤ'*_fs^B 0eUx]]/xzKsW>~D?]-%fu]^xcLjR|W=uUɟQpipE%YubevLtIGyW OAB|_" #2Or{kUʻSV'%-( .0J_YU1hrm,%̵SI X p#=B~cLOL>ʖ-K#VF`AT57[ƞ,5 u9ۗKmm|Mm0-G:N̷ <G8|/𩄟*€D|=i-qg -fsI60*LeYŏiOXV!E̡ *(*{8E}H>PxUCa4\ E`,솗 \P\5`We=هϩ5ERעG\EWo$j !GXH?3&-Ԛzk,A O(EћAӕ)!g1'?|A #?Q)gB4ޙ*(RkPcG_zV@ZҬ0#9.gϱ皌#0#5iD@I0Wq_"&M"䋷u4h|_?>=H2ΈEgqS հwb?ʩdDAh 2mtғg/eoU#'*V*oT&V?yK^OUN=oߖj}ҶCק ~G 7.D gĊ3d8#0#0!(gF`F(:C3#0#w`F`g >0#0@#b_}F`F`!3d8#0#0!+!~pF`Fp]IENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/diagram/utilityclass.png0000664000175000017500000030323115110656147023542 0ustar memePNG  IHDRGjWsRGB@IDATxxT5^BA@.IPޥwH!;߼wMdMܻwNռ{fΙ2dxWlYa"    - Ky!    ݻ$    EXHHHHH~HHHHl'@K =H  N"vf|HHHܞEHHHl'@i;3>A$@$@$@nO"@$@$@$@    '@_     PDΌO t bH'/^?@48q<{,ZfHuRP!WD$@v#!dY ("mŢCqƲh" ȂK*iϟ?Zj z{{ }SLe D\xQڷo/֭X`U\x.ۦMə3`+W.hDB >&L(;wڵk ,q[l]K,}7?Xy},DΝ[/Yl\tI޽ۼys?qbH?TXG$@$VūITy#>jlϲsa+{TuĊK "GXwءnFeȂ T)W#ʼnGe|ƴ8BH~F4,8&O-$7oΘ&e<=?isy܇ `ZR /oq"?^,'Oe !-%k4}w}IH"F B"ւ c n,|a >&V0cNe0 )^[+$K(b"2ZZQI+߿ m ̗Sݴq7' Y" O%#)Ƌ.y 4;|]?yJp<}J<őĪHaNIKJ0*hF0~x)ǑGT$a;}vTA&X$ T80b-#b="l !p7־?ڀE,0OpCXc%ŋ=QdXE6N]PWNdͺ6<Ţ$@$^-n!Vl:)붝+.-ɶ6xq/=|x'^⋗z%K )S$|9SKh9-6 0 $S!d6%a=",X6 @ mҗφshm {F *2.6NIHK"Ҿ<]9\YSʨJ %dH&Ϲ<,7zFҥN/)'b1VXhL,L!_yaÆ٧BB$@$@v$@iGVՕdڢZukUA/6%Crv|J;@0x_ iJ*% N#*4):>55@Q[!  PD)J?S) VGI:EJʼnKYȅ+4RD-VHHHF sZo8!Ke CjfkȦ I+dVy$@$@$@$Ed 9$kz:mV$/iJ=tHHH PDF1l.{ҴN!@q=2źIHH\E $H2~WY%gN#\ą(p($@$@$@QA"2(ϟ_~7#Wz+gk4OJ ZkgxM$@$@$@ t{gC\v{޹|]z {5v|w1bǒvxMl찁m9a74jZ;f"   [DVr؇xڴi 1vZ0-)S3f(~W^],J,wX|λp$NX[,P<}TC}lٲIRd޽:e6mw5jԐw^Zl)*UL22SLyElVn]5jwÆ bI^xj  ߌ3+St˗7޵,Y7|#ŋW^Ifdĉ3믿;z@zٲer%- 7oP)ܹS[0'ϟ+WPBlYti֭ѣܺuK bڴiu̝;W]ɓ'ѣ W_mt:th K*6x!D?V{B[HHHB JECsΒ>}z={tI49X,Y"Uׯ_y扏_>X4i coӧKÆ M gכ"h\\99sΛ9snZPwxdȐ!ZB$BBHJJɓ(|/*ձhѢn:S? 8P Yf˗@RfM-^kh7*5)A$@$@$LDB (QB[8 B,Y A7nԩS` 7o^>ׯ__?h2g,o6݂4ibz1ݍ~>a!̓'> !)GF!C~{8`ó?^ -ߨ'*ηUX M=xB޽{g1Ky_FbyHHH"(u8B`M"0bHX_د_?m}´5奭ygQKE#[ L3H,B ,X)UV5+#R¢RzL0A*VaY3T_@ )Ԙ/n~"g2u'u F qkz7 ;i!D$@$@6mCY# F-~:1>xR 6nNX!V3gN9v옶6"y`nܱc@좬! <#,XFAHo^6~~o؛>pYH?ey,Y8Hn M RNY{u )7?./w?}}fJ2iRH2`~4IW$q;AD5uA˖-+ E*"{R2D4.q'!$/_>KYa2d+rɿxB8"o<X! m$Ke3U}=GNސsi끿l+VϥW܅+d7dTZrmv*:~, S;|H15kAXw飏>ҡeݓ"{Y+Qŏű 8">YtԄ.kr#[;|#H֌Y]1 ;3@42k!}ԫQ@*ɒxJl=ܗ?Q>#&mt˰okg1-g'/KdsJ|҃X2z:vCWX=cyptuoɖ>vGDIQ.>e}\d0ΨS)^è # G&4"%k1m&:36/ckt˲ueF_$'?)(cԶ8 cSE= 3uT#s$czcbCRLv޶To^2G7xHu) Xj :g\3PMS-RJ^iߤtCNً8ܸ+nLC' pG^_|^kh q…:$Do3` '<-8}I>j_+o ;lӰ(Aj'jmb eۥr-qU59CrT6H n[Y0-TcLH?Q։мpWR{% iΙ5SS)'mt?Kd$ aߑz] E$@$FD"Hwt(x8#OX/’&lp@pd$8:9<-Ť5$ٳ+d%kH C9"嫦}_+ʘU([ZW|zc)_ 8qԵO݇،/1~,'`gU>qB]q#㪺PGT8E=ke-˅{+zW=k~A"  +BDq! 1-"" ~?.z;Exfc=#{) VxZ#8F{1>HpLpEAՍ}qtsx#R^=]>k֬ WG1R|O#5\F~GKLb{cO[lۨ tm~RI?}~/,pF#:Qer]kϘ2 ЄPzM|'@g[%B! t-CȃylVȃ9ep}[E57 ָ6ִ>3#К-+y'C\oyN>2ǒ0A3 SHLQgQ&bY{nq ۴iCرC["alѢܹSǥ\lޅVHĆD!lɈ^zBb*7ҥK:9aOo}OTȾJe7 yA}S8z x'՚FkI:%"3AHA@A`B0qD(!$<2oE!C,E 3ZbjI!&U*MIźUKiL9Ai,ATHHH <BDb*Zj$I= !%X!8 SH]̙3u֦ cM>4`G?uŦз j`"%?;OzCD,ʉer⽘\`kAc7he#Ы6,nvΦvz* I[&TdVt5K5pD$@$@E #̖Up8-6)G% &uSu֕ŋkX6< @DIL_zUÞHnB *CMw3 -b( ;xrk b "n'4طVHgpװ<Ə_CHO#aGiʎx֓:Sڭgv|z8XK_]"?D;gX+<p5MXe' LQmi,<Xpa 9oNb8Td Hc<g7eR=1#良 w$G/NR9#   \^D;|FH>>TSeҜ=S V JH$ﭨH)z-s013c|A$@$@$@("iu`z^?z5 Hv:9F,Z ^bϞj:on<_ DeάeL)L] MsQ9Hrl>Q}>k|8! $ I`ZҨVaTO]_9SkX&JOJK#S՞)%P-8gɐ\oh<CJ0㊿S'F M$@$Ed *^ 3vI>%"}.k!S8Ϣ!浛UK%.s^o1]= +[Jɖ)zklUgkcd^  tw=*ɗNf.=(y%=Sd!a/jlayM<}Z饯ɴ]%  p,>7q^j S湕 aIJy{ZC9Xr}g  p6ƜpVOF9\N4IX"P TX#< 8(H}1-/"22a}[OLN?7< iJ%(aWxTwE> 8Hgz[.W̘.>>4[_yC%,ȱ3txGO^jgJx3 Dcϖ-ʜYSJlEa}yF!FKxqGx&  d?ˉ'">4;s|,7>MQLv]ӛ7r#s玬|J=;Sp$@$@nI B"-qNC3@+AJu $*ǝ8|RPuE!80~Ag   93Xܼ+IX|g*   pG9P KDp|P:.[ayrNu 2HW~ČSMi{飆䖓V7+"  g$;> D/IHHH) PD:kcwdʔ)ҴiS)S4iD֯_`ݻ=}˗b~? PD" K:udR`AܹčW4h #F~iQe„ 2v@Yϟ?~Z̯㇐g&X cv˵k.9yKNUVRT)-۵k')S4 իWB'X-Z$z'j0aB"!^zUI*xzz+wNT I<ĈÔga%E;L$@$@KH}wߵFa\lf7o>}O^:t &M3gg}f<˧HB K.5YXfNZJ.7|v Џ\rEϖ- 6/^ 6H3gh)H:tܻwO=*>|X ѿ019RHHE;IyF[,M[u5j԰8[jgᜃp҂ VZ,pY|\vMm&}ys̑r C6jHs2A67.]7x& p.['$)̙3͛GLKC 7!Yfieٲe4^H>,6OYd[nS] N0L޼yuh 8ӠXo,Y2]abɼЅ Kt8*LgÊrJI4$NX/jReƍzM"X+i aN1lJ(Yׯf͚UPw˖-ܹsIL9c $׮][Osg̘Qŋݻ^i?km4>' p^=w2UVHA4@"a3VZ5M QɨbոFJo^F*Z`o׀3ܱ@ƹ8de$@$@$@$("=s$@$@$@$`WvHHHH=PD{(IHHH("튓 {tQ ] PD'+#    @$   +NVF$@$@$@A"=3GI$@$@$@v%@iWHHH܃E{gHHHJ"Ү8Y Hx% ؕE]q2   p9J   +v "pe7n >\&Lhb;ѣo^ݻ'+V0@IѣfՓJ*N8gȐto߾W|}}tҒ8q@Q9#\!CG}$*T4ӧK8q$}Q=B.]ǏK%k֬?Yf#F ɒ%|gիW͛Me/֭++W64״D:ͫbGIHyܺuKƏy!"qŋ뀀}\~]!4Q/^:MU?{LV*6l0Åz-[J|}F9!?@:wD!ٲPT4Rm_lLk֬;*G!RfMeС+W.Ypٳgeʔ)ǒ hѢr]fϞ=x@_l=6HAK vH\@*U.]T~'I2iGװFMt߸xtQo.w5n3E6mSN2x`AZjٳO>Ćٵ:tNrΨ[ZXګMw'G;a:}xzzʯ~,7Ǐƻ˖-޽{yb( gHWx )1ciNLu_0hsݻeΜ9~~~FO޽K ֭?Dۭ Vy}iӤEZ@sΝt5-%:={vK.s"e^%B$@K`رJ%2 0]tҮ];&Cȑ#MTvLH:ZK{+WnΝǕP=2=-(F}))R$G#{AhLK(QBSDFyK$@.L%UTC7er[\iXb~M`erʕ 璲es)lGTɬuIyV@ŭqv&4D|W= `3nͷ9"ߣnݺIuhXbA 7 ,@M3D'r+%HWz 8فۿ 3la섩lD #%IbDHӦUs]"48$/>϶@|&|- =Ha OWNv96 &nuі+ D% `{=DL1cholaMW9sfp9~Z#yBs&ٲҥJ՜ՃLm۶Uha{ߥ%j/VL|! 2g%r#yF[iԨJX[i.J)o95EӼ*vHB4h@`I{sb;w4kL>GwٳS] eGZsB9cz)P Z!^JiӦr5iҤ^ωilš9r= @BºXɒ%zOJ=HǎKJʫWd߾SjW;=w:VlG"{5𸆇5]6BⰖ>2Ik%"YI$@NJbxs&lB^xqCM}D['\aX0)*aDlmJIHHH"_     PDڌ PD;@$@$@$@$`3H    H~HHHHl&@i32>@$@$@$@$8 n޼'7zzgbmNFp'{a. D67nƩ;j?ykZ琉=ߋ2mҪU o7PD$x& 7'p=YhFw]iX~|$ozKI=H\>~\~e|e`eyD:[cIHnz /CgH\eР}،Uռy5V 0U^l|[9aBOgwRsj9t|i}L$ ht1 @8ܹP,.GڵJNu#\Gm'7KzhvjyLFl17I PD:[cIH =zfY7/^ޏ.O$IJK@@fɒV w)`HH dd޼KdxJ!# sN7oܸV8_ɓw/U1paC$@$Xki:1vXd4e, L`oX9slYC㏕:^g% íP`5+֊DiEdfc$@$`={(Uƌ*I&#1`:'q&hӦCү_={:8RE؞twɑ 0lU矫'ʳ!C&yj0ApU0AEVaH <("Cϐ @زڮp`>} QL$`HxozԽoRHe,)yf6Lv%@iWHG1 o@IDAT'O^)^fҧwp%k'LRKǎu ;eʔG&L$`/"zHH c vdUA,ƒWtP8c1L;#EdԱfK$@$*}4i<{KcX ,0a)L˖0Aa2 PDZ›$@$4|RJr3fw-, Ba 3f!U+.pa"[PDBeIH xR][#7nܓ[c"$P@8v2`Tɚ5v)^<OD&|"҅^&B$|N$'.׻tVO|`@ʔI/UOʊN B X/H $!} D2wfkzzHlUUTb!}\v&P9˗tO9j h$iqHt^2ztgI3{۷ ֿH…gܾ}WrA$SILaN]+o߾Sf1a R[+EqI~5ǏH@səpd5as6#$@$~7?5tpyO3tc"'Gf#[ŋ#ϜN.ZU/҂I?W;TU0q}֭[ҿW5~Hz ̤I+#$m:T ɓIpƖ-eԨjd%2KؓqPNkuH|"b gkzѣg"OG#DKݺq9&h֬inJ Ɨ MSDFt6I$޽{'˖MI. HU+KpܿD}0AYS%Jf WyVAiHH ;Ȅ ˴C¨Q_J [#&D6*U{O *.I2LCt"2( X"pEeRZ iР-2 +/_Pׯߓ i <tQ DL_/]Cݽ{֋w&@ )]OUjc1;^蘓\2Hx @4 K;h~lAxxyX`qA6mAZAznDEdDY P.\!HѤIN_[AΝIi$JQ87--g#@lo%p(7EIux7v@[S8PD! ??rm*U2B! "ӭF ܺ@"1cP  #@K3H  wJLY#͚UUƞi ^˟ 8 oʼy[t0K֬i& DZI\H<==dȎ C!  7>E$&83jS76I$@  +=)S>ҥ+H,7Iܙ/?}B j)2vg; X$@i o +_W2ny&@o_&ruꫯUx;w=ZƎn^ o|;.zj7fvСC%I2?~\,X0P~p>/_VMÇK„ M=zH޽{b S^ A=a'NHҤI%C ݿ+jv$NXϞ=[[h<>{﷥%K޽,8NI p=?%}z/8%h}x`5;wN~7}m6]3Ҟ={dŋ`D(P@ԩ#}͛W|V?[dZ/^(6+ms=1~TZU6l`gҫW/iٲ˗O7u+3?Ç}g_|is3-Ni {8xLBZX;ػ~:oT&X)W="G*VcsϞ=|2yd(mذ(Rk[*EJ**nk=(ťKO?$)S4 tbkرݻwM"M6ҩS'}*d\kg+Ȑ6 evȦMeK6vŁ>}Zq#ĊK2g,.\pšrLv&3fLP>/ ݻw˜9s--T"nytA 2bDGV$]&Æ AL]-[Vz-;wS:9L3XĉGEbA920LegʔIiӦIԩ6Z5*lח +Gp" O +s@Vd'Lec$'e|v3E;uܔdڴځ;DO?^^z=Zje̙SߵXŴ69 @!d gځ#L֬Y#}`Xscƌ$N6藑`)aҥKP.F~t7 DfϞF7ncinݺVYᴰh" :M4^͖us!THt'OQ$HٳgLc2?&NhdK\JLx5g 0x9OMC%T;^8q0ósA͛r}0v~GKMjԨ!80} 6iH&&XC{1ΖFU\91h^hp pBɴi1*8BJ38RH`#q*n7~G0#{zV+LEb?F+YPjڥtW ؃m~"GHHE ^ mێKƒ'O&GA$@F"^C$1 ';4iG2>M$@$"0Hyo*HGI0b4HE3%H*/^]Jd;%>L  >>L${b?Α%r_T8}ye@$("#1[ $.{`7hPIU+IGߐۏ숩/^8GI$nFIdҩS)V,WtviۆwoٹCJU\/ѣg2ca;N$5("3[!#-[…[_j4v}%{Ay7 "HB#@! 8 ]- SJP}s`#رRh[)CbƌfHE vH=oʔ)w#MI(>фB!w<&3>($;&K:t&0@b %"' FGO̎$FRr+v=#pJNipϞ>Xرcm-ga)9sROW߾Pʖ/_]_gO XHA"2 @{ھp-, VFs,x3  r PD" /'/K5vqxpx\ Edg$@A _>}H\GuXb(]C~a_D ` ?{e#>???QDYH ltP.!!i"%Ǭ9h}@HC{ "n[xbTՅ 3t> Xd NƌY$]!i0};q3=Fs,_@s g!  PDΌO H٫j=,'O&1bX-ۡw0ĎZ8>fVK PPD$@'0oiC5mKrH/=z4ѣ %cczaywuLطtRVGHEv\cz͟1udr}I>guܷک_%;O$a"$@'0iҤJ0kc⹥u:p^ls wO 87H~= 84Ψt|Y|A}-KI4rv:GG{1 @Rl܇ւH,͛fpz͠-VS^\o$LIuؽď!E D3_ٰ:KMO@8u1cqĈ$@$n("s$qRvYSj  8ܼҥ[2j|i#URD`F?u{q+ 9tx& PD̆9$`"pbW=^X&p%UŊN3w>V@8BzzzX|rϞ2m:=xe-9%߯H(fwxiN Io܎E۽r8<;*u4 ϣnL|y$K,RƾuVqct^7P78zn%3c\2yp@}uvҭ[@=6["!&@֯+9sH Z"@)uZxLT^Srw bqU=uЅB2·@"rƌ !6 Hy) $;w*8O ̦w  *MyK$H]:$ /iשqmۚʉ>nFY &`C$@N`ڽ*\2իTZjo`sMyr$ ;NgK 8UBPabʵkwo>9 WPv^2 vHEs?ܒڵ{}Oe̘:% qi~?@dI㖼8h  Au D*q㺫85# VN$@$`DZ\     ("-@-    ("a.  u0HHHHH PxH l+ٲe [a" p).:9W"+ݳ8?._;wիW?~t[T7_&  #@~#9eڵR~}ʵ`=x8q ^޼y#_$MT[J*%! *Yf˗u?, 6 HT$QD2h \"K"e_x>, tI)d;'O9K,9sfǏˉ'zSI 7N#gϞ?3gɓ'p~r}s~gϞɢEd߾}]X9qFI85jVɒ%KJJd٦Q|`5eHHE+I`b?… e޼y#'OȨQ$wҶm[-O:%6mu̙3/cƌ/^<2dBnٲEv;t]vefղUV:K.عss۶m-s̑rɊ+Oz衭D6mT Y9I:YX7!4^jds޼yeԩr-=.\O^ѣZ~HH PD:+b17nha띑`9rOZXb8SLF2D]ҥK7n\)Z'N-&!/)1bh#XbzjŋrY`AYfFٳ6mZ]/!d!Z_ٿ3H[[)|\DWQSM"Z X[~kV*WgX/_M2uԑ~tY1bUV6n࿭R  H`d%$oZDLbVƍk8J",}Li/^X I8Bb رc-ª?p±0u&™ڰ&LP Z:HMf͚`S/X?9i$ydp[x{%JHʕS|XN{w(VAZ)1كoD&Xa=w<ٳG;k8h; q$8gaƑ#G5| aicǼn^ DؑS-k%EXMp`Z{uVpuk q`SwKD`ZSߨxpB}$I14ڰB$u}z:bcǎ3Ƒ aI9ޱ k>9M4z&KիO gHemT='8#^@-s*VQ '+8r[HiTX`|'3|`#Vm=|Cģ  PDڟ)k$``K DBe˖ aH‚ GAg߾}b3FZJ<zЎ5k&}>}Z BX/ݻwB V$y%L+|ZY9OixM)Y24jTY nbbXO ໃ)lXa? ~ zgXG´4yKЄID$+5pK|R2a ,-!T6轘1cy>^0R7K ВB+Zԩd#RH5_B˥a{mU#31cP? }Vʅ>K[+>fK88`CDBT"֭[eҥzK<˧V`)$~0 8_1^ ؑaet+r`Za+Zf̘b޽[[<VHL[#=hꫯX 4A6LΊH Xu`K$FFLEUݻu("x:Zj׮r ,WU|C2yJeEͭB 8DhCq\3ۭo FժU+E^T]r]o׮/ٯ[Δo\ Ԅ _& #@KdԱfK$`R 頇F&ci wuJ`QkM[1_:ۤC2mZe} YJ\QFZD5촄"(DHYYQ%eNE,!! *HHw-4,l=qݝ=ssgΜy\v}H_aB~4$饗tp 5CFDC@4:i9Vlmn6ո(ή!ہaS h֭w B؊L&obdʔ^Z_C̙CtvS>+ B4)Sœ}r%y`lѢ:zE|6RCa;A_~e eZ z"%-1u02ZQ1 Р(~ƒh;vkHhW/"kР>ԾFsD`"1r;7 D1Æg 8 %"q=ZU:j t]+Ss{&&"/$! Ćo4D)E%4(H>ܩ=(԰VH(H[ȧF$ZD'G#x҆ i`qĶ6~)"E d[{H &rAكr !C*˼Z/[X"ףGmeD5D^VAFۅ GX&0" P֭/ycrPP彄oCgWFbGdRsyr&!d [ә(??HȤϧ&`(,(ƶAtrA^DZccݻ!# ,:P&/$6P{҇4+D28?PZ4@QCu<A Hnl/p7? ;_!< x8w_FǝJ{rT›3gN̙q~A4|*fXa}V^X|аe P4E'_3jwc,lMR4H " (iOs, ՌƢgt8H0"q 3Pb[g}nPiA m7aɌllc+hѢ2HH *)Sŝv}Tvظ;"VpӃ8, @ + Q156I(tؖ6GXamD*` (!I(kC(ӦMӑ/^qIHGJ֌ېrEBc-jfCÖ6,P\(r66_o/ɝ;.C?~ UIĨ3?? 4llT< p;Gm@G#^hvA60?1lEAÇ5"ZmqAؠnqXF4T1 Ү VMȇTC՜={1HHDs]9+$?;JLHC:!XPaDDqF$@$`oζrv$vuN8P*%v | @%2Qqa$`?R\l$@$@EHZoΖHHH\BJK0r   ,lIF!PiRrc?PUb`I @R+ ę@FU:]$Ecz {TH*qHH9*ι, 4mZM"'~/#Ft%G+Çeʔһwh" @ '2ؓHDZ!wɓV>& ,Y2DۏIHbOJdY' -[X&L++W%s[D\fTV%# '*HA WlK-?~ܽN`9p̟^MIh<F~  }"Ë$'dɒ% ^zj6mbż &Se_˨QݕdN?J_V}!m2crUԫW^os4[w>‰'TQ\iT"=g9Q\]rprLujuEܮa\ҺuHC&KLwo$ 䒱c+gyZctZ_ҦM-W/X-lٯ-[*XNgi\OG9yWQ\p@NĉZ5jV@LV^J`VvYO>n"cǎU5ڵkoU?WG/_Zӻ#'O{qܡC+QO]8W^u}~TRETS?3]2w\3=*E^zСCLaYh~15kO^pJeŊԩS۵{Hg3oՖ8ҡ$@q&!CZiԨ,[E+g#G?bş2dtm 뤻]2eJ/:)Pr ?Yn9)R'# @hذemҥ2i$kq>~xF +̙3Uxٳ[oIo2n8֭6mZ)bck׮Iql29r>_dIl::H,"@f#жm-]~[_n{o"_N]"O_2Zrܸ>K1cZeB[ƍSMHGÇ;ai =]s -Ç+aǹuH4aᘖH;"@&#k$X~y6ăugJeƽ'%g/gri3ӻ3I(>>u@'ʹZ'0uTɐῢk۷*Ǐ˶m?آFB͛7Oԑܹsؖ~w17oެ*[j̙3ں8jҊJW2m[Sm_lG=?T*iѢ4hPQ^xaG\M?+OէOKADxϛN9U e9<97xLc `F 5U *(4mT+"xh<6Lnݪ͍x (]Zl#0dĆDF$@M ]4Ҭ3U^LVؑ,w܉45Xr|6(N@AtitرelٲE2gh\Ϛ5qKNz LC,U5'\V_AO&&|;wivŠ(JA^FDO[E?׮= ); $%'1bC6!"VL=6ѦL"GDC @D| ˧ $1X#7H# yN,"'''II`D} z/ @[]rZQ,VVܹsJ$ְr=ky\b9*t!6R8NTʖ-[S9i$`MF<]LT~TC>vkd^Ts>F{rNmlqDontE8pJ<VFDeO6M玄#W!G$ڍ7$o޼~х  .]!3yEG,K./^l)), $6# XO?_nUֹ$eW+[ϛ6:;Svz?K?Oژ@߾_| ujSzj.Tn>ӛlp2$j|>?/'I=~ S [iH:w(%LFDh19+Xƌ驂mIر޶ѣI+9*?'xcxeD|ҦMMU9fxx*vYI΃lNG_uwa2Z҈'w8,DZHeU8 $4p$J֑^fz]S /?PtHC$~:~嶲df]H{6lXIӧ/U)q䟌hȬY+e#7#UB1:r/wH?FXam{ypx2PL2|0 @| tR_f͠7|Vx [;lIN76p޼uzk_Vȭ%3g\Hq^jINJd/ Ts3aw2thGUgh|=ODɕ+]nM< !;矷ȠAu?D?oz鈾W4,P/\eזR J۶O&5*I|> @ G",PʍX!c'? ŅJdܿGٳWIlxFsʍJCqC^x> Ө5Ff'oW DK{QWyoϤE@g[Hժ%#=7|RL!4kWK[ӥK?OP*QF h x{gSJd_9^z5SDK]ADznnոqe>}D Jj$ ؁H;"@$ 2)S,#T$Cn!pWFJԙ0Z">ӕH_ߋ+1u':.ٳpr%iNQL|* 7T*Ur9@P.H8|S_OJΜY+%;sRvWb֮`9"'U9#T"MHK)UVS)ߨ%_c@͚eҤrN\rSnr7Uj?nc(re7Fv!@%.+y #({t%pY6mJ4A% ?W_}U)ի|(A̙3ɓСȯn?HppT^=7^rE#ҥSUcHڴiɓ'3dH+xa:b\J&Lw b7~&xSp @t Z!k# 4.AAAƆbO#6^/^޽8P4^WJuVVMﯶsT:sϜ5kϟ_7֭[Kٲex82AJl(;u94 $.Z"7F$6qb?\tM`/w'аaCP }vYtZIZ4ۻw>58x?~IF^|V ѯp-Β%V q ffdƍF$@D& o'yeΜ*f>rf SNUɓG+ƍ?.۶mSՋ>ìS4nv8P:HNȄ3$@!m}[ CC-ZP)xeժUj9`dH h?;I$`a(A[lL)z޼yu]G߿_ +ZUuʔ)#=[l|aA6q: @$T"#!  O!$&$wSȽ{@=;m۶H߃_]j׮- rc+W,SL)=z$?;wNCܹS珄{t n7H D&!G 0E7Ey)/yot OTw-YfT5.E(ŊSUar$Hr  ,ƫI&.{>"O&@HO^}ΝH@@;g*]dIlz2bTYfNe(z3ftf͚:xċAZ"EСC:ŋwTy7T-7CCRVRx H 1PL | %lY]YrҵkiҤ%6P&쮖"E )Qw:kJ2ƠPɝ;ysߩD:%ʓV$@%ҊFIFLBs_C]VnG춇s`ؾ}ttBYVU.]HUᩤd/%&g1Z 4KSUʥ Yo3zm,vWY˛A-H,!0 \ꀛӗ**Jt,Q(]xy=Եi!ydQӧTEȥcfnݺFyf; vC8:kIΟ?']&Ii|$@INZRz~/JMK?W<d̘N@I={ߺRB޸,-gwi]wCˡbHg P4P6 @b>zE>l;WF*'L#,IE$7Kɳ6t(y$e*0"~B޿@Ξlw=z^~uJ~S)9JX.H,De%( ImOrذNg5WL?y +nԨV -$Çknk ^^)#)b'ՊիJ(bRΨ\b  [h߾#nݺ*WVdN>}Z*|j~̙RyܗːˑvI_Ӻ;Sl?E}KIJ;rl _ɓɱcdK% %oܸ>ҭ[[1x|e<%cy?uNJ(HADҠwW*H'u[G|VyN8Fr{batpX{B."7oޑB8@^#  ۖׯԨQ:xGR (Vjgq&~C4R ;q I FT"cD$@$3Zj4loeK8a||kBH,J o* g4@Xt1=@iS je`n _ٹ,Xh)e#* vذ{TmoE2 &.|,"@TyDݻ*Zn~Pxci(9sfq :DZg()  W "iLY,G$L6HEːKBΟFD8wdȑٸ6$@%҆) *|A'pԨ*tg7i( ğ,ˀ@T"g˖*/A5VJ%CJ}֒3!0!`87nt\OZfBI) $jWXLn߾Kuܴi@Yw iţ$@!mloOwR (&4cː! ة^1xI.yiXBdʔ^*U*_FUno|仅 PQt k}dɒ2rmBDYeÆكHa*XG΂H"'OJ5d}LYhIMA7Y@Ξ=,d„ zǏ˫*W"ٰaCK{'2e$88XW.(&]rE%k#ҥ*UHڴONwѣGŋ] %O>-8~~Aiw:e…*e`_~]+ժU2w\3g͚%1cH֭lٲgYF֭[ .qFBYr%ʫGI,MUD (ҪU i׮$K$6l(xAY\tL4I+,?/*PK4iĉB8sL~2^z Qr_͛kKe5d٪XgE:E2K{Ҥ%22 $.jJÇJ7WW>H@#ŌVpaPlaCeYf*@h+y @t3f̷"٦MMZ%MJ.\aѸqcUqܸqe۶m60ԩ#4a팋?#ƃuQU*DZj(, "gXkQX%@\?WyȑpPBTf<'iӦB_[J߾}UҫWpF*.U.۴riգ$@$-[&=ߺv۶# Z%mڜTL2dȠܹsGrn9(Q,X "e3nѢUVi>OdyHHe47V)Jep9e͛WٵkW8߯?"V~peʔ6GQ-[mDM\,όciUL$@$gϤ=T-2qwҠAEԩ" x̙3K۶mu5|T"믿.kזB Cʕeʔ)RX1<.ZHΝ;!Q #}=pb۰Ryԩpd͚̙3:͏:H2{JnjζjQV %Pny nˮ]< 'G+wP̐ ܊+\6X4B̕+Nb$H*@U$VbդI! R}}PR^qz׭x@KW2 x$0=z^[)7ny$NsB z#*P2f@͚5u}.R:tH9ŋwTy7,D2s;6*v\UΉHP_kwuI.U Զ7'GP&asWCC߰ŎcljHHTPeꪦ`  c*) miDH<kg<#Snݻ+W$^8֮ݭ+T2ߓn~DO2JP HH˩9&|Z9gm(8SE>O;x8mΙD:PHOroQ idn T,֟g@$`*4rP H84o^U>|ɓMzkNR遬]b-d8 + Pt%ME$@&":ՓӇ^_ȏ?.!!L$%E!**V]9M$@$>}OOA2dtY F$@ P̄gHHd Qɕ+ddf |9) ^HLGA6|3iKoV&-ڨNV D$`^T"ͻ6HJ/>zE6r> ؇H%gB$@"={&yֺ5o"<T"=o9c pJ [LRL!xH"? HJd؁HHHH "*3 @DƈHHHH"H? ; \zM.^Gn{ɣGL5/*Z C$@$@&P@.yL'مޓs.8QL~޹sǸxGat`ƌ􉌁/@t^# ܹsf͚`;w#o9zVŰ٧Ov:&,P0^߷o/_>m]vE۸F$xD&k>HlE!,ziz W˖-H")x`-[X8h_H䙄"a͛܎ܳgOYfzd:(l$@GىǚO" [ڵkҵkH9(ǏaÆ 8zRV|yɟ?Ν[  uu ʰûw!N(`\R-I p*cˑIH`!Y{/4qi1lԩS *JcժUgI,Y[7ސ_]9@ uְkeĆCG$@=^% p$Jٲes<}a Yt '̀9< ؑH;*D$@$@$@n&@%̀9< ؑ}" X`ʕESipBUYrVzUpU Vt' Hy _~Y @D̈=HHr RdVJbP1O;t A$@H~ HHfS|Сsz9m Dq$4,YIy]>ٕer($@"@%TAaHH a]-=XĵUv)$m2bDWen %l]zNHnPyfƽh&65ZT^i^CWA]˰ax@$@q%@%2؟HLH`ǎʲ8Ci=u U#>W^*SׇHH .ZK$@&!X'00Xf^%g˨QݥP!oBa-Cv>Q}GCBɸq9$~7nܑ̙Cŋ昬:DbS4 @iRgeKjJɺtxzΝ;e>RZxϛH*6oWʕ-'*WΓ'NI,"r$~9xA -ɓ'lSgȘ6;v)nܾXRr;Fp!S#\FӥүPx7rHI%… wP-iӱrM$`ׯˏ?,sڵroVɈ[ꟻƖ#p K'XWbO$@I@){{'YwD @RT\ %!2RQIi޺B,b7 p/*IH AC j"ҡm'o&p+*nIH Qڀ>T ㏑w @k$% '*?Z#ꕐ_<H|DoM( @]B~i׹IJJYWr x$ןmRo'M!@H%%9J`ݿg X-V_AO$`9 J͜8 iߵH,F9 -`<= Gd| HtT"9H$@ Wl$@$`T"RHVyZ~]G 5{vJU j Prv$@&$tO&Uk9+9sglӛPZD$@ Pt΅gI :$}zeʕq'O,;wu:ҩS'iԨL0Ayb^s sygٽԀJ.-6m֑#GڵkQ^텉'ʰa$һwouԬYSf̘"&Yy`u.m*8ѷ4lV$@$`XcU#W_Ie…[h!JX(˕+'7nܐ{"'9sfo=}Yddɒ=-ߗɓK)>}|ҧO}G1cFOe\LB6܋DlPԩSG??x@˘&M944Tp.]tNm'hܒ-GSq$@$`Re҅X&,gϞJ1%Kʟ)y *˗}2w\Ϥk׮P'u֭+ZjzD-9rc",Vr%-[V`|B?q!~:tCI,)SF#ٲeѣG1۷ŒyAڵkkN:%O=|G;RԽo;ZΔ) 2]=m K{|9Q iߵ̒+".\ ȋ/(s̑/mYʾ} /8}uV-ݻwJXaD???QVq̙tR9p`ҥKrAm僵rĈYR%y7dŊZU~NL@lç~Af͚/<1߫WjF=zT`P׬Y#K1y豔(bR6 *N$7UV___?"0X ,(ݺus(Gu/yРAk&{N+e8?|XX1m |qƎ+ǎVݻwK.]G+tNʲ|r-3lšؽ{wYl׊ >@QDÇޓٳK6my%gΜҤIǏoWzvE$aDz؂sCʕ+V%KhO?-_S! h+O!uq [iӦRxq3f%9;kae9}V1O?_P\ ̓'OJV5XMFlFK6^a#8g*dᢹ:=΋HPthVhPh(}i^X h4 o߮w1.kKᆱ-bAyrc/Bt7,f͚m۶I:u 1vtʱч$@$@"@%ZEi-Bm۶S`ݻW+q<#(p-A,<"hhH3mQF9|`öy [ҴiSyee˖:y/ {(+b&p5* A$@&$@%҄BO`ԩHjA X찥 $ZΝW^Z9KȌ1&|%=iX&T;PỈ|8ƶ6oٲEV^-FdA8zU@EoD~`[P.ǟ7o^Be{'TLOds$@!LxxM>^7BKu?o"xUq[HsSH&DD+zl>*U*탉q`D*la9#a}o#PÎ,ms7BY ې&D8kfkѳK[Ϫ *I?lZPjr$@I௭Il8u_ cMxHmաl$ (~IƍuP˂ bq'$%JO֓R>HK}$`hy$"Q09+{v9+r_(Gs@$@V"@KV XJ 'jQ> *VZ-J$`)!!ȡKRJpr=}U 2~ *[2 L$`hTRJO[eHOS,v/IeíGSzSb %*n'q1siݡX@OgRWsxHFJV$:/g:$@$@v%@%Ү+yEKԉ˲d@:mDzG۟I`eni]cr  H*T"<$n\+K3'H;ѳl$XȞCתH>HFJr`3"eׯ/;( 닗W 3HY<ߥ;O)H$ Dz*{lErʻ:G黁$$OJ-Γ O$@$D&>s>1 ?Gtuwŋ7@hhsH:)Tj(]%ş ePRQиط,M(*c?,O=#-]>Lxx<\)Cިׯݕ%ꏕ#"sRK;yHLMJŕ@0pr ~/=C8?NgW ]SJ$}"=;ٓPjz\No,1:KTz{W­G?۠PרTϺu3PYSJڴb$@$`-kU1X~Q% /].1>H ` \ڱb#oI&]2! 3iU N ܻ@2JB^XI!_RJA}Șd¸>Yb$@$`)T"-\#lh}lZə+Ws&Ι& XZS}FzTOt W|c @K@" y@$=Nܹ\q(~&XcU:qYrygltM 6؇HBDZhy }jgO_OmaCGƪ?;@D+ooo}СC2}&7Mn +'rfˑ!^& 0+*f]mKHSUsG@ ,YRx4GɮdɒE$@$`iT"-|>>\-:%?aq&O9o/ ؍]|>yQѫT -9ZO!3FۇIHhHɌ%Kt9 PfQ7uI PU7ɜQf$uj/y3ɺP Ȕ9kh$@$`T"M(ƼY[T*Lұ[U*IH,M>^> ӂrmi۩ H.#%' `T"=xjW.ȐךW,%  @=nvl;!ZJtOf&  0 *Y rpYhxdΒ IHHDxq4gܯ7WJN6P  *ƛB'tȂsV%K0]ps$@$*Qy'~F*'*tɘ@O󗜹X EINJdܙX@2&%KHecy=\rG2fJ#R1=V H?"g~6)Q*lWecr  K7[Ճg# %Ү+B* d$'!p4T D!@%2Q0{C+ךSOyy9[~%23y m p;۶K4w[f(7'qxe /H*Upg컞={VM&&LcW_}U'W^˗G9(` 68{ɓ'e:Γ?Or-iРhB*WG~l?e߸夀$@$_DƗD (JV:v*Et݌'O.~L:5N2|X'xNPPPP"qӂŋw:e…G3f{;>Dĉeذa?~ݻV$k֬NҥK˦M!itKOi $- F ]*T)(0N=88X[^u/J[XKat£33gNI&MtGְaC ҥKeҤI#y5a+Z̚5+ѝrzɜ*wʔqqP?ӧG1cF8Pqܻw/xvX&O[Dz֬Y#<;؇s%MufTN$ .3<j+d>8[ld˖MƏҰҊ1ܑ#G?SEh[֊Ѹq/yIvUVI\Zj)S&mTԩSZϵ"y֮][HoSի-PN2wX@CBBĉ:tPo *˗}2w\Ϥk׮ePB?#eʔܹs{1wyGdɢbȐ!0|' p!*.C{No9&/m,ɒ% oV+2PB:t }Cv(G-grA13f 0@z}}}K.2gw_Mǀ.:pVz>nݺUpΝ;+t_(~?@9bAɡC۹cG-*U7xC 0U~־}/n01M(:uk׮ 0 ~BBB5{?j(B0+: :l"h~PVn]i޼+aŊJW\رcرcҸ{nPh.MXPEAFP a5?Xf|̙3zK%ٳK6m)(p+hҤxyyǍ|' p9Aa^BCSK.ψjbϟ?_+,5jЋ;,X #GtPsF'X,Xб=!C} A(6F3|*ޱ c>~aʰ-o޼QZlÒXxqmeao1X1ʖ _ &ކR<+bV6YX1G(IZ$@$`3DlAs:goA4u`BE%KhƘ,H VZKmEmhkC;bΉƍ/"_.v& ?EAV5,51,SlڴܼySABI2ٲeK}VRb[[ȹL @(ˊ{!Y MVf͚KI 0[FNZ^{5X'A3 x:|wQl4 ?Als+%+#藘ʕ(p9/"_c^[lիW;tY"!'b/Dr+*5h ʇիW׊s=q(S| B ; $SQ kK\ 7BK]"ܬ?8L|wZUT+lFG|ryn߾-lElՃCU!c>{ȇT d9s:a' ߮aTV7^$^& H m=.NA#80&^<dyM@{%?1]؆mgy&HP8TO?ܧ5.s ZؤQήAΒ%K:.!;Hppe [IHD~# _+54P$^=l%p])-5]F$@!@%Vjn_IdmG$@$@$aDz؂4uKԩa21uu   &@%҃?/ݖHL ? '@%2<m칳6KU$k6/'N$@$@$@%2mC"ErרݧK 9uK`$@$`T"Jn񺪲R"կI^n k^+ِ ĆPyH6$G6)G%sIRxypH,@Jɝ"n|TBBKeM#?Ϳ 8!  = %VܲEwX?w˒%K"Ç% @daHp<ĭ[ٳf,Y2s՛dߞRrA eÈ`HcP#A>Aȋ$@$Dz"S|VygMy)/enɤ"O^1wg\(xH>c>Frpm?^$ O$@%҃V}ڃ.v*,?kD]4no\Jl!28% }h4rBUSe# OJdxk'{ֲO7_#|;H > )iҦC.D{;P&\'HH#Xeh҆|oʌ)\w`IlMUYT~Y[l۷+Jv $@$iDzQHΝ&/ݽ8O֣? @z%Q4ƀA۪2zB'wy *?`\c XH/ax,MxyYr`oF?a^%8H)iW+ÚuTT~O^~q9 !@H%O,RB~ 'O\)o˻;R ZW`D2Wiӱb liK~)9 -.h!_#+NhVqߥ^*%Т .'3? .k4~:ťX2{F9r^|dȘ&R?  %+ҬuyJD/^/եi{ҠFNHcsfQk', k|' O!@KMWUi.]Xj?/%8-Fr~8vJ?@ }TJ, cꠠۨhW OlݡJz7_nRDKN%yrm [iE>;X!<_]R/8@Uy{\{AEgOɖ Cj]9iG::Mi ST"bɐ2|TKKL¹kJ\'U"m*XBf IQhڲoCg,'<kuZsM`t>ygKI_O?^sXR'Df: *%:EJ  PѪ"9G}gL?+D`z yTRR@ $1:ۨF$@v#l҅;q)sZS ~cﶕ\ޑ#M-<#8h\:znÒҲ]8ͮ$@$`nD{}b-ݩJ4nZCo9rQt3joŞwwЉɿND2A$@ #@%2aLsnU@IDATVF o;}ꊼ1d(P@ETl3wn&@$@&@%݄aOTY4Gء*r7[Iڴ> @g{֔+->#O$ioŻFIP=;OfyOA6dUk|Y˩ܙұ[U/N&@Ko/M i VJa%[ut+DJNr55?HHjhڊ׮':Ys)J,_J\BQ 0ԩd-fT#w,&A2SDb#~7;ם͈Jd%ͿT9e3/7e+FҲ-S$O73wty+]Iz-if)r9@ҥGxƷoߖcI D&_|NlJ1pW'Jlش4nQI"Hzb[ 57iج|" WJ1H%BBBY¼vw'O`hl߉n_!Zb !o Tyb# 3iՉB_ێK٭4i޺kT*I2fJ򨶕`h3e*/ HL4rl4hRZgHMĻtULL4hZ:' JR^DJ7w g) ؁H"~41ϔ٦ceٯhR\0/v*)y. +K x,Xl/[i$}l#E RӦ'yA={ RJ/ŋe߻w2yd:u;w.uCʔ)uc<(0~{ wwґEDEHGņ(Ri ;{76%wɼ?۝yr[~mpႵ;h+W.yׂ>O4IO$o޼R|yy~[v%8p@+&חڵkKDAn|ܹHTIqˤ*kZj~wN3m @ `4Qg.ry9jOPP @~2vXMƒߠA_w݌3Bݷo|gzGTCXB>F&N(ܼy3rm}޽{ ꫒>}z֭@0|MM.^ۅ_"Eda5 u$wAvء:ر>nԨ7 ÃkPDZp#2kD~Y 0S6|WW*2>8.b?n[@~f@O.}?\nܸ!ɒN[n+ǎ 2(p`ЧO&zƍ gmm6B:w\ټy˗/PݥtҚLBÒK. Q\qWy}ׯJjԨ!ӦM ARpaACYxq}q1i'Kcy]y$MT!giҤL2,S7!r]*dk07D?kJSJĉ[CJr@r!':ty?y *K _hX6L^z%yb٪U+̙3(͟?_k7=^d&_kA놉VrO$K,Z[6g2vH*ʾ@)UK,I;sjժD @pwԨQl&0 @ڴi|nj# H2zZJg.&m%kKF1jܸ!:u(3HJyoA H?goMKE~gcK6X >~Ak*V04^ X}r#h5BH] GyyY*U rzO:X=.L3ˆ 2i;C>?Lv, A0livR9 a5k&zg-^Xkiaأqp"fԩA]AlbF31Dbg&h -N%Qq,L/Vێ{2_A C";;IZ)V˵+۫i|Gɓ!tEm۶ M̦ O|zJA#9q4o2Fl2_ 6NJ|A)Ah"ɐ@?/Vf&X ͤYݤI=zt0¸zjDbVRE3^~]r" b=EkI Z1c6)T4@&~B?M `F Fú4Zȓ/IjcG.ȫG{ZN-$Qم48nFz r&B21ڵX$&pu:!A-Z瓛㘞%iK &,*<"#Q"Y j57ː[hRiܘLŘ-Z@+$@ɒ%Js0!CH\ ԠDZ/A1cƏuɺ cWk0 *_-ׇjoYf\K;|p!&DօǔcD)SF|N1|.L>c9pSv [h|"}ƝCTV[=x:92x V8uk/@A3 Ghk y`~ܮd->߮\j רU3S J!ȷnݲ z[~f|iK=0BBkoߓFI4=eRc_/tjw華6UFh AA čGzR&'}Xz> o"@P ~ِ/dͺ*fj!EJUd`mB냊_A(Y9H%dܹzʔ)%Z$_|M/eҤIҴiS!S8ew!+Vڵk'O 'C(` y" Pg=uԺ@;' 88ڐHo!~/ئ%7~Ҫ6( 1 "ڟQKRI"_]ry>~Ƭ;Ѓ}AfnV)W$-[jJLդoNAGr]v՚ŰQrႌYMH")^z%]"ݻwq>HCv ]D- )8iQ#ku4+\pA+DJMFYi ޽{d+%vфҖ?%3Θ1#0$2*PvׯݖkʓʔUkGRQA @$;uJ&n+oߋFHY/v-#&/HD &RGySKhl-!a%}$v|ЊR,M!~-)W.0j(9mڴ9 B+$Dm"}iB1{[iDHx7qSQ)'2*Zʘ%K(9|.ٽ 4߉g`z1| g:U&OU.4T>_!sxXHXX>YCHNX-X۸qca| )Ǝ`a %2FLѐUҥK5 \m`B3G+ s3EVl5L=z{b0b|i&UKKHF>_KxY|}^MvLL#u>~q5۵,Ez*{ $_" ȝAƼP=_:hscǎi1&g" 3x۹3RSh}@Vs1א?*hMe˖А"ĩE)WXyl1G37~SFH:'s=ގbqt}6$2w.b܌ry:Qr=@4#жc%x |'e{l*ar(ze ი "JgϞD}?>hƍk6>0XB [/" фH"h]fl A&(oƋ&DK.=zTkaF9f}>cU1$ ڀemH*q ڈy3 @;H9Q񑌎{`B!yjRէO >a& eM{Tr:\RGTC.I#&fuɴO>AEf4 (jݺuZ58 5/_hXHNT6 *X7:v[7WiٛYRL4h\қZެAvvD&sG2"Œsv691뒺 Xp"l1I$3n0_KQ8:OY$$tA h\eɞ#yIH؛C"-]C8}7C$6>=zSN!U޸ `/&LesfC^N(^s{mgO_ _{֑iz:cA|5I<|R230!B֍~Cj48խ&D\~bi޺n 42|) A*Uk>vP+gܓ٤re0`4|=t^&~g A YL")9Xr>R;eܨ"W}d)^J׻{ӧ*9rd 愘[wT*1LB՟M;̙+_`c1sܸqGz2FG$r#AC"?*Լ1"09/Iɿ: j,eΒZ~^B.oךd3_R“:Q:1c$w.HsrE$HTi+Oɞ36W_>~g2c4kK}' &-#ifA  g}7&I1ǯN ͏Wdy]Udm./4t`Koha.ry{wȷSE| Ɨ7GUJC 0i❆D@0| {LtZW4}1fhSNrJCP5\̬uɦr18"v҉{=0110$2 ;)ri@nݼ۵@9ߐ&1>~3A k*%M"Huqg}R'^ʇ *X˗o-Ǥ|w-A #}bid@ϟ?yuٳ_ 5+T[b|֪:F.7+ ?Res酺9Ojca^|H˔˭,hKD;|A/尀q_~DW&{cd3]|n=oԼq"NC#煱KܩY#55C"#_'S{\<[|de~"#;&Oc1 jʌ'@5}x*/U.yeHLWy7ʴjF=Z+f#G`2ҬRf Q$VjȵiS51%A @( 8=DJU^U#+LOyںXd1Lq6XIGFt\D=h+_2] ]_wFw`(C'%UVCk&*Uj( Yܽ>dm6m&#G]$OiUZ*:>]r(U *ru6lRJ j|!d>xC"}z\RI*DuFF UBqARE)Tfypڅ_Fߧ@i*lrό߻y&t #{֖ϒEꜥQI ;N2f#7rNE"EUx*#G  &KhXĈoL"rOncr{ZBoHH jϰ~?Hkk-iiMp"@Vg;W/wDt1x {:[(9SY WBI]{]sW~0HO!ix?53\VsNqs:ٻ3G@LAe2t Gc}yܹsG @*'&Zqg~kwb$?e!^mW \@͏*ҮTـ j/ k]]-7U]b tl2d]xNDqJAkecSǗWzחJ #ѣ] o3ILB#@H/LIB[deRM/л]3Y%M. T2dJ ш>Hs%FWv4N\KčW+[~G|C"pg Y]%{S/^m1]OV8'Wr1=WY4K猍lb=6s4B"`Cb={N*xD3/Щo"Ɍȵ#zu=y (s蘪wFN`@ ^DA249jG67i6 Ch"=e`GTnJGWЮ^W$~X#cG&5nN)zRM1eJfA T $bv\]S}B"c_J HX5bٗhfTU)^O9A0lޮ+U:ku2Y\pCLXKpuNȲ͌ GYBi#7GЀ'c0xi?lV&pFU۶nOmE{ [7U-dΓ2{j'pE;uǺ\CF ԨSX'pː"d'Xn<3r! ջ2t()_>:cL} s'PT}l+Y~:dDqs=q좼3llrTL7*idxA4cFVV@~_3V>[n/^>|(W\ jo8{Přp-\x߿/ňA q 8v\r_jtQAG̛?SY*WJEUF>ѪD}1v~bpBiѢ?~LҧO/iӦm]=zTk3C ҪU+y>yf)ZdɒE7tPz,XP 9sԟw!;wu 'MTƌ#{C=|6 xRe ?#UiΟ.M.Qxvg*m-^+89hMOTJe~;c5{פL2YZrtAe&4jH[={@ a|W);w|˗1|ׯ˪U42o<}SN7o^t?k.M\oܸ۲eKpl۶ML9}t? eE%Kh WJ>R*crcs_jB=n  C"=p(2XN%"ݕ+oʬiK5uGݭwe7+tg:U֩|=R<"4jի’{τ$ʎc9sԯ__J(!'N~I/W|  .]DpݲtRi֬L4I |ٳgDɈ#_~Yĉ*qœ_]V^̴Mh˖-+*U҄sWX5 &ɓ'sUW^Z]vZq3ܽ eذah;v:ׅ ӧΚ5֭[%:3 YO,%S[%oToZFvxZ-ˈ3$q*)=A V"7Ko9>$Mz< |]5Us>eTyɓX/&sxl< R׾}{9|$H@J*ee>9Dc$qĚ '|-)]*6Xb8m~`~y<9sfMbZ:s=Iڵkezhe;3dLtv$.Z Iii?]iиx!eNKNv)mϯ*˖-s +5j8=捝SNZ|+|О$IDkbOdWGi߷ɓk-VÆ (2ڹO] _LYRCfK6夊6&P]rzfS~yU:B5++N/֐2ri$/ر50a&RIF_qܸq_hm#J7>.]:m3g<䓚B0i#hV ZJGB9"h5dlhΝ /{@͆ t E:|&?NaҒ[ f9P%Gѝn`̙^:wyhN99hd̜R2V}\f̘1C?l/R0Y.C!9|8˗&`AP@h ans"`y`H[|œ $ do1+[ni.eʔz7saniۂ3/| /5Lį3햳I&!o4if7F6En U+6}rI&}J z$?U& n7\U*SJ]e'TPA[n2h mX>}M"Ѡ"|9SLo}JC@>~:t1aeMd$n$ w!Asq۝h S\BeЛb-t["K~mD/$鯿ҧF4 '?+V 60C 9yOa6>gb=C͒%Rp9j cB&J,)hxO8Q ^g:]|SD-{L9h DaO=&'`BH mZxQzٲde疍*lXBϞg:U ۺ3ewcGwd`2dqq9k | !B4hX0gCH9_t  Rr.>|?cHwZO/w¹lde>Dwm۶`AЎ[ 0$2ws0>L%A:wNs@GD?"C,hf&0/!jPh*D"V m]Rŵ٣`fYf%B4s lArmܸ6A0SZiK\>aSn <N* t(~A=~o!DS@ &s'Ho蹡Md$!Z r f?Pc&m@Jq[w>=DXf~V2 ]v)[.lx$ۏ}99r^N"_!;Wi_N޹5@?NQl'vۓ!͖~ZwęF8@,uNXk~ X@^1EL0}r@cO{?a5؄4k̕t8{)zV %?jAӰ)YhBtȒ#%1bnnOeCfT&h=x`C`8p@R|Q!`"G=rס !~df] |1{}8'l9>XhB 92 .6:|W_i҇y&Wڵ&~w%rYB.8"eoeڇ>də^:<_-չ|_+7{w3"{WQ3St_VP)G>Kvbzn;KHrLBŲM ήT&V6H{*|1U?-"}DBXM㹐N4حhF->q$^X5Uv!2 }ySEĚE(B @x ~f߶mȴ;N:Hڕ,`Yt z)3W:\3~jw]vEՁp\Rx[nAÍ@"& s2Ad۷o5kjsXc OT?Z{^bbr  $5O}+R4LQ"dA_2|0x(SD,% UHkdU!y8;6ejARD[8efQǼz7G?o$yvRmqu-.h̒5`x$V IoyG_tDӄ--ZnW1N|ZY ZSqW |-b .Vk !C ?wט\w̟>,~_OѿI?Ȍ)k>q2Oܥ1Dp㏄$MHk Bb&i>}HHvaR A ܸTMxJzؘ@?'?(~8^? qiР6 Q$ d%D|k 1l|XC.]įqSethc'04 VZ2t|tÉ_롕,Y&N4Y?r-Y҅ۥyraXc,^*lpXeB`@O0sr!m;VV"K7p@4pBvԩSZ}h3F^x//V$55֭( Ak3YA.W3V-+ F fȋ/pW-AhYC,xyAM;1c7iSӦMuD/O !#f{"9üZ̜0ʗ}J$IB"*=ߣ$Q)ɍkwy ,g>@d#/wDmʥ;Kq29oY4dؙ` 1e$Wϓ}g}ڟ;;MJ! >.Fǯ K/!5 h@KyrD4DБ~<0?@%0~.4v<[qwYNTA<4~C~>kObG=D;ø}9hxa fha-iiLC]݂=/T*Axxo:Q'" =_ gM{MdxE ut9ݻwnH$/O[ 6Dwz!TAAKL[% H$Dvd |>Ad5fj|LB #"!|łqYZB4>|qsB!I^xa͒UVM9#Fk<ɂPV-7&M{18ZV'SX) RI2!nԁ˽#46.##m;Nu"ϝ*?k4v'xWI@ b8{s܆T5J A=9fij]~Ş!y1r|<<\ O4pm]cborLX:gû"LB#Vu"{]dk}2 كI$KD$O: 73ƪ٪UTmR@ 9TV-Hk)ދ"S@IDAT}K7 Y M7y#!(&@P As~cZ~CP\d˙V~_Cڨk)l7UZ;gZ)iϧ,ʗdsxUn!nC|"Ř%Ҭ: pFD% 2cN"x t,~S>S+}H)k $7d^n<" C@.[ 8y^,rk4qh-&Q(yDZG +sƴ, d❺Tm٥vb!~!ԭuzКn%&JWwL>7of$IH2E<5dۖJm|ʋ ^`@^bz;ÐH7P|")vdo**5}mض|e 8mmIHOVf H#@6&q;Q^8{>0A)~i01itW,yL Ki'V9d. :2Bbڶxavnar\H>T5W&OՙBLRI"u/'\b'tt -ۈ'rSu7ǑD*16J"qMEXAxaVa>&Z&pGӍ&7vUVQJ]Ma,e+.khS\̶A `86˂y2oO:UGICIǧ, ֊ܷ!T! A'<B&EԻPZσ;19KN(n|1U k^tuBk)!o%%J /úD˲q`M:C _BZr\.j<SI,5(qr7+E###[6 kڐN#:"lc89~H~7Uܹs.~R^qm۶~`2ydݞV'b́+?cAo,hAr5\5`h_,, ͏[BKr.,M{u5X|HTjBEkI7`. dNQy%]|`΢5IXD:x:/s `EgRvQ3ۗgfvi#Aȸ#׮]-y~Dvu%\Ύޟu}ihü\Yӑ@ZǢc6{t5I Am.Z@VBM奇JD@€ KZH4; crvZm4hسp'jHoFR: JR4qI2ٱcG"敾ѐDMc e[ +oFƇ\p6 *\D*ߌ];PϘBwzJD ipƷ gџ#r-sA {Rb!l#Uq$ 7LJܒYH7g>x!x hiрC,!0">֤xS61D mi yH Uތg sՄm._Ϙ79DD:7[rv`gPS;u$2*wW2ycdH:YaH@x)")d<:f `_Hˋ/H46Q%hI*n$v v|E.E[6M!ɤ+*,1x3HC= *ƜƝ" &zkAyy) -aDe#:ŽD\d6UG!ŀ* Ǐ^~|20Uɜm [8bճM)J|-,QU+<qĿD;%2t,`*K#=V A![㏮u48;Bvnz]㏲>ۡ8'uaӦ-[Szkw$Ji$ dvQ Kr}9w|6] ( w# LfʜJ#"N3|?E?#Ja‡ZDMzQ&h !V< ^H+] c#K?9K\BB 7J9QBZGd/>%@<5jp>Gv6W bـRi+EF5TX8|RFhٙ[ʛAr29Uj%FW\+cixdЛ̓At:᭝No!0o$q/,*jg,B>8!u$¤"u`c͛7"?$ZVh"jiJO: 9}\6#9ǧ@u Y=g:gŪU*.k N?NpI + LQ |h jE*YE]b m@x|ChYiL2Vr) cڅ%k4As۴5 ?{ )-R_fiL)C"#9Mt$Ecğx=Ms+tU5%b|竨RrKlib\ ;aHdı3g@1ETK4Eg&-z.N7  OgH%%1 !r<8NJ7~_~}`HVK˶uLO3_H yY4yr͐~q6 6F}E(6kUVFMe;ݱ"GU*jJ)^:]W8$?kܾ+2z7p3X!1v2B5 + q@ZDq0޸qo_2gI-o}9|0f  TV@HѻJX@8i_)dD% @p G2kRJd|stf<*?ե׬/VKӖe%C1 3!pmٽR˞'G"Ú+} $ _EH_3WW>κ0S tTY; ` T9Tf;Oʥ7pѬRh6e~BҦK&*kW Uy梦A % G/36?HeFNK3YcK*WwHrL@#gEn?gdj9#']UPҡkUX֬~:|.Ygdz3 D0iښY}WWA2dM 3dJMEmsgIU=_LҢ*UP:Kast~1 ߑHׯ87uN>}l}P;әPw3Yd/c8/]$hJ)P(@Go}TdC0`|yYdIej `pvN,d-8LǏ^ ; ԿΔ9Nyd1KuQGw83 oرCZn-4K fL0AN:ɝ;wg۶m+/ htR={>|X2e$smAj*9~@Z5j$͓˷~υlׯꫲaի>}Z5kV֭wٲez߽@TYEbv7Pͦgވ_$mR\Q=s=A (#:Lh".bJܹs֮cǎ:+vI4ҥK'˗/6 | :r,Z mۦg;"hHLZSL2w9rϟk?4g__xQ03G|&!fvae Q7xMg#0ūrSU\J!dTIIOKZ~hB_UǼPH\jljG<ӑ%Hd - Iyф6j(B "M̾ow8⃈@0i˗Oo޼ & f3fȳ>+]W.7yY?͛7.ن,XPNI`51L qdO9>S7n\}JWC9fX$0YtmHztS ˖ϭI/j#s).kcI7 >2sȔ?QDSM)ݿte$>bŊ 90B0B֚4i+L6!ԩS'EG՜?h_ưHoI-a_w%o\rɓRxq[̛y!ϯ??s/RG;VdQmgϞ.ۚM@CW.K751t$A驔)s.D1*]Z$Թo۱|3dlJƦ s-W)@X@KtDR@"+ "wmm qI/_Ceoϭs$m$O$JAz!/ۧ5.N:~majwuܱDi𫦮Qir֯;(U)8nv"9/R˚$ޒ+$}>I%&ZT~D4$eҳɪ "@yS+/喙=lEl$m!XA9#ɘѹp.8PgB.˰ĚWX"VM*%]rdys]Rm/E)z2޸~Gnܸ#Dq=p}SksW"C}I9L5ׁGThIr&''j'I>K@,#ӭdaKd{cӉA `p%W1I<^L7f}UP~BiȎ$CƔRLNym@=T:3.AP T zlSr!Ma 5K<*ۙHN4YVٗHUR"m˔˭÷e[S*=E\ `}%$"&C))g%O ƱErU.,8#W}EYn~~~&~wU;"j_Q8ڗ8I?^Cx|l6*RHA#1uHqe:Z=f `uDB {⦆Z:tu~DCEggjh;#I >%r֕2Iѭrځw޸qKo>*e+j \] ?I߭%F| /^OO*xs\ʁ5/'*'q>S9}zCئ X` s,J|F=r\VWp*?q|hʜ #OCoiƝ;H"m$iLm0%߱0Q1a$y89Yp e Fv]G̜9tHͧ/ f'*6o6W~we yo/:3F % k"T1έ r8Wi?9631#k֬H!C sϐ!o3Wu;*Qe A=$t;]*wIzCH~Qa43x֮'dW^ONi͔$Ӌq-y$1;)SZ)wR`Feq*YzR%DCȵxɕ'}6zUޑUm#I=Єʱ:{:ih#G9GH*e2>Qx\dRVH]oIdfOΞ31]Od楝V89u #2wFmέdLs L cؼ=]t|.ٱ\rK_m!%2JITF G ^dtZMٳ뤬_s@V*Ν&>Z$on2#4|C"}Vxo TӲx6y{lHS~1md`N*,?uR \휏2 OzM,\r^T_O[-JKrv.y>ZPc$JV$XjXI-.^gD>Zh.7l aUrl y <>#6)%+)W˚?s%[8RrPb GcJRổ2J!$PNE2U kү`iҤ5$6xy:|F.Z;s,#0y$9}oC"c73%_d~ dʽrֽRŲ:2RrCDM!Jc!eA `3cgQZp 3 WʫZؑ TZA;wӚJ|+- Us `0 !{ozd!,VεoY_iAS'_&E,MЎ_t3xA `UnwN6ENv)*Ȑ{Gm%W 2&>yγV_A `0 @&m2]?2YO_(-ڔedٺtXݶRE(ND%O$5#I!%qe2tY8o[Zΰi A `DFIDh4,.h!U#Tq.̊|fOBː)3q[y[uYSA `ܹ֭sGdsA 0$2_4iʥ 7$STrSTFȁg`JܾLå9s9 DE`B0@HPA#*dTDE$'ɒd[v/U?ކygn7UruѫUw%@Pp;vcN8uiҥKMd9dsU/Z=`)l6-P0uT7*%"4,{7 =2_Pt_c9tP[%*"cq|>qݒ!o>q3<ݦC+$ϟ$^n!X-0kM $wLp=t 3Cm:T"rDX_P&pㆉ{greX+廼 Pg]}WJ `6*,F9}t{,_쫯7~ ÇKť[nܼyr]w.yJ)dРAVbbԳgO%,,Z~CԍeJ*RV-+vɒ%grZ`v Y)ٳ 0 }9{ɒ%O>Çsܹm׭['Q-PY}9x|5B`B[oyBrw-Xu֕.|_U .l?zsi`&>DkAc^p=!/zZ"smon|Y]:zWK5&-ٮ>u4IGʄ#=0ɳsHo)Dmsѫ.F|oߒM%DzY._&|K&kxV֏e#FC sFLas aÆɛoi%Csۛ;):n{1ɤbŊ΢hEo&+W}*Kj:Zß2d,=bx?9r3ڇ ,Ÿ= )#V^~"ƍu4h`W6v]CHѣ7".sVn`yT{lBmsi7[liJdbי2e]snXwDd)I,܊n^yeJ;k#:K25+cT.O"IO)I.-VnG/o ]@d9X8Y蘶-oo1/>R&O, 323x`YfMtg8| 4GN _)L&oy||ci4\ƼPE2BH߾}#7s&FGXKC S1 ,|NJ^umL2 E,8Ǝ^k@ͪUlMdQLZ$Ibh[:%z|U|8Gydٵo3"VM|qV@Gx!(}BΘ1> Zh!Uq<8/;B +2ʜ +sݡp޽;O ]\]Lٲe=r{vq@!氚󐁋yO~!Xk.X^{lC)+"M6@p:SV;<“}]'&K-lHgs@e3%6mZ?@\oj1iu[n5jD>bt/w :qV*-/O?lRܴk<Ӡxɜ2tԽvd"1;˛fܸqVq}c{X#1PX)H!jذ]?=z?pL0 IN8g\h[qm;* ]8qq x^8p_?o"Bs6ol[d §Ƞa2zў8I~o+5uo}HvJX?-w ;)~]MՍ N?o%,vB*9cd{D%^ukruXu]q,qDz}msQz-7EE!'*wm}кk>Fqpcu /Nz~WK¹dm*Xq8wSU)L2$[Xq3NwW6ͦq$:wno1!e֭퍎 79npX^r\xBFύ&ns?7bOo O\X ۶W.ts6] m""AcA U/fDC8Xhfp 7XJ\=(qqĝO"u׉XtpsP(! ։e"X"em۶)<&_)Pi3ABX0bR#{f*HJ\.;a#4X>G_ȚRxNڽq)mٜm띛+bdK͚5'Z9p L,+ǹvaik)lˋ'rm]BQӮ#)1 o.g(Ia }.\h}lܽ_[fͽ8ϓ/-[c$yx#(qzG?a&c63̟a13.IrnfC>VR$,M?"s`QN zqq-XHy9*@W@k۩|3c?P7VzsЎf:WC wbZIlӖpAO)QWy&l[|1xz:ev` \gqt(n=\8 9)~uXH1DU" 9V#9:F'Fn3I@NWRMn_yX2(t_<YuVܐ!W[0;nwc3/n Sbү%ʖwUC: HdΌ\/eͽOi~\\CpZa)PC@=FT0i9G@ NwZW|V#4L ~P=xOB"X'2j{G;Z'L Չ _`tӖCBư;:(c^H䍌w!ވfr)ɜ5T]DsUCĶp.C 8|N_}U{$q6[;;Og,zXi[8ϰŋ?I7@QOB{q0"@rzzzOwon"q^z+rFUmCK7;;9ְ~\!"7om*2Dj_1)( 9ዏe]_PfZ;s;7ol#:ƐlTyGKa>w&r 7EM ;1-bIMٶԍ(ŐO0i.'dr5'$u%u8VC' }"g„ jDX1P3r|F':H0V,>.—qtFOd|?d8=Yg:}[W$mnu>GdW嵾3d;KaҬuy`2}Aoi۱OdD#] aO ϮSx y$@ee%)̸ra8u=*I@:; Àb'o夆avNR`{}Fp:֠0+< :u X Cg^q?`L-Jn3Ħ0Khlзa3H7mIam.$ߚmF\@jɗaOiKh%,8`c8{-5ITR>KX9ܴ CrTqÙ@d:QYvw6AR9ɅY;ѵXwrO%2>`Ern|;ޘcon[$ktpqffҠq)KJߧ#>#uO[=G1ED J'

6HG`a恁[@ y!_j$ڞL+LEpn⻈,i1gox0si~D?f@. F [\'w*S~6g`CУ>j8r &@{<0?"Cb(2}Y~%5adxZ6&oc=q>h%~>US+tۨfBgGza=-XK5++/h+ngT@;n⦄{aX˱:BOu"j[',$?o;OuOeDw5 QzfS-$D'|8FX%ŦOd}tkedB*lwGy%ΐ*?7 G0#KAl XVD-4sGDRh'ɨf7ADE}:l8Yx(,ษx:pDֻ46k]vp`\&P3COgv% ≀FgeCsM#HdfP37.+4.*淳ݤJ#?}:C@>&9t萝TX?{'zO>6jzv#B,Dȑ#V:;r o6IB0qFg;瀾ǝFgǝajhؤI,|R!Z[7M2?3+_ O5qmH ( ?(S`z4YĽ{<}X$4 x+f߿}ݷHqUoWDoBM /Q֏5dW퉆v^;g.֖o.$#fТ@/['")HԭE (%]Kx=T`լ{ӗdgw?˔gq@yJ.5EҮsU_奁mo%gcw2e{#w6/gZ%%az|qvP@jet.twn? 6m\^qYh&{)[!}{ya@豧ˁ'eUp:Za иLau½׮{"۲rhg:ODFVZfrU 4iQN,\~:ctOdsɂo]zaq?ɣ=y\'~$}ސ XI5*)S\Z@ g(gΜ y\$^\$O{|TD1iw5y ۠^qT;! g|&ӤMV^@>y,eJ @t?w.I,I!kP!:u ͐rHJͤ74Y &%K|g;\h J#͝+wXw?,[-bnd}i~9r*Gh$*擴Rs׵o~$PF0S6 g~\5ۼYkYWȫV$'þ^fox*)S&g_lagIgn&ZFCD/5,].m>$۶2PL!\:SM z GҒŌT?;aR Y $VHO+_&\gXS>!"'^ II3eG28x:^j)#cWi-T-˪wɼk2] oZ@B'}[j3 %S*"cJ,@#07stR=V&(#Vf( 0t_Mc:)'\mnlB,3 #2(bxS4"O2<9ses~C|E%9 OM7Gu2dүfAs9_AqjFz~ԢC@Edt(:xtRe/HݻoKmϞ(rP]!wL6QRyI9\|T<{zo][D(|h@Λ@˜K/g̔:j J  4aZhΤ<7ڬ_KɬhUignjˇ$:xo ;vCXO7O/gLst>nUx:&eyv dWQ3[fț?H%a i7цI9~dΒC3\XZx aW#呌 Ξhx(pqJ9[Q>.ݺ>u+xzlY_QI?7HVћkʋh#9XL %?JsΝhsmt@ Resۄ8#Ե|ƲhĵS*U2kYD,/KZCjo@-ښ`b&%_mI|LS՟'s 'r .p$E.#9{ĠI]U (K^N0Μ]q.DH@EdY L1=H2` I9H ċyh)Lʖ[;eglR^r O{ȇ0}̸"pؠ6CѤѢ@`u6yS? f+MXuF[ **"px #WS~3cI°.G kSŬ&y9AHD+OmǶ#vI0/P( n*X8[|qL׆Mc_u0,`P4QD}A^P#\kZ7uk*j^hMB@Ed(s0S|^a6&sQ)T5&'uVE@ >vc3ɏ 7ɞG]r;eM2mGz;EL.F1Bo>;q%H63XU.jOJ@ *W+$W\}0p> #I+M/aHV4ADy [jUH$4Y29&vu_ aMCcIK>"=9q켝G>,ZW,<46tXt"}WJ@ >!v [,i"WL p}|3c*qt}4QZO&^ND 3:>}?u7F19O `Fho>?tM%@(PbGT=ݺ7o-c:24d u?9T Tb2pi=v aF?qא9Τɒ^=Kp t\1W^N]_0;sg/wv>c4fV$JN]+%!xӠijMbnfmvXXWB܅Tqu3w=]4"qwU#fڵU~mVT" AɌL4Lљ"MQJn4i3TԈ SinpA (%PL~sX{؎<؄ oӨdV9nHaE (%@(YChG` 2MU\ Z 7g`֩+%P >7۰nϐJ|4iYNFTBKjC (%H`TDmݾ  9%_L/wUƾbŊk׮hGUVY_J*6VRlGy_>-*!k%u#.&zm(%p%PB:uOrIYx-[6||?p535'S~TRwޑΎ͜96 8 ]v kԨ!w}*f:rwW3B~N?uV_yBN-V%w-笓og-Km37}JHeeqIɝ7gBڭܗulytjȠ<@)%C 4;,ժU[3]fM3nΞ=+?̚5K¤vڂ.[lWT)y 駟#\"O<=ZFZ<+Fiק?M6JV*Ν 2X##R+cƌ ץpg?{g}&&LKJeΝ裏ʤIdѢEv[~g9<޽['O.O>kVӓ5׵_"2 RoTRƿHzmP_Ef曃Nw&Z|3_IŤQ2?eB(.ސW 2e$ }ڋrE#"9%I!סC2eJsΝeڴiK>,[lܹsc=f‹Pܴi'?<ҪU+pҤI;TeSNCI:uCxRsРAv=|3cǎIZ )"_M-V~>?^Xk"˝~!*gڠ K(!Æ ~={ĉ_Ǐ ˾ө?>u8;>m+&fsKrރvIuٵoJ@ (%(0|`ĊXB\!,\Ǘ.];$ǫWSO=eP*Yd֭[[n={ܾ}|ג={v+X)W\ҥK+`Yں~ SOn"E guOJ>?c|3g3ugΜuܵiۨ%D>~ )Z<(;^z!cjiw5iծl|RIDQFt'x*%B6,˗/͛7[ b)X Dat I#GnJѢE;"z󡀉1cFoDJ;رcG+5jd&RNZ ||7E~ce act urUEd`\iӥC>`yK2pDʡJ׶͇liJBҰIiys_P 5"=X֭k1 X7_|g]7VX*-[aul=~truJDod%gΜc }rFFϙ2e}=z4ҠulEWKV1| ^;dO|mxgɔ%w2?/w}%3#9Wi߾}S-7}ݷo_֭[A$_Uܸq̝;׮E` 6s׭tA<>stXHԏ$!SIZWBlnKv?3pB{ofv\7~{ϒ2 ֢zd|/|Cq͐b%rZKsulx}|;t\TUDGE (%Bk%,u,znl.AP t:NӦM;s>}IrFV2=6lafrxEVb"4,gݽ?N!EkA2 {"T⭟xWM~!؟dx Aʔɤnu9Y.2| ĩX+%*tPJ@ $XBG@FUAĽ{oGDm{WvPFEK_I~GmLh d싼~e~PJJY} )% HH};b֮"2t]{ެUٻLWͨXW ;S&SU9][&|T\&*a`DM)%@ FDM@EdԌ]77Ζoۡyf^m:T?쑹ыt?PJ@ (PW!}Ig29'_&aP-sΟ$?VI"s2W(cPJ@ $\*"rϲfO'=VLE(mҤM!5<ݼmXܸ~ EΜ`#Ke%PJ vTDƎ[٪l|Ҡq)`ҧ_ x|#tFvًσ2ߤQes)!sJ9%S41^UJ@ (% Lԇηh[Q>&S\.>T'a ;/e>w6̊&oɵ(%P %p!hH|ޔ#O-eۖC2wtMp^x)`/ ش(%HhTD&#!@f.Ʋ,G «%NCsٳ0J @ƿ|\ (%ĄRxNw\VB^\gHeԜBN3?%f4J@ (%DE _ kvjuBڰx[]RX7o.+WV=Ǐ˰a$yJ7nܐ޽{GXu֕ Dۗ{'|"$ԩ0ĉ9`sm3] (%.ݩR^*~3عTz!+$kժ%cǎ wҥ ˗e͚5KaQFɉ'$M42eJٰaƍVՏ<͡XbFYH}ZPJ PKd91ӖwU2&gq?cO5ѶnCnݺم$]t{I=o'O+Wܲ1ܹs˲enYvu9wdȐe/^"懇~8C駟J=ȫ*˗IDk$uO'O}~QJ@ (&U?$"?mKfM]'6Y.];vDا~ c K&L GΝ; b`g+Ο?/vt̘15kVa8N:rY[4"hѢү_?v6$Qki!8}<:u`,UJ-J@ (%0 L5 {ujɉe԰u- mj#Qhh&RQIH-J@ (%|I n&_D I>Z_>D;yO3^xjWX;I2E (%? %ҟtIL!Sjc^աDru7PJ PO_>~otRؑߩTC (%@P(TֽJ\>x{ TJ@ (%K*"c N7BwH `ھPJ PPdBўw2$EPJTDgK?ڳdˑ^@.^TPJ@ (F@Ed;;?P,o + 7ZPJ@ (C@Ed9A'}L%I"_#ZPJ Prbb%sɀ!dO[e/cǎ*"Ο6ֹPJ 6'26t!() >kGPJ@ pJ@ (%2*"hwPJ@ (% TDQ>*%PJ  QJ@ (%@(P GIPJ@ ( #"2vG (%P@@Ed(%PJ@ (% ; %PJ@ pJ@ (%2*"hwPJ@ (% TDQ>*%PJ  QJ@ (%@(P GIPJ@ ( #"2vG (%P@@Ed(%PJ@ (% ; ڝ fyԙǏ˳>+/_U='O:HFdBbRz-YbEL6n\b]P!K@Ed:? lݺUƎ&7nzJ.-?S|7g|C=$OZjESTmٲEq-K\+%@ .i@~;wN2dpK'=*3fIese˖wIrJ>5J*ݺut{'=zyI>[W^o]$Ia;_ũ^v)ϟԩSmfGV G%)[>^+SO`׮]V3Ff*3g:uٳg-^X*U$X9 =ScB ]w%&L0TJ<ɟ?e CsR ҋx{d>6l]W(%@` ,om- >ZTb0aW|y9uꔵeW $۶miժUҩS'ax|ܸq^J[/E}F<}3gX;Xrt"3gδ R`͛w xk)%ɐ~ʔ)iӦ}uPJ@ ֖B@lX>3I,9r䐗^zɣ_>8u'NHbd6ֳgO᳧p'_Cg]o߷o*TYeJ~E_v-X#"/Q}!\F Yc޽ " S¤@;:jQJ@ C8 IDAT(Pxb$.^hE̴iӬυWT~z˗[pS2e$1TԃG9rD.\`/WXQDc|ǬE_~E֭.0#6PJ@ q!JS~}'5qV9i}\wm'c7k0\N$6ӦM>\Zml&X, }iҤшlѢ|6jժY+*wJ?(%@PPA;HGCP 9EVXQ7o~.uQ|ATrif(kRP_|fŸYϟo}ܵ[ ,YҊS"i }!ZtC U3d]Hˊ(pKXt(%@\:ujz8}t9y{iݾr׍@<5rO/M2w^)W8"!ԎOCDmct/X&>.^|H'_XKIp3}!$"V] QqKc KXuҸy٘l*%ߖmC;s˓={jyu#%#^Q$y &/oŽ>oE;)trvEG@/HexSAܻ ||((%@pPG{N9f>#\_}PJ 㧽Wq&/!C+ΕiJ@ (%hh`M9ԺJ@ (%c5)%PJ PhPJ@ (%|G@EXjMJ@ (%H4TD&C;PJ@ (P;ZPJ@ (% P*%PJwTD֤PJ@ (DC@Ed9ԺJ@ (%c5)%PJ PhPJ@ (%|G@EXjMJ@ (%H4TD&C;PJ@ (P;ZPJ@ (% P*%PJwZܙkZnN`2| )j%0g7xڕPAL "2ǩqv1NJ P=T^V^洝 &PxIZjP?L9DZl$"iYdJ@ (%IM;PJ@ (%"2~kJ@ (%I*"CiPJ@ (%TD/m] (%P!I@EdH6PJ@ (%◀寭+%PJ $ æVJ@ (%@Pu%PJ@ $!yشJ@ (%_*"㗿PJ@ ($"2$vZ (%PK@Ed֕PJ@ (%TDaN+%PJ ~ ܖ'Ok֬֕PJ@ (%Bc- IENDB`opentimelineio-0.18.1/src/deps/rapidjson/doc/tutorial.md0000664000175000017500000005315115110656147021067 0ustar meme# Tutorial This tutorial introduces the basics of the Document Object Model(DOM) API. As shown in [Usage at a glance](@ref index), JSON can be parsed into a DOM, and then the DOM can be queried and modified easily, and finally be converted back to JSON. [TOC] # Value & Document {#ValueDocument} Each JSON value is stored in a type called `Value`. A `Document`, representing the DOM, contains the root `Value` of the DOM tree. All public types and functions of RapidJSON are defined in the `rapidjson` namespace. # Query Value {#QueryValue} In this section, we will use excerpt from `example/tutorial/tutorial.cpp`. Assume we have the following JSON stored in a C string (`const char* json`): ~~~~~~~~~~js { "hello": "world", "t": true , "f": false, "n": null, "i": 123, "pi": 3.1416, "a": [1, 2, 3, 4] } ~~~~~~~~~~ Parse it into a `Document`: ~~~~~~~~~~cpp #include "rapidjson/document.h" using namespace rapidjson; // ... Document document; document.Parse(json); ~~~~~~~~~~ The JSON is now parsed into `document` as a *DOM tree*: ![DOM in the tutorial](diagram/tutorial.png) Since the update to RFC 7159, the root of a conforming JSON document can be any JSON value. In earlier RFC 4627, only objects or arrays were allowed as root values. In this case, the root is an object. ~~~~~~~~~~cpp assert(document.IsObject()); ~~~~~~~~~~ Let's query whether a `"hello"` member exists in the root object. Since a `Value` can contain different types of value, we may need to verify its type and use suitable API to obtain the value. In this example, `"hello"` member associates with a JSON string. ~~~~~~~~~~cpp assert(document.HasMember("hello")); assert(document["hello"].IsString()); printf("hello = %s\n", document["hello"].GetString()); ~~~~~~~~~~ ~~~~~~~~~~ hello = world ~~~~~~~~~~ JSON true/false values are represented as `bool`. ~~~~~~~~~~cpp assert(document["t"].IsBool()); printf("t = %s\n", document["t"].GetBool() ? "true" : "false"); ~~~~~~~~~~ ~~~~~~~~~~ t = true ~~~~~~~~~~ JSON null can be queried with `IsNull()`. ~~~~~~~~~~cpp printf("n = %s\n", document["n"].IsNull() ? "null" : "?"); ~~~~~~~~~~ ~~~~~~~~~~ n = null ~~~~~~~~~~ JSON number type represents all numeric values. However, C++ needs more specific type for manipulation. ~~~~~~~~~~cpp assert(document["i"].IsNumber()); // In this case, IsUint()/IsInt64()/IsUint64() also return true. assert(document["i"].IsInt()); printf("i = %d\n", document["i"].GetInt()); // Alternatively (int)document["i"] assert(document["pi"].IsNumber()); assert(document["pi"].IsDouble()); printf("pi = %g\n", document["pi"].GetDouble()); ~~~~~~~~~~ ~~~~~~~~~~ i = 123 pi = 3.1416 ~~~~~~~~~~ JSON array contains a number of elements. ~~~~~~~~~~cpp // Using a reference for consecutive access is handy and faster. const Value& a = document["a"]; assert(a.IsArray()); for (SizeType i = 0; i < a.Size(); i++) // Uses SizeType instead of size_t printf("a[%d] = %d\n", i, a[i].GetInt()); ~~~~~~~~~~ ~~~~~~~~~~ a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 4 ~~~~~~~~~~ Note that, RapidJSON does not automatically convert values between JSON types. For example, if a value is a string, it is invalid to call `GetInt()`. In debug mode it will fail on assertion. In release mode, the behavior is undefined. In the following sections we discuss details about querying individual types. ## Query Array {#QueryArray} By default, `SizeType` is typedef of `unsigned`. In most systems, an array is limited to store up to 2^32-1 elements. You may access the elements in an array by integer literal, for example, `a[0]`, `a[1]`, `a[2]`. Array is similar to `std::vector`: instead of using indices, you may also use iterator to access all the elements. ~~~~~~~~~~cpp for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) printf("%d ", itr->GetInt()); ~~~~~~~~~~ And other familiar query functions: * `SizeType Capacity() const` * `bool Empty() const` ### Range-based For Loop (New in v1.1.0) When C++11 is enabled, you can use range-based for loop to access all elements in an array. ~~~~~~~~~~cpp for (auto& v : a.GetArray()) printf("%d ", v.GetInt()); ~~~~~~~~~~ ## Query Object {#QueryObject} Similar to Array, we can access all object members by iterator: ~~~~~~~~~~cpp static const char* kTypeNames[] = { "Null", "False", "True", "Object", "Array", "String", "Number" }; for (Value::ConstMemberIterator itr = document.MemberBegin(); itr != document.MemberEnd(); ++itr) { printf("Type of member %s is %s\n", itr->name.GetString(), kTypeNames[itr->value.GetType()]); } ~~~~~~~~~~ ~~~~~~~~~~ Type of member hello is String Type of member t is True Type of member f is False Type of member n is Null Type of member i is Number Type of member pi is Number Type of member a is Array ~~~~~~~~~~ Note that, when `operator[](const char*)` cannot find the member, it will fail on assertion. If we are unsure whether a member exists, we need to call `HasMember()` before calling `operator[](const char*)`. However, this incurs two lookup. A better way is to call `FindMember()`, which can check the existence of a member and obtain its value at once: ~~~~~~~~~~cpp Value::ConstMemberIterator itr = document.FindMember("hello"); if (itr != document.MemberEnd()) printf("%s\n", itr->value.GetString()); ~~~~~~~~~~ ### Range-based For Loop (New in v1.1.0) When C++11 is enabled, you can use range-based for loop to access all members in an object. ~~~~~~~~~~cpp for (auto& m : document.GetObject()) printf("Type of member %s is %s\n", m.name.GetString(), kTypeNames[m.value.GetType()]); ~~~~~~~~~~ ## Querying Number {#QueryNumber} JSON provides a single numerical type called Number. Number can be an integer or a real number. RFC 4627 says the range of Number is specified by the parser implementation. As C++ provides several integer and floating point number types, the DOM tries to handle these with the widest possible range and good performance. When a Number is parsed, it is stored in the DOM as one of the following types: Type | Description -----------|--------------------------------------- `unsigned` | 32-bit unsigned integer `int` | 32-bit signed integer `uint64_t` | 64-bit unsigned integer `int64_t` | 64-bit signed integer `double` | 64-bit double precision floating point When querying a number, you can check whether the number can be obtained as the target type: Checking | Obtaining ------------------|--------------------- `bool IsNumber()` | N/A `bool IsUint()` | `unsigned GetUint()` `bool IsInt()` | `int GetInt()` `bool IsUint64()` | `uint64_t GetUint64()` `bool IsInt64()` | `int64_t GetInt64()` `bool IsDouble()` | `double GetDouble()` Note that, an integer value may be obtained in various ways without conversion. For example, A value `x` containing 123 will make `x.IsInt() == x.IsUint() == x.IsInt64() == x.IsUint64() == true`. But a value `y` containing -3000000000 will only make `x.IsInt64() == true`. When obtaining the numeric values, `GetDouble()` will convert internal integer representation to a `double`. Note that, `int` and `unsigned` can be safely converted to `double`, but `int64_t` and `uint64_t` may lose precision (since mantissa of `double` is only 52-bits). ## Query String {#QueryString} In addition to `GetString()`, the `Value` class also contains `GetStringLength()`. Here explains why: According to RFC 4627, JSON strings can contain Unicode character `U+0000`, which must be escaped as `"\u0000"`. The problem is that, C/C++ often uses null-terminated string, which treats `\0` as the terminator symbol. To conform with RFC 4627, RapidJSON supports string containing `U+0000` character. If you need to handle this, you can use `GetStringLength()` to obtain the correct string length. For example, after parsing the following JSON to `Document d`: ~~~~~~~~~~js { "s" : "a\u0000b" } ~~~~~~~~~~ The correct length of the string `"a\u0000b"` is 3, as returned by `GetStringLength()`. But `strlen()` returns 1. `GetStringLength()` can also improve performance, as user may often need to call `strlen()` for allocating buffer. Besides, `std::string` also support a constructor: ~~~~~~~~~~cpp string(const char* s, size_t count); ~~~~~~~~~~ which accepts the length of string as parameter. This constructor supports storing null character within the string, and should also provide better performance. ## Comparing values You can use `==` and `!=` to compare values. Two values are equal if and only if they have same type and contents. You can also compare values with primitive types. Here is an example: ~~~~~~~~~~cpp if (document["hello"] == document["n"]) /*...*/; // Compare values if (document["hello"] == "world") /*...*/; // Compare value with literal string if (document["i"] != 123) /*...*/; // Compare with integers if (document["pi"] != 3.14) /*...*/; // Compare with double. ~~~~~~~~~~ Array/object compares their elements/members in order. They are equal if and only if their whole subtrees are equal. Note that, currently if an object contains duplicated named member, comparing equality with any object is always `false`. # Create/Modify Values {#CreateModifyValues} There are several ways to create values. After a DOM tree is created and/or modified, it can be saved as JSON again using `Writer`. ## Change Value Type {#ChangeValueType} When creating a `Value` or `Document` by default constructor, its type is Null. To change its type, call `SetXXX()` or assignment operator, for example: ~~~~~~~~~~cpp Document d; // Null d.SetObject(); Value v; // Null v.SetInt(10); v = 10; // Shortcut, same as above ~~~~~~~~~~ ### Overloaded Constructors There are also overloaded constructors for several types: ~~~~~~~~~~cpp Value b(true); // calls Value(bool) Value i(-123); // calls Value(int) Value u(123u); // calls Value(unsigned) Value d(1.5); // calls Value(double) ~~~~~~~~~~ To create empty object or array, you may use `SetObject()`/`SetArray()` after default constructor, or using the `Value(Type)` in one call: ~~~~~~~~~~cpp Value o(kObjectType); Value a(kArrayType); ~~~~~~~~~~ ## Move Semantics {#MoveSemantics} A very special decision during design of RapidJSON is that, assignment of value does not copy the source value to destination value. Instead, the value from source is moved to the destination. For example, ~~~~~~~~~~cpp Value a(123); Value b(456); a = b; // b becomes a Null value, a becomes number 456. ~~~~~~~~~~ ![Assignment with move semantics.](diagram/move1.png) Why? What is the advantage of this semantics? The simple answer is performance. For fixed size JSON types (Number, True, False, Null), copying them is fast and easy. However, For variable size JSON types (String, Array, Object), copying them will incur a lot of overheads. And these overheads are often unnoticed. Especially when we need to create temporary object, copy it to another variable, and then destruct it. For example, if normal *copy* semantics was used: ~~~~~~~~~~cpp Document d; Value o(kObjectType); { Value contacts(kArrayType); // adding elements to contacts array. // ... o.AddMember("contacts", contacts, d.GetAllocator()); // deep clone contacts (may be with lots of allocations) // destruct contacts. } ~~~~~~~~~~ ![Copy semantics makes a lots of copy operations.](diagram/move2.png) The object `o` needs to allocate a buffer of same size as contacts, makes a deep clone of it, and then finally contacts is destructed. This will incur a lot of unnecessary allocations/deallocations and memory copying. There are solutions to prevent actual copying these data, such as reference counting and garbage collection(GC). To make RapidJSON simple and fast, we chose to use *move* semantics for assignment. It is similar to `std::auto_ptr` which transfer ownership during assignment. Move is much faster and simpler, it just destructs the original value, `memcpy()` the source to destination, and finally sets the source as Null type. So, with move semantics, the above example becomes: ~~~~~~~~~~cpp Document d; Value o(kObjectType); { Value contacts(kArrayType); // adding elements to contacts array. o.AddMember("contacts", contacts, d.GetAllocator()); // just memcpy() of contacts itself to the value of new member (16 bytes) // contacts became Null here. Its destruction is trivial. } ~~~~~~~~~~ ![Move semantics makes no copying.](diagram/move3.png) This is called move assignment operator in C++11. As RapidJSON supports C++03, it adopts move semantics using assignment operator, and all other modifying function like `AddMember()`, `PushBack()`. ### Move semantics and temporary values {#TemporaryValues} Sometimes, it is convenient to construct a Value in place, before passing it to one of the "moving" functions, like `PushBack()` or `AddMember()`. As temporary objects can't be converted to proper Value references, the convenience function `Move()` is available: ~~~~~~~~~~cpp Value a(kArrayType); Document::AllocatorType& allocator = document.GetAllocator(); // a.PushBack(Value(42), allocator); // will not compile a.PushBack(Value().SetInt(42), allocator); // fluent API a.PushBack(Value(42).Move(), allocator); // same as above ~~~~~~~~~~ ## Create String {#CreateString} RapidJSON provides two strategies for storing string. 1. copy-string: allocates a buffer, and then copy the source data into it. 2. const-string: simply store a pointer of string. Copy-string is always safe because it owns a copy of the data. Const-string can be used for storing a string literal, and for in-situ parsing which will be mentioned in the DOM section. To make memory allocation customizable, RapidJSON requires users to pass an instance of allocator, whenever an operation may require allocation. This design is needed to prevent storing an allocator (or Document) pointer per Value. Therefore, when we assign a copy-string, we call this overloaded `SetString()` with allocator: ~~~~~~~~~~cpp Document document; Value author; char buffer[10]; int len = sprintf(buffer, "%s %s", "Milo", "Yip"); // dynamically created string. author.SetString(buffer, len, document.GetAllocator()); memset(buffer, 0, sizeof(buffer)); // author.GetString() still contains "Milo Yip" after buffer is destroyed ~~~~~~~~~~ In this example, we get the allocator from a `Document` instance. This is a common idiom when using RapidJSON. But you may use other instances of allocator. Besides, the above `SetString()` requires length. This can handle null characters within a string. There is another `SetString()` overloaded function without the length parameter. And it assumes the input is null-terminated and calls a `strlen()`-like function to obtain the length. Finally, for a string literal or string with a safe life-cycle one can use the const-string version of `SetString()`, which lacks an allocator parameter. For string literals (or constant character arrays), simply passing the literal as parameter is safe and efficient: ~~~~~~~~~~cpp Value s; s.SetString("rapidjson"); // can contain null character, length derived at compile time s = "rapidjson"; // shortcut, same as above ~~~~~~~~~~ For a character pointer, RapidJSON requires it to be marked as safe before using it without copying. This can be achieved by using the `StringRef` function: ~~~~~~~~~cpp const char * cstr = getenv("USER"); size_t cstr_len = ...; // in case length is available Value s; // s.SetString(cstr); // will not compile s.SetString(StringRef(cstr)); // ok, assume safe lifetime, null-terminated s = StringRef(cstr); // shortcut, same as above s.SetString(StringRef(cstr,cstr_len)); // faster, can contain null character s = StringRef(cstr,cstr_len); // shortcut, same as above ~~~~~~~~~ ## Modify Array {#ModifyArray} Value with array type provides an API similar to `std::vector`. * `Clear()` * `Reserve(SizeType, Allocator&)` * `Value& PushBack(Value&, Allocator&)` * `template GenericValue& PushBack(T, Allocator&)` * `Value& PopBack()` * `ValueIterator Erase(ConstValueIterator pos)` * `ValueIterator Erase(ConstValueIterator first, ConstValueIterator last)` Note that, `Reserve(...)` and `PushBack(...)` may allocate memory for the array elements, therefore requiring an allocator. Here is an example of `PushBack()`: ~~~~~~~~~~cpp Value a(kArrayType); Document::AllocatorType& allocator = document.GetAllocator(); for (int i = 5; i <= 10; i++) a.PushBack(i, allocator); // allocator is needed for potential realloc(). // Fluent interface a.PushBack("Lua", allocator).PushBack("Mio", allocator); ~~~~~~~~~~ This API differs from STL in that `PushBack()`/`PopBack()` return the array reference itself. This is called _fluent interface_. If you want to add a non-constant string or a string without sufficient lifetime (see [Create String](#CreateString)) to the array, you need to create a string Value by using the copy-string API. To avoid the need for an intermediate variable, you can use a [temporary value](#TemporaryValues) in place: ~~~~~~~~~~cpp // in-place Value parameter contact.PushBack(Value("copy", document.GetAllocator()).Move(), // copy string document.GetAllocator()); // explicit parameters Value val("key", document.GetAllocator()); // copy string contact.PushBack(val, document.GetAllocator()); ~~~~~~~~~~ ## Modify Object {#ModifyObject} The Object class is a collection of key-value pairs (members). Each key must be a string value. To modify an object, either add or remove members. The following API is for adding members: * `Value& AddMember(Value&, Value&, Allocator& allocator)` * `Value& AddMember(StringRefType, Value&, Allocator&)` * `template Value& AddMember(StringRefType, T value, Allocator&)` Here is an example. ~~~~~~~~~~cpp Value contact(kObject); contact.AddMember("name", "Milo", document.GetAllocator()); contact.AddMember("married", true, document.GetAllocator()); ~~~~~~~~~~ The name parameter with `StringRefType` is similar to the interface of the `SetString` function for string values. These overloads are used to avoid the need for copying the `name` string, since constant key names are very common in JSON objects. If you need to create a name from a non-constant string or a string without sufficient lifetime (see [Create String](#CreateString)), you need to create a string Value by using the copy-string API. To avoid the need for an intermediate variable, you can use a [temporary value](#TemporaryValues) in place: ~~~~~~~~~~cpp // in-place Value parameter contact.AddMember(Value("copy", document.GetAllocator()).Move(), // copy string Value().Move(), // null value document.GetAllocator()); // explicit parameters Value key("key", document.GetAllocator()); // copy string name Value val(42); // some value contact.AddMember(key, val, document.GetAllocator()); ~~~~~~~~~~ For removing members, there are several choices: * `bool RemoveMember(const Ch* name)`: Remove a member by search its name (linear time complexity). * `bool RemoveMember(const Value& name)`: same as above but `name` is a Value. * `MemberIterator RemoveMember(MemberIterator)`: Remove a member by iterator (_constant_ time complexity). * `MemberIterator EraseMember(MemberIterator)`: similar to the above but it preserves order of members (linear time complexity). * `MemberIterator EraseMember(MemberIterator first, MemberIterator last)`: remove a range of members, preserves order (linear time complexity). `MemberIterator RemoveMember(MemberIterator)` uses a "move-last" trick to achieve constant time complexity. Basically the member at iterator is destructed, and then the last element is moved to that position. So the order of the remaining members are changed. ## Deep Copy Value {#DeepCopyValue} If we really need to copy a DOM tree, we can use two APIs for deep copy: constructor with allocator, and `CopyFrom()`. ~~~~~~~~~~cpp Document d; Document::AllocatorType& a = d.GetAllocator(); Value v1("foo"); // Value v2(v1); // not allowed Value v2(v1, a); // make a copy assert(v1.IsString()); // v1 untouched d.SetArray().PushBack(v1, a).PushBack(v2, a); assert(v1.IsNull() && v2.IsNull()); // both moved to d v2.CopyFrom(d, a); // copy whole document to v2 assert(d.IsArray() && d.Size() == 2); // d untouched v1.SetObject().AddMember("array", v2, a); d.PushBack(v1, a); ~~~~~~~~~~ ## Swap Values {#SwapValues} `Swap()` is also provided. ~~~~~~~~~~cpp Value a(123); Value b("Hello"); a.Swap(b); assert(a.IsString()); assert(b.IsInt()); ~~~~~~~~~~ Swapping two DOM trees is fast (constant time), despite the complexity of the trees. # What's next {#WhatsNext} This tutorial shows the basics of DOM tree query and manipulation. There are several important concepts in RapidJSON: 1. [Streams](doc/stream.md) are channels for reading/writing JSON, which can be a in-memory string, or file stream, etc. User can also create their streams. 2. [Encoding](doc/encoding.md) defines which character encoding is used in streams and memory. RapidJSON also provide Unicode conversion/validation internally. 3. [DOM](doc/dom.md)'s basics are already covered in this tutorial. Uncover more advanced features such as *in situ* parsing, other parsing options and advanced usages. 4. [SAX](doc/sax.md) is the foundation of parsing/generating facility in RapidJSON. Learn how to use `Reader`/`Writer` to implement even faster applications. Also try `PrettyWriter` to format the JSON. 5. [Performance](doc/performance.md) shows some in-house and third-party benchmarks. 6. [Internals](doc/internals.md) describes some internal designs and techniques of RapidJSON. You may also refer to the [FAQ](doc/faq.md), API documentation, examples and unit tests. opentimelineio-0.18.1/src/deps/rapidjson/doc/encoding.zh-cn.md0000664000175000017500000001531415110656147022027 0ustar meme# 编码 根据 [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf): > (in Introduction) JSON text is a sequence of Unicode code points. > > 翻译:JSON 文本是 Unicode 码点的序列。 较早的 [RFC4627](http://www.ietf.org/rfc/rfc4627.txt) 申明: > (in §3) JSON text SHALL be encoded in Unicode. The default encoding is UTF-8. > > 翻译:JSON 文本应该以 Unicode 编码。缺省的编码为 UTF-8。 > (in §6) JSON may be represented using UTF-8, UTF-16, or UTF-32. When JSON is written in UTF-8, JSON is 8bit compatible. When JSON is written in UTF-16 or UTF-32, the binary content-transfer-encoding must be used. > > 翻译:JSON 可使用 UTF-8、UTF-16 或 UTF-32 表示。当 JSON 以 UTF-8 写入,该 JSON 是 8 位兼容的。当 JSON 以 UTF-16 或 UTF-32 写入,就必须使用二进制的内容传送编码。 RapidJSON 支持多种编码。它也能检查 JSON 的编码,以及在不同编码中进行转码。所有这些功能都是在内部实现,无需使用外部的程序库(如 [ICU](http://site.icu-project.org/))。 [TOC] # Unicode {#Unicode} 根据 [Unicode 的官方网站](http://www.unicode.org/standard/translations/t-chinese.html): >Unicode 给每个字符提供了一个唯一的数字, 不论是什么平台、 不论是什么程序、 不论是什么语言。 这些唯一数字称为码点(code point),其范围介乎 `0x0` 至 `0x10FFFF` 之间。 ## Unicode 转换格式 {#UTF} 存储 Unicode 码点有多种编码方式。这些称为 Unicode 转换格式(Unicode Transformation Format, UTF)。RapidJSON 支持最常用的 UTF,包括: * UTF-8:8 位可变长度编码。它把一个码点映射至 1 至 4 个字节。 * UTF-16:16 位可变长度编码。它把一个码点映射至 1 至 2 个 16 位编码单元(即 2 至 4 个字节)。 * UTF-32:32 位固定长度编码。它直接把码点映射至单个 32 位编码单元(即 4 字节)。 对于 UTF-16 及 UTF-32 来说,字节序(endianness)是有影响的。在内存中,它们通常都是以该计算机的字节序来存储。然而,当要储存在文件中或在网上传输,我们需要指明字节序列的字节序,是小端(little endian, LE)还是大端(big-endian, BE)。 RapidJSON 通过 `rapidjson/encodings.h` 中的 struct 去提供各种编码: ~~~~~~~~~~cpp namespace rapidjson { template struct UTF8; template struct UTF16; template struct UTF16LE; template struct UTF16BE; template struct UTF32; template struct UTF32LE; template struct UTF32BE; } // namespace rapidjson ~~~~~~~~~~ 对于在内存中的文本,我们正常会使用 `UTF8`、`UTF16` 或 `UTF32`。对于处理经过 I/O 的文本,我们可使用 `UTF8`、`UTF16LE`、`UTF16BE`、`UTF32LE` 或 `UTF32BE`。 当使用 DOM 风格的 API,`GenericValue` 及 `GenericDocument` 里的 `Encoding` 模板参数是用于指明内存中存储的 JSON 字符串使用哪种编码。因此通常我们会在此参数中使用 `UTF8`、`UTF16` 或 `UTF32`。如何选择,视乎应用软件所使用的操作系统及其他程序库。例如,Windows API 使用 UTF-16 表示 Unicode 字符,而多数的 Linux 发行版本及应用软件则更喜欢 UTF-8。 使用 UTF-16 的 DOM 声明例子: ~~~~~~~~~~cpp typedef GenericDocument > WDocument; typedef GenericValue > WValue; ~~~~~~~~~~ 可以在 [DOM's Encoding](doc/stream.zh-cn.md) 一节看到更详细的使用例子。 ## 字符类型 {#CharacterType} 从之前的声明中可以看到,每个编码都有一个 `CharType` 模板参数。这可能比较容易混淆,实际上,每个 `CharType` 存储一个编码单元,而不是一个字符(码点)。如之前所谈及,在 UTF-8 中一个码点可能会编码成 1 至 4 个编码单元。 对于 `UTF16(LE|BE)` 及 `UTF32(LE|BE)` 来说,`CharType` 必须分别是一个至少 2 及 4 字节的整数类型。 注意 C++11 新添了 `char16_t` 及 `char32_t` 类型,也可分别用于 `UTF16` 及 `UTF32`。 ## AutoUTF {#AutoUTF} 上述所介绍的编码都是在编译期静态挷定的。换句话说,使用者必须知道内存或流之中使用了哪种编码。然而,有时候我们可能需要读写不同编码的文件,而且这些编码需要在运行时才能决定。 `AutoUTF` 是为此而设计的编码。它根据输入或输出流来选择使用哪种编码。目前它应该与 `EncodedInputStream` 及 `EncodedOutputStream` 结合使用。 ## ASCII {#ASCII} 虽然 JSON 标准并未提及 [ASCII](http://en.wikipedia.org/wiki/ASCII),有时候我们希望写入 7 位的 ASCII JSON,以供未能处理 UTF-8 的应用程序使用。由于任 JSON 都可以把 Unicode 字符表示为 `\uXXXX` 转义序列,JSON 总是可用 ASCII 来编码。 以下的例子把 UTF-8 的 DOM 写成 ASCII 的 JSON: ~~~~~~~~~~cpp using namespace rapidjson; Document d; // UTF8<> // ... StringBuffer buffer; Writer > writer(buffer); d.Accept(writer); std::cout << buffer.GetString(); ~~~~~~~~~~ ASCII 可用于输入流。当输入流包含大于 127 的字节,就会导致 `kParseErrorStringInvalidEncoding` 错误。 ASCII * 不能 * 用于内存(`Document` 的编码,或 `Reader` 的目标编码),因为它不能表示 Unicode 码点。 # 校验及转码 {#ValidationTranscoding} 当 RapidJSON 解析一个 JSON 时,它能校验输入 JSON,判断它是否所标明编码的合法序列。要开启此选项,请把 `kParseValidateEncodingFlag` 加入 `parseFlags` 模板参数。 若输入编码和输出编码并不相同,`Reader` 及 `Writer` 会算把文本转码。在这种情况下,并不需要 `kParseValidateEncodingFlag`,因为它必须解码输入序列。若序列不能被解码,它必然是不合法的。 ## 转码器 {#Transcoder} 虽然 RapidJSON 的编码功能是为 JSON 解析/生成而设计,使用者也可以“滥用”它们来为非 JSON 字符串转码。 以下的例子把 UTF-8 字符串转码成 UTF-16: ~~~~~~~~~~cpp #include "rapidjson/encodings.h" using namespace rapidjson; const char* s = "..."; // UTF-8 string StringStream source(s); GenericStringBuffer > target; bool hasError = false; while (source.Peek() != '\0') if (!Transcoder, UTF16<> >::Transcode(source, target)) { hasError = true; break; } if (!hasError) { const wchar_t* t = target.GetString(); // ... } ~~~~~~~~~~ 你也可以用 `AutoUTF` 及对应的流来在运行时设置内源/目的之编码。 opentimelineio-0.18.1/src/deps/rapidjson/doc/pointer.zh-cn.md0000664000175000017500000002052415110656147021720 0ustar meme# Pointer (本功能于 v1.1.0 发布) JSON Pointer 是一个标准化([RFC6901])的方式去选取一个 JSON Document(DOM)中的值。这类似于 XML 的 XPath。然而,JSON Pointer 简单得多,而且每个 JSON Pointer 仅指向单个值。 使用 RapidJSON 的 JSON Pointer 实现能简化一些 DOM 的操作。 [TOC] # JSON Pointer {#JsonPointer} 一个 JSON Pointer 由一串(零至多个)token 所组成,每个 token 都有 `/` 前缀。每个 token 可以是一个字符串或数字。例如,给定一个 JSON: ~~~javascript { "foo" : ["bar", "baz"], "pi" : 3.1416 } ~~~ 以下的 JSON Pointer 解析为: 1. `"/foo"` → `[ "bar", "baz" ]` 2. `"/foo/0"` → `"bar"` 3. `"/foo/1"` → `"baz"` 4. `"/pi"` → `3.1416` 要注意,一个空 JSON Pointer `""` (零个 token)解析为整个 JSON。 # 基本使用方法 {#BasicUsage} 以下的代码范例不解自明。 ~~~cpp #include "rapidjson/pointer.h" // ... Document d; // 使用 Set() 创建 DOM Pointer("/project").Set(d, "RapidJSON"); Pointer("/stars").Set(d, 10); // { "project" : "RapidJSON", "stars" : 10 } // 使用 Get() 访问 DOM。若该值不存在则返回 nullptr。 if (Value* stars = Pointer("/stars").Get(d)) stars->SetInt(stars->GetInt() + 1); // { "project" : "RapidJSON", "stars" : 11 } // Set() 和 Create() 自动生成父值(如果它们不存在)。 Pointer("/a/b/0").Create(d); // { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] } } // GetWithDefault() 返回引用。若该值不存在则会深拷贝缺省值。 Value& hello = Pointer("/hello").GetWithDefault(d, "world"); // { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] }, "hello" : "world" } // Swap() 和 Set() 相似 Value x("C++"); Pointer("/hello").Swap(d, x); // { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] }, "hello" : "C++" } // x 变成 "world" // 删去一个成员或元素,若值存在返回 true bool success = Pointer("/a").Erase(d); assert(success); // { "project" : "RapidJSON", "stars" : 10 } ~~~ # 辅助函数 {#HelperFunctions} 由于面向对象的调用习惯可能不符直觉,RapidJSON 也提供了一些辅助函数,它们把成员函数包装成自由函数。 以下的例子与上面例子所做的事情完全相同。 ~~~cpp Document d; SetValueByPointer(d, "/project", "RapidJSON"); SetValueByPointer(d, "/stars", 10); if (Value* stars = GetValueByPointer(d, "/stars")) stars->SetInt(stars->GetInt() + 1); CreateValueByPointer(d, "/a/b/0"); Value& hello = GetValueByPointerWithDefault(d, "/hello", "world"); Value x("C++"); SwapValueByPointer(d, "/hello", x); bool success = EraseValueByPointer(d, "/a"); assert(success); ~~~ 以下对比 3 种调用方式: 1. `Pointer(source).(root, ...)` 2. `ValueByPointer(root, Pointer(source), ...)` 3. `ValueByPointer(root, source, ...)` # 解析 Pointer {#ResolvingPointer} `Pointer::Get()` 或 `GetValueByPointer()` 函数并不修改 DOM。若那些 token 不能匹配 DOM 里的值,这些函数便返回 `nullptr`。使用者可利用这个方法来检查一个值是否存在。 注意,数值 token 可表示数组索引或成员名字。解析过程中会按值的类型来匹配。 ~~~javascript { "0" : 123, "1" : [456] } ~~~ 1. `"/0"` → `123` 2. `"/1/0"` → `456` Token `"0"` 在第一个 pointer 中被当作成员名字。它在第二个 pointer 中被当作成数组索引。 其他函数会改变 DOM,包括 `Create()`、`GetWithDefault()`、`Set()`、`Swap()`。这些函数总是成功的。若一些父值不存在,就会创建它们。若父值类型不匹配 token,也会强行改变其类型。改变类型也意味着完全移除其 DOM 子树的内容。 例如,把上面的 JSON 解译至 `d` 之后, ~~~cpp SetValueByPointer(d, "1/a", 789); // { "0" : 123, "1" : { "a" : 789 } } ~~~ ## 解析负号 token 另外,[RFC6901] 定义了一个特殊 token `-` (单个负号),用于表示数组最后元素的下一个元素。 `Get()` 只会把此 token 当作成员名字 '"-"'。而其他函数则会以此解析数组,等同于对数组调用 `Value::PushBack()` 。 ~~~cpp Document d; d.Parse("{\"foo\":[123]}"); SetValueByPointer(d, "/foo/-", 456); // { "foo" : [123, 456] } SetValueByPointer(d, "/-", 789); // { "foo" : [123, 456], "-" : 789 } ~~~ ## 解析 Document 及 Value 当使用 `p.Get(root)` 或 `GetValueByPointer(root, p)`,`root` 是一个(常数) `Value&`。这意味着,它也可以是 DOM 里的一个子树。 其他函数有两组签名。一组使用 `Document& document` 作为参数,另一组使用 `Value& root`。第一组使用 `document.GetAllocator()` 去创建值,而第二组则需要使用者提供一个 allocator,如同 DOM 里的函数。 以上例子都不需要 allocator 参数,因为它的第一个参数是 `Document&`。但如果你需要对一个子树进行解析,就需要如下面的例子般提供 allocator: ~~~cpp class Person { public: Person() { document_ = new Document(); // CreateValueByPointer() here no need allocator SetLocation(CreateValueByPointer(*document_, "/residence"), ...); SetLocation(CreateValueByPointer(*document_, "/office"), ...); }; private: void SetLocation(Value& location, const char* country, const char* addresses[2]) { Value::Allocator& a = document_->GetAllocator(); // SetValueByPointer() here need allocator SetValueByPointer(location, "/country", country, a); SetValueByPointer(location, "/address/0", address[0], a); SetValueByPointer(location, "/address/1", address[1], a); } // ... Document* document_; }; ~~~ `Erase()` 或 `EraseValueByPointer()` 不需要 allocator。而且它们成功删除值之后会返回 `true`。 # 错误处理 {#ErrorHandling} `Pointer` 在其建构函数里会解译源字符串。若有解析错误,`Pointer::IsValid()` 返回 `false`。你可使用 `Pointer::GetParseErrorCode()` 和 `GetParseErrorOffset()` 去获取错信息。 要注意的是,所有解析函数都假设 pointer 是合法的。对一个非法 pointer 解析会造成断言失败。 # URI 片段表示方式 {#URIFragment} 除了我们一直在使用的字符串方式表示 JSON pointer,[RFC6901] 也定义了一个 JSON Pointer 的 URI 片段(fragment)表示方式。URI 片段是定义于 [RFC3986] "Uniform Resource Identifier (URI): Generic Syntax"。 URI 片段的主要分别是必然以 `#` (pound sign)开头,而一些字符也会以百分比编码成 UTF-8 序列。例如,以下的表展示了不同表示法下的 C/C++ 字符串常数。 字符串表示方式 | URI 片段表示方式 | Pointer Tokens (UTF-8) ----------------------|-----------------------------|------------------------ `"/foo/0"` | `"#/foo/0"` | `{"foo", 0}` `"/a~1b"` | `"#/a~1b"` | `{"a/b"}` `"/m~0n"` | `"#/m~0n"` | `{"m~n"}` `"/ "` | `"#/%20"` | `{" "}` `"/\0"` | `"#/%00"` | `{"\0"}` `"/€"` | `"#/%E2%82%AC"` | `{"€"}` RapidJSON 完全支持 URI 片段表示方式。它在解译时会自动检测 `#` 号。 # 字符串化 你也可以把一个 `Pointer` 字符串化,储存于字符串或其他输出流。例如: ~~~ Pointer p(...); StringBuffer sb; p.Stringify(sb); std::cout << sb.GetString() << std::endl; ~~~ 使用 `StringifyUriFragment()` 可以把 pointer 字符串化为 URI 片段表示法。 # 使用者提供的 tokens {#UserSuppliedTokens} 若一个 pointer 会用于多次解析,它应该只被创建一次,然后再施于不同的 DOM ,或在不同时间做解析。这样可以避免多次创键 `Pointer`,节省时间和内存分配。 我们甚至可以再更进一步,完全消去解析过程及动态内存分配。我们可以直接生成 token 数组: ~~~cpp #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } #define INDEX(i) { #i, sizeof(#i) - 1, i } static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); // Equivalent to static const Pointer p("/foo/123"); ~~~ 这种做法可能适合内存受限的系统。 [RFC3986]: https://tools.ietf.org/html/rfc3986 [RFC6901]: https://tools.ietf.org/html/rfc6901 opentimelineio-0.18.1/src/deps/rapidjson/doc/Doxyfile.zh-cn.in0000664000175000017500000031206615110656146022035 0ustar meme# Doxyfile 1.8.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single 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. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = RapidJSON # 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 = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "一个C++快速JSON解析器及生成器,包含SAX/DOM风格API" # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. 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 = @CMAKE_CURRENT_BINARY_DIR@ # 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 causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = 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. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = Chinese # If the BRIEF_MEMBER_DESC tag is set to YES 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. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES 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. # The default value is: YES. 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 and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # 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. # The default value is: NO. 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. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES 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 # The default value is: YES. FULL_PATH_NAMES = YES # 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. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # 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 list of 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. # The default value is: NO. 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-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # 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 Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # 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 behavior. 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 behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. 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. # The default value is: NO. 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. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act 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 = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # 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. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. 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. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # 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); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) 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. # The default value is: NO. 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 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. # The default value is: YES. 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. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES 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. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = YES # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag 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. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_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 respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # 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. Does not have any effect # for Java sources. # The default value is: YES. 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 only methods in the interface are # included. # The default value is: NO. 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. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO 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. # The default value is: NO. 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 these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. 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 these declarations will be # included in the documentation. # The default value is: NO. 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 these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. 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 then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. 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. # The default value is: system dependent. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES 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. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # 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 group names will # appear in their defined order. # The default value is: NO. 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 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. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = 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. # The default value is: YES. 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. # The default value is: YES. 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. # The default value is: YES. 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. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = $(RAPIDJSON_SECTIONS) # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have 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 value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. 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. # The default value is: YES. SHOW_USED_FILES = YES # 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 value 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 value is: YES. SHOW_NAMESPACES = NO # 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 command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. 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. To 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. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. Do not use file names with spaces, bibtex cannot handle them. See # also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag 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. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag 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. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. 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) # The default value is: $file:$line: $text. 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 standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is 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. # Note: If this tag is empty the current directory is searched. INPUT = readme.zh-cn.md \ CHANGELOG.md \ include/rapidjson/rapidjson.h \ include/ \ doc/features.zh-cn.md \ doc/tutorial.zh-cn.md \ doc/pointer.zh-cn.md \ doc/stream.zh-cn.md \ doc/encoding.zh-cn.md \ doc/dom.zh-cn.md \ doc/sax.zh-cn.md \ doc/schema.zh-cn.md \ doc/performance.zh-cn.md \ doc/internals.zh-cn.md \ doc/faq.zh-cn.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. 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 patterns (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, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.h \ *.hh \ *.hxx \ *.hpp \ *.inc \ *.md # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # 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. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = ./include/rapidjson/msinttypes/ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. 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 # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = internal # 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. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = ./doc # 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. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. 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 information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = readme.zh-cn.md #--------------------------------------------------------------------------- # 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 that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. 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. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES 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 documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = 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. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES 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. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the # clang parser (see: http://clang.llvm.org/) for more accurate parsing at the # cost of reduced performance. This can be particularly helpful with template # rich C++ code for which doxygen's built-in parser lacks the necessary type # information. # Note: The availability of this option depends on whether or not doxygen was # compiled with the --with-libclang option. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = NO # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. 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 a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. 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. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html/zh-cn # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = ./doc/misc/header.html # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = ./doc/misc/footer.html # 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 left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- # defined cascading style sheet that is included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet file to the output directory. For an example # see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = ./doc/misc/doxygenextra.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = 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. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # 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 (see: http://developer.apple.com/tools/xcode/), 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. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset 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. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # 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. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # 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. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # 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. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # 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). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # 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. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. 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. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. 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. # This tag requires that the tag GENERATE_QHP is set to YES. 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 # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. 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 (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # 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. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = YES # 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 YES, 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 # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # 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. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. 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. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://www.mathjax.org/mathjax # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , / IgnoreResult(const A& an_action) { return internal::IgnoreResultAction(an_action); } // Creates a reference wrapper for the given L-value. If necessary, // you can explicitly specify the type of the reference. For example, // suppose 'derived' is an object of type Derived, ByRef(derived) // would wrap a Derived&. If you want to wrap a const Base& instead, // where Base is a base class of Derived, just write: // // ByRef(derived) template inline internal::ReferenceWrapper ByRef(T& l_value) { // NOLINT return internal::ReferenceWrapper(l_value); } } // namespace testing #endif // GMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ ././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-generated-matchers.h.pumpopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-generated-m0000664000175000017500000005264115110656150031534 0ustar meme$$ -*- mode: c++; -*- $$ This is a Pump source file. Please use Pump to convert $$ it to gmock-generated-matchers.h. $$ $var n = 10 $$ The maximum arity we support. $$ }} This line fixes auto-indentation of the following code in Emacs. // Copyright 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // Google Mock - a framework for writing C++ mock classes. // // This file implements some commonly used variadic matchers. #ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ #define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ #include #include #include #include #include "gmock/gmock-matchers.h" namespace testing { namespace internal { $range i 0..n-1 // The type of the i-th (0-based) field of Tuple. #define GMOCK_FIELD_TYPE_(Tuple, i) \ typename ::testing::tuple_element::type // TupleFields is for selecting fields from a // tuple of type Tuple. It has two members: // // type: a tuple type whose i-th field is the ki-th field of Tuple. // GetSelectedFields(t): returns fields k0, ..., and kn of t as a tuple. // // For example, in class TupleFields, 2, 0>, we have: // // type is tuple, and // GetSelectedFields(make_tuple(true, 'a', 42)) is (42, true). template class TupleFields; // This generic version is used when there are $n selectors. template class TupleFields { public: typedef ::testing::tuple<$for i, [[GMOCK_FIELD_TYPE_(Tuple, k$i)]]> type; static type GetSelectedFields(const Tuple& t) { return type($for i, [[get(t)]]); } }; // The following specialization is used for 0 ~ $(n-1) selectors. $for i [[ $$ }}} $range j 0..i-1 $range k 0..n-1 template class TupleFields { public: typedef ::testing::tuple<$for j, [[GMOCK_FIELD_TYPE_(Tuple, k$j)]]> type; static type GetSelectedFields(const Tuple& $if i==0 [[/* t */]] $else [[t]]) { return type($for j, [[get(t)]]); } }; ]] #undef GMOCK_FIELD_TYPE_ // Implements the Args() matcher. $var ks = [[$for i, [[k$i]]]] template class ArgsMatcherImpl : public MatcherInterface { public: // ArgsTuple may have top-level const or reference modifiers. typedef GTEST_REMOVE_REFERENCE_AND_CONST_(ArgsTuple) RawArgsTuple; typedef typename internal::TupleFields::type SelectedArgs; typedef Matcher MonomorphicInnerMatcher; template explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) : inner_matcher_(SafeMatcherCast(inner_matcher)) {} virtual bool MatchAndExplain(ArgsTuple args, MatchResultListener* listener) const { const SelectedArgs& selected_args = GetSelectedArgs(args); if (!listener->IsInterested()) return inner_matcher_.Matches(selected_args); PrintIndices(listener->stream()); *listener << "are " << PrintToString(selected_args); StringMatchResultListener inner_listener; const bool match = inner_matcher_.MatchAndExplain(selected_args, &inner_listener); PrintIfNotEmpty(inner_listener.str(), listener->stream()); return match; } virtual void DescribeTo(::std::ostream* os) const { *os << "are a tuple "; PrintIndices(os); inner_matcher_.DescribeTo(os); } virtual void DescribeNegationTo(::std::ostream* os) const { *os << "are a tuple "; PrintIndices(os); inner_matcher_.DescribeNegationTo(os); } private: static SelectedArgs GetSelectedArgs(ArgsTuple args) { return TupleFields::GetSelectedFields(args); } // Prints the indices of the selected fields. static void PrintIndices(::std::ostream* os) { *os << "whose fields ("; const int indices[$n] = { $ks }; for (int i = 0; i < $n; i++) { if (indices[i] < 0) break; if (i >= 1) *os << ", "; *os << "#" << indices[i]; } *os << ") "; } const MonomorphicInnerMatcher inner_matcher_; GTEST_DISALLOW_ASSIGN_(ArgsMatcherImpl); }; template class ArgsMatcher { public: explicit ArgsMatcher(const InnerMatcher& inner_matcher) : inner_matcher_(inner_matcher) {} template operator Matcher() const { return MakeMatcher(new ArgsMatcherImpl(inner_matcher_)); } private: const InnerMatcher inner_matcher_; GTEST_DISALLOW_ASSIGN_(ArgsMatcher); }; // A set of metafunctions for computing the result type of AllOf. // AllOf(m1, ..., mN) returns // AllOfResultN::type. // Although AllOf isn't defined for one argument, AllOfResult1 is defined // to simplify the implementation. template struct AllOfResult1 { typedef M1 type; }; $range i 1..n $range i 2..n $for i [[ $range j 2..i $var m = i/2 $range k 1..m $range t m+1..i template struct AllOfResult$i { typedef BothOfMatcher< typename AllOfResult$m<$for k, [[M$k]]>::type, typename AllOfResult$(i-m)<$for t, [[M$t]]>::type > type; }; ]] // A set of metafunctions for computing the result type of AnyOf. // AnyOf(m1, ..., mN) returns // AnyOfResultN::type. // Although AnyOf isn't defined for one argument, AnyOfResult1 is defined // to simplify the implementation. template struct AnyOfResult1 { typedef M1 type; }; $range i 1..n $range i 2..n $for i [[ $range j 2..i $var m = i/2 $range k 1..m $range t m+1..i template struct AnyOfResult$i { typedef EitherOfMatcher< typename AnyOfResult$m<$for k, [[M$k]]>::type, typename AnyOfResult$(i-m)<$for t, [[M$t]]>::type > type; }; ]] } // namespace internal // Args(a_matcher) matches a tuple if the selected // fields of it matches a_matcher. C++ doesn't support default // arguments for function templates, so we have to overload it. $range i 0..n $for i [[ $range j 1..i template <$for j [[int k$j, ]]typename InnerMatcher> inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } ]] // ElementsAre(e_1, e_2, ... e_n) matches an STL-style container with // n elements, where the i-th element in the container must // match the i-th argument in the list. Each argument of // ElementsAre() can be either a value or a matcher. We support up to // $n arguments. // // The use of DecayArray in the implementation allows ElementsAre() // to accept string literals, whose type is const char[N], but we // want to treat them as const char*. // // NOTE: Since ElementsAre() cares about the order of the elements, it // must not be used with containers whose elements's order is // undefined (e.g. hash_map). $range i 0..n $for i [[ $range j 1..i $if i>0 [[ template <$for j, [[typename T$j]]> ]] inline internal::ElementsAreMatcher< ::testing::tuple< $for j, [[ typename internal::DecayArray::type]]> > ElementsAre($for j, [[const T$j& e$j]]) { typedef ::testing::tuple< $for j, [[ typename internal::DecayArray::type]]> Args; return internal::ElementsAreMatcher(Args($for j, [[e$j]])); } ]] // UnorderedElementsAre(e_1, e_2, ..., e_n) is an ElementsAre extension // that matches n elements in any order. We support up to n=$n arguments. // // If you have >$n elements, consider UnorderedElementsAreArray() or // UnorderedPointwise() instead. $range i 0..n $for i [[ $range j 1..i $if i>0 [[ template <$for j, [[typename T$j]]> ]] inline internal::UnorderedElementsAreMatcher< ::testing::tuple< $for j, [[ typename internal::DecayArray::type]]> > UnorderedElementsAre($for j, [[const T$j& e$j]]) { typedef ::testing::tuple< $for j, [[ typename internal::DecayArray::type]]> Args; return internal::UnorderedElementsAreMatcher(Args($for j, [[e$j]])); } ]] // AllOf(m1, m2, ..., mk) matches any value that matches all of the given // sub-matchers. AllOf is called fully qualified to prevent ADL from firing. $range i 2..n $for i [[ $range j 1..i $var m = i/2 $range k 1..m $range t m+1..i template <$for j, [[typename M$j]]> inline typename internal::AllOfResult$i<$for j, [[M$j]]>::type AllOf($for j, [[M$j m$j]]) { return typename internal::AllOfResult$i<$for j, [[M$j]]>::type( $if m == 1 [[m1]] $else [[::testing::AllOf($for k, [[m$k]])]], $if m+1 == i [[m$i]] $else [[::testing::AllOf($for t, [[m$t]])]]); } ]] // AnyOf(m1, m2, ..., mk) matches any value that matches any of the given // sub-matchers. AnyOf is called fully qualified to prevent ADL from firing. $range i 2..n $for i [[ $range j 1..i $var m = i/2 $range k 1..m $range t m+1..i template <$for j, [[typename M$j]]> inline typename internal::AnyOfResult$i<$for j, [[M$j]]>::type AnyOf($for j, [[M$j m$j]]) { return typename internal::AnyOfResult$i<$for j, [[M$j]]>::type( $if m == 1 [[m1]] $else [[::testing::AnyOf($for k, [[m$k]])]], $if m+1 == i [[m$i]] $else [[::testing::AnyOf($for t, [[m$t]])]]); } ]] } // namespace testing $$ } // This Pump meta comment fixes auto-indentation in Emacs. It will not $$ // show up in the generated code. // The MATCHER* family of macros can be used in a namespace scope to // define custom matchers easily. // // Basic Usage // =========== // // The syntax // // MATCHER(name, description_string) { statements; } // // defines a matcher with the given name that executes the statements, // which must return a bool to indicate if the match succeeds. Inside // the statements, you can refer to the value being matched by 'arg', // and refer to its type by 'arg_type'. // // The description string documents what the matcher does, and is used // to generate the failure message when the match fails. Since a // MATCHER() is usually defined in a header file shared by multiple // C++ source files, we require the description to be a C-string // literal to avoid possible side effects. It can be empty, in which // case we'll use the sequence of words in the matcher name as the // description. // // For example: // // MATCHER(IsEven, "") { return (arg % 2) == 0; } // // allows you to write // // // Expects mock_foo.Bar(n) to be called where n is even. // EXPECT_CALL(mock_foo, Bar(IsEven())); // // or, // // // Verifies that the value of some_expression is even. // EXPECT_THAT(some_expression, IsEven()); // // If the above assertion fails, it will print something like: // // Value of: some_expression // Expected: is even // Actual: 7 // // where the description "is even" is automatically calculated from the // matcher name IsEven. // // Argument Type // ============= // // Note that the type of the value being matched (arg_type) is // determined by the context in which you use the matcher and is // supplied to you by the compiler, so you don't need to worry about // declaring it (nor can you). This allows the matcher to be // polymorphic. For example, IsEven() can be used to match any type // where the value of "(arg % 2) == 0" can be implicitly converted to // a bool. In the "Bar(IsEven())" example above, if method Bar() // takes an int, 'arg_type' will be int; if it takes an unsigned long, // 'arg_type' will be unsigned long; and so on. // // Parameterizing Matchers // ======================= // // Sometimes you'll want to parameterize the matcher. For that you // can use another macro: // // MATCHER_P(name, param_name, description_string) { statements; } // // For example: // // MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } // // will allow you to write: // // EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); // // which may lead to this message (assuming n is 10): // // Value of: Blah("a") // Expected: has absolute value 10 // Actual: -9 // // Note that both the matcher description and its parameter are // printed, making the message human-friendly. // // In the matcher definition body, you can write 'foo_type' to // reference the type of a parameter named 'foo'. For example, in the // body of MATCHER_P(HasAbsoluteValue, value) above, you can write // 'value_type' to refer to the type of 'value'. // // We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P$n to // support multi-parameter matchers. // // Describing Parameterized Matchers // ================================= // // The last argument to MATCHER*() is a string-typed expression. The // expression can reference all of the matcher's parameters and a // special bool-typed variable named 'negation'. When 'negation' is // false, the expression should evaluate to the matcher's description; // otherwise it should evaluate to the description of the negation of // the matcher. For example, // // using testing::PrintToString; // // MATCHER_P2(InClosedRange, low, hi, // std::string(negation ? "is not" : "is") + " in range [" + // PrintToString(low) + ", " + PrintToString(hi) + "]") { // return low <= arg && arg <= hi; // } // ... // EXPECT_THAT(3, InClosedRange(4, 6)); // EXPECT_THAT(3, Not(InClosedRange(2, 4))); // // would generate two failures that contain the text: // // Expected: is in range [4, 6] // ... // Expected: is not in range [2, 4] // // If you specify "" as the description, the failure message will // contain the sequence of words in the matcher name followed by the // parameter values printed as a tuple. For example, // // MATCHER_P2(InClosedRange, low, hi, "") { ... } // ... // EXPECT_THAT(3, InClosedRange(4, 6)); // EXPECT_THAT(3, Not(InClosedRange(2, 4))); // // would generate two failures that contain the text: // // Expected: in closed range (4, 6) // ... // Expected: not (in closed range (2, 4)) // // Types of Matcher Parameters // =========================== // // For the purpose of typing, you can view // // MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } // // as shorthand for // // template // FooMatcherPk // Foo(p1_type p1, ..., pk_type pk) { ... } // // When you write Foo(v1, ..., vk), the compiler infers the types of // the parameters v1, ..., and vk for you. If you are not happy with // the result of the type inference, you can specify the types by // explicitly instantiating the template, as in Foo(5, // false). As said earlier, you don't get to (or need to) specify // 'arg_type' as that's determined by the context in which the matcher // is used. You can assign the result of expression Foo(p1, ..., pk) // to a variable of type FooMatcherPk. This // can be useful when composing matchers. // // While you can instantiate a matcher template with reference types, // passing the parameters by pointer usually makes your code more // readable. If, however, you still want to pass a parameter by // reference, be aware that in the failure message generated by the // matcher you will see the value of the referenced object but not its // address. // // Explaining Match Results // ======================== // // Sometimes the matcher description alone isn't enough to explain why // the match has failed or succeeded. For example, when expecting a // long string, it can be very helpful to also print the diff between // the expected string and the actual one. To achieve that, you can // optionally stream additional information to a special variable // named result_listener, whose type is a pointer to class // MatchResultListener: // // MATCHER_P(EqualsLongString, str, "") { // if (arg == str) return true; // // *result_listener << "the difference: " /// << DiffStrings(str, arg); // return false; // } // // Overloading Matchers // ==================== // // You can overload matchers with different numbers of parameters: // // MATCHER_P(Blah, a, description_string1) { ... } // MATCHER_P2(Blah, a, b, description_string2) { ... } // // Caveats // ======= // // When defining a new matcher, you should also consider implementing // MatcherInterface or using MakePolymorphicMatcher(). These // approaches require more work than the MATCHER* macros, but also // give you more control on the types of the value being matched and // the matcher parameters, which may leads to better compiler error // messages when the matcher is used wrong. They also allow // overloading matchers based on parameter types (as opposed to just // based on the number of parameters). // // MATCHER*() can only be used in a namespace scope. The reason is // that C++ doesn't yet allow function-local types to be used to // instantiate templates. The up-coming C++0x standard will fix this. // Once that's done, we'll consider supporting using MATCHER*() inside // a function. // // More Information // ================ // // To learn more about using these macros, please search for 'MATCHER' // on https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md $range i 0..n $for i [[ $var macro_name = [[$if i==0 [[MATCHER]] $elif i==1 [[MATCHER_P]] $else [[MATCHER_P$i]]]] $var class_name = [[name##Matcher[[$if i==0 [[]] $elif i==1 [[P]] $else [[P$i]]]]]] $range j 0..i-1 $var template = [[$if i==0 [[]] $else [[ template <$for j, [[typename p$j##_type]]>\ ]]]] $var ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] $var impl_ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] $var impl_inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(::testing::internal::move(gmock_p$j))]]]]]] $var inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(::testing::internal::move(gmock_p$j))]]]]]] $var params = [[$for j, [[p$j]]]] $var param_types = [[$if i==0 [[]] $else [[<$for j, [[p$j##_type]]>]]]] $var param_types_and_names = [[$for j, [[p$j##_type p$j]]]] $var param_field_decls = [[$for j [[ p$j##_type const p$j;\ ]]]] $var param_field_decls2 = [[$for j [[ p$j##_type const p$j;\ ]]]] #define $macro_name(name$for j [[, p$j]], description)\$template class $class_name {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ [[$if i==1 [[explicit ]]]]gmock_Impl($impl_ctor_param_list)\ $impl_inits {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\$param_field_decls private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple<$for j, [[p$j##_type]]>($for j, [[p$j]])));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl($params));\ }\ [[$if i==1 [[explicit ]]]]$class_name($ctor_param_list)$inits {\ }\$param_field_decls2 private:\ };\$template inline $class_name$param_types name($param_types_and_names) {\ return $class_name$param_types($params);\ }\$template template \ bool $class_name$param_types::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const ]] #endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-more-matchers.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-more-matche0000664000175000017500000000643515110656150031545 0ustar meme// Copyright 2013, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: marcus.boerger@google.com (Marcus Boerger) // Google Mock - a framework for writing C++ mock classes. // // This file implements some matchers that depend on gmock-generated-matchers.h. // // Note that tests are implemented in gmock-matchers_test.cc rather than // gmock-more-matchers-test.cc. #ifndef GMOCK_GMOCK_MORE_MATCHERS_H_ #define GMOCK_GMOCK_MORE_MATCHERS_H_ #include "gmock/gmock-generated-matchers.h" namespace testing { // Silence C4100 (unreferenced formal // parameter) for MSVC #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4100) #if (_MSC_VER == 1900) // and silence C4800 (C4800: 'int *const ': forcing value // to bool 'true' or 'false') for MSVC 14 # pragma warning(disable:4800) #endif #endif // Defines a matcher that matches an empty container. The container must // support both size() and empty(), which all STL-like containers provide. MATCHER(IsEmpty, negation ? "isn't empty" : "is empty") { if (arg.empty()) { return true; } *result_listener << "whose size is " << arg.size(); return false; } // Define a matcher that matches a value that evaluates in boolean // context to true. Useful for types that define "explicit operator // bool" operators and so can't be compared for equality with true // and false. MATCHER(IsTrue, negation ? "is false" : "is true") { return static_cast(arg); } // Define a matcher that matches a value that evaluates in boolean // context to false. Useful for types that define "explicit operator // bool" operators and so can't be compared for equality with true // and false. MATCHER(IsFalse, negation ? "is true" : "is false") { return !static_cast(arg); } #ifdef _MSC_VER # pragma warning(pop) #endif } // namespace testing #endif // GMOCK_GMOCK_MORE_MATCHERS_H_ ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-cardinalities.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-cardinaliti0000664000175000017500000001327415110656150031626 0ustar meme// Copyright 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: wan@google.com (Zhanyong Wan) // Google Mock - a framework for writing C++ mock classes. // // This file implements some commonly used cardinalities. More // cardinalities can be defined by the user implementing the // CardinalityInterface interface if necessary. #ifndef GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ #define GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ #include #include // NOLINT #include "gmock/internal/gmock-port.h" #include "gtest/gtest.h" namespace testing { // To implement a cardinality Foo, define: // 1. a class FooCardinality that implements the // CardinalityInterface interface, and // 2. a factory function that creates a Cardinality object from a // const FooCardinality*. // // The two-level delegation design follows that of Matcher, providing // consistency for extension developers. It also eases ownership // management as Cardinality objects can now be copied like plain values. // The implementation of a cardinality. class CardinalityInterface { public: virtual ~CardinalityInterface() {} // Conservative estimate on the lower/upper bound of the number of // calls allowed. virtual int ConservativeLowerBound() const { return 0; } virtual int ConservativeUpperBound() const { return INT_MAX; } // Returns true iff call_count calls will satisfy this cardinality. virtual bool IsSatisfiedByCallCount(int call_count) const = 0; // Returns true iff call_count calls will saturate this cardinality. virtual bool IsSaturatedByCallCount(int call_count) const = 0; // Describes self to an ostream. virtual void DescribeTo(::std::ostream* os) const = 0; }; // A Cardinality is a copyable and IMMUTABLE (except by assignment) // object that specifies how many times a mock function is expected to // be called. The implementation of Cardinality is just a linked_ptr // to const CardinalityInterface, so copying is fairly cheap. // Don't inherit from Cardinality! class GTEST_API_ Cardinality { public: // Constructs a null cardinality. Needed for storing Cardinality // objects in STL containers. Cardinality() {} // Constructs a Cardinality from its implementation. explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {} // Conservative estimate on the lower/upper bound of the number of // calls allowed. int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); } int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); } // Returns true iff call_count calls will satisfy this cardinality. bool IsSatisfiedByCallCount(int call_count) const { return impl_->IsSatisfiedByCallCount(call_count); } // Returns true iff call_count calls will saturate this cardinality. bool IsSaturatedByCallCount(int call_count) const { return impl_->IsSaturatedByCallCount(call_count); } // Returns true iff call_count calls will over-saturate this // cardinality, i.e. exceed the maximum number of allowed calls. bool IsOverSaturatedByCallCount(int call_count) const { return impl_->IsSaturatedByCallCount(call_count) && !impl_->IsSatisfiedByCallCount(call_count); } // Describes self to an ostream void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } // Describes the given actual call count to an ostream. static void DescribeActualCallCountTo(int actual_call_count, ::std::ostream* os); private: internal::linked_ptr impl_; }; // Creates a cardinality that allows at least n calls. GTEST_API_ Cardinality AtLeast(int n); // Creates a cardinality that allows at most n calls. GTEST_API_ Cardinality AtMost(int n); // Creates a cardinality that allows any number of calls. GTEST_API_ Cardinality AnyNumber(); // Creates a cardinality that allows between min and max calls. GTEST_API_ Cardinality Between(int min, int max); // Creates a cardinality that allows exactly n calls. GTEST_API_ Cardinality Exactly(int n); // Creates a cardinality from its implementation. inline Cardinality MakeCardinality(const CardinalityInterface* c) { return Cardinality(c); } } // namespace testing #endif // GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ opentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/0000775000175000017500000000000015110656150030127 5ustar memeopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/0000775000175000017500000000000015110656150031441 5ustar meme././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/gmock-port.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/g0000664000175000017500000000415715110656150031621 0ustar meme// Copyright 2015, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Injection point for custom user configurations. // The following macros can be defined: // // Flag related macros: // GMOCK_DECLARE_bool_(name) // GMOCK_DECLARE_int32_(name) // GMOCK_DECLARE_string_(name) // GMOCK_DEFINE_bool_(name, default_val, doc) // GMOCK_DEFINE_int32_(name, default_val, doc) // GMOCK_DEFINE_string_(name, default_val, doc) // // ** Custom implementation starts here ** #ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_ #define GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_ #endif // GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_ ././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/gmock-matchers.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/g0000664000175000017500000000372015110656150031614 0ustar meme// Copyright 2015, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // ============================================================ // An installation-specific extension point for gmock-matchers.h. // ============================================================ // // Adds google3 callback support to CallableTraits. // #ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_ #define GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_ #endif // GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_ ././@LongLink0000644000000000000000000000017500000000000011606 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/gmock-generated-actions.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/g0000664000175000017500000000051115110656150031607 0ustar meme// This file was GENERATED by command: // pump.py gmock-generated-actions.h.pump // DO NOT EDIT BY HAND!!! #ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ #define GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ #endif // GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ ././@LongLink0000644000000000000000000000020200000000000011575 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/gmock-generated-actions.h.pumpopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/custom/g0000664000175000017500000000063715110656150031620 0ustar meme$$ -*- mode: c++; -*- $$ This is a Pump source file. Please use Pump to convert $$ it to callback-actions.h. $$ $var max_callback_arity = 5 $$}} This meta comment fixes auto-indentation in editors. #ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ #define GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ #endif // GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/gmock-port.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/gmock-po0000664000175000017500000000743315110656150031575 0ustar meme// Copyright 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: vadimb@google.com (Vadim Berman) // // Low-level types and utilities for porting Google Mock to various // platforms. All macros ending with _ and symbols defined in an // internal namespace are subject to change without notice. Code // outside Google Mock MUST NOT USE THEM DIRECTLY. Macros that don't // end with _ are part of Google Mock's public API and can be used by // code outside Google Mock. #ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ #define GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ #include #include #include // Most of the utilities needed for porting Google Mock are also // required for Google Test and are defined in gtest-port.h. // // Note to maintainers: to reduce code duplication, prefer adding // portability utilities to Google Test's gtest-port.h instead of // here, as Google Mock depends on Google Test. Only add a utility // here if it's truly specific to Google Mock. #include "gtest/internal/gtest-linked_ptr.h" #include "gtest/internal/gtest-port.h" #include "gmock/internal/custom/gmock-port.h" // For MS Visual C++, check the compiler version. At least VS 2003 is // required to compile Google Mock. #if defined(_MSC_VER) && _MSC_VER < 1310 # error "At least Visual C++ 2003 (7.1) is required to compile Google Mock." #endif // Macro for referencing flags. This is public as we want the user to // use this syntax to reference Google Mock flags. #define GMOCK_FLAG(name) FLAGS_gmock_##name #if !defined(GMOCK_DECLARE_bool_) // Macros for declaring flags. # define GMOCK_DECLARE_bool_(name) extern GTEST_API_ bool GMOCK_FLAG(name) # define GMOCK_DECLARE_int32_(name) \ extern GTEST_API_ ::testing::internal::Int32 GMOCK_FLAG(name) # define GMOCK_DECLARE_string_(name) \ extern GTEST_API_ ::std::string GMOCK_FLAG(name) // Macros for defining flags. # define GMOCK_DEFINE_bool_(name, default_val, doc) \ GTEST_API_ bool GMOCK_FLAG(name) = (default_val) # define GMOCK_DEFINE_int32_(name, default_val, doc) \ GTEST_API_ ::testing::internal::Int32 GMOCK_FLAG(name) = (default_val) # define GMOCK_DEFINE_string_(name, default_val, doc) \ GTEST_API_ ::std::string GMOCK_FLAG(name) = (default_val) #endif // !defined(GMOCK_DECLARE_bool_) #endif // GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ ././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/gmock-internal-utils.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/gmock-in0000664000175000017500000005413215110656150031563 0ustar meme// Copyright 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: wan@google.com (Zhanyong Wan) // Google Mock - a framework for writing C++ mock classes. // // This file defines some utilities useful for implementing Google // Mock. They are subject to change without notice, so please DO NOT // USE THEM IN USER CODE. #ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ #define GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ #include #include // NOLINT #include #include "gmock/internal/gmock-generated-internal-utils.h" #include "gmock/internal/gmock-port.h" #include "gtest/gtest.h" namespace testing { namespace internal { // Silence MSVC C4100 (unreferenced formal parameter) and // C4805('==': unsafe mix of type 'const int' and type 'const bool') #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4100) # pragma warning(disable:4805) #endif // Joins a vector of strings as if they are fields of a tuple; returns // the joined string. GTEST_API_ std::string JoinAsTuple(const Strings& fields); // Converts an identifier name to a space-separated list of lower-case // words. Each maximum substring of the form [A-Za-z][a-z]*|\d+ is // treated as one word. For example, both "FooBar123" and // "foo_bar_123" are converted to "foo bar 123". GTEST_API_ std::string ConvertIdentifierNameToWords(const char* id_name); // PointeeOf::type is the type of a value pointed to by a // Pointer, which can be either a smart pointer or a raw pointer. The // following default implementation is for the case where Pointer is a // smart pointer. template struct PointeeOf { // Smart pointer classes define type element_type as the type of // their pointees. typedef typename Pointer::element_type type; }; // This specialization is for the raw pointer case. template struct PointeeOf { typedef T type; }; // NOLINT // GetRawPointer(p) returns the raw pointer underlying p when p is a // smart pointer, or returns p itself when p is already a raw pointer. // The following default implementation is for the smart pointer case. template inline const typename Pointer::element_type* GetRawPointer(const Pointer& p) { return p.get(); } // This overloaded version is for the raw pointer case. template inline Element* GetRawPointer(Element* p) { return p; } // This comparator allows linked_ptr to be stored in sets. template struct LinkedPtrLessThan { bool operator()(const ::testing::internal::linked_ptr& lhs, const ::testing::internal::linked_ptr& rhs) const { return lhs.get() < rhs.get(); } }; // Symbian compilation can be done with wchar_t being either a native // type or a typedef. Using Google Mock with OpenC without wchar_t // should require the definition of _STLP_NO_WCHAR_T. // // MSVC treats wchar_t as a native type usually, but treats it as the // same as unsigned short when the compiler option /Zc:wchar_t- is // specified. It defines _NATIVE_WCHAR_T_DEFINED symbol when wchar_t // is a native type. #if (GTEST_OS_SYMBIAN && defined(_STLP_NO_WCHAR_T)) || \ (defined(_MSC_VER) && !defined(_NATIVE_WCHAR_T_DEFINED)) // wchar_t is a typedef. #else # define GMOCK_WCHAR_T_IS_NATIVE_ 1 #endif // signed wchar_t and unsigned wchar_t are NOT in the C++ standard. // Using them is a bad practice and not portable. So DON'T use them. // // Still, Google Mock is designed to work even if the user uses signed // wchar_t or unsigned wchar_t (obviously, assuming the compiler // supports them). // // To gcc, // wchar_t == signed wchar_t != unsigned wchar_t == unsigned int #ifdef __GNUC__ #if !defined(__WCHAR_UNSIGNED__) // signed/unsigned wchar_t are valid types. # define GMOCK_HAS_SIGNED_WCHAR_T_ 1 #endif #endif // In what follows, we use the term "kind" to indicate whether a type // is bool, an integer type (excluding bool), a floating-point type, // or none of them. This categorization is useful for determining // when a matcher argument type can be safely converted to another // type in the implementation of SafeMatcherCast. enum TypeKind { kBool, kInteger, kFloatingPoint, kOther }; // KindOf::value is the kind of type T. template struct KindOf { enum { value = kOther }; // The default kind. }; // This macro declares that the kind of 'type' is 'kind'. #define GMOCK_DECLARE_KIND_(type, kind) \ template <> struct KindOf { enum { value = kind }; } GMOCK_DECLARE_KIND_(bool, kBool); // All standard integer types. GMOCK_DECLARE_KIND_(char, kInteger); GMOCK_DECLARE_KIND_(signed char, kInteger); GMOCK_DECLARE_KIND_(unsigned char, kInteger); GMOCK_DECLARE_KIND_(short, kInteger); // NOLINT GMOCK_DECLARE_KIND_(unsigned short, kInteger); // NOLINT GMOCK_DECLARE_KIND_(int, kInteger); GMOCK_DECLARE_KIND_(unsigned int, kInteger); GMOCK_DECLARE_KIND_(long, kInteger); // NOLINT GMOCK_DECLARE_KIND_(unsigned long, kInteger); // NOLINT #if GMOCK_WCHAR_T_IS_NATIVE_ GMOCK_DECLARE_KIND_(wchar_t, kInteger); #endif // Non-standard integer types. GMOCK_DECLARE_KIND_(Int64, kInteger); GMOCK_DECLARE_KIND_(UInt64, kInteger); // All standard floating-point types. GMOCK_DECLARE_KIND_(float, kFloatingPoint); GMOCK_DECLARE_KIND_(double, kFloatingPoint); GMOCK_DECLARE_KIND_(long double, kFloatingPoint); #undef GMOCK_DECLARE_KIND_ // Evaluates to the kind of 'type'. #define GMOCK_KIND_OF_(type) \ static_cast< ::testing::internal::TypeKind>( \ ::testing::internal::KindOf::value) // Evaluates to true iff integer type T is signed. #define GMOCK_IS_SIGNED_(T) (static_cast(-1) < 0) // LosslessArithmeticConvertibleImpl::value // is true iff arithmetic type From can be losslessly converted to // arithmetic type To. // // It's the user's responsibility to ensure that both From and To are // raw (i.e. has no CV modifier, is not a pointer, and is not a // reference) built-in arithmetic types, kFromKind is the kind of // From, and kToKind is the kind of To; the value is // implementation-defined when the above pre-condition is violated. template struct LosslessArithmeticConvertibleImpl : public false_type {}; // Converting bool to bool is lossless. template <> struct LosslessArithmeticConvertibleImpl : public true_type {}; // NOLINT // Converting bool to any integer type is lossless. template struct LosslessArithmeticConvertibleImpl : public true_type {}; // NOLINT // Converting bool to any floating-point type is lossless. template struct LosslessArithmeticConvertibleImpl : public true_type {}; // NOLINT // Converting an integer to bool is lossy. template struct LosslessArithmeticConvertibleImpl : public false_type {}; // NOLINT // Converting an integer to another non-bool integer is lossless iff // the target type's range encloses the source type's range. template struct LosslessArithmeticConvertibleImpl : public bool_constant< // When converting from a smaller size to a larger size, we are // fine as long as we are not converting from signed to unsigned. ((sizeof(From) < sizeof(To)) && (!GMOCK_IS_SIGNED_(From) || GMOCK_IS_SIGNED_(To))) || // When converting between the same size, the signedness must match. ((sizeof(From) == sizeof(To)) && (GMOCK_IS_SIGNED_(From) == GMOCK_IS_SIGNED_(To)))> {}; // NOLINT #undef GMOCK_IS_SIGNED_ // Converting an integer to a floating-point type may be lossy, since // the format of a floating-point number is implementation-defined. template struct LosslessArithmeticConvertibleImpl : public false_type {}; // NOLINT // Converting a floating-point to bool is lossy. template struct LosslessArithmeticConvertibleImpl : public false_type {}; // NOLINT // Converting a floating-point to an integer is lossy. template struct LosslessArithmeticConvertibleImpl : public false_type {}; // NOLINT // Converting a floating-point to another floating-point is lossless // iff the target type is at least as big as the source type. template struct LosslessArithmeticConvertibleImpl< kFloatingPoint, From, kFloatingPoint, To> : public bool_constant {}; // NOLINT // LosslessArithmeticConvertible::value is true iff arithmetic // type From can be losslessly converted to arithmetic type To. // // It's the user's responsibility to ensure that both From and To are // raw (i.e. has no CV modifier, is not a pointer, and is not a // reference) built-in arithmetic types; the value is // implementation-defined when the above pre-condition is violated. template struct LosslessArithmeticConvertible : public LosslessArithmeticConvertibleImpl< GMOCK_KIND_OF_(From), From, GMOCK_KIND_OF_(To), To> {}; // NOLINT // This interface knows how to report a Google Mock failure (either // non-fatal or fatal). class FailureReporterInterface { public: // The type of a failure (either non-fatal or fatal). enum FailureType { kNonfatal, kFatal }; virtual ~FailureReporterInterface() {} // Reports a failure that occurred at the given source file location. virtual void ReportFailure(FailureType type, const char* file, int line, const std::string& message) = 0; }; // Returns the failure reporter used by Google Mock. GTEST_API_ FailureReporterInterface* GetFailureReporter(); // Asserts that condition is true; aborts the process with the given // message if condition is false. We cannot use LOG(FATAL) or CHECK() // as Google Mock might be used to mock the log sink itself. We // inline this function to prevent it from showing up in the stack // trace. inline void Assert(bool condition, const char* file, int line, const std::string& msg) { if (!condition) { GetFailureReporter()->ReportFailure(FailureReporterInterface::kFatal, file, line, msg); } } inline void Assert(bool condition, const char* file, int line) { Assert(condition, file, line, "Assertion failed."); } // Verifies that condition is true; generates a non-fatal failure if // condition is false. inline void Expect(bool condition, const char* file, int line, const std::string& msg) { if (!condition) { GetFailureReporter()->ReportFailure(FailureReporterInterface::kNonfatal, file, line, msg); } } inline void Expect(bool condition, const char* file, int line) { Expect(condition, file, line, "Expectation failed."); } // Severity level of a log. enum LogSeverity { kInfo = 0, kWarning = 1 }; // Valid values for the --gmock_verbose flag. // All logs (informational and warnings) are printed. const char kInfoVerbosity[] = "info"; // Only warnings are printed. const char kWarningVerbosity[] = "warning"; // No logs are printed. const char kErrorVerbosity[] = "error"; // Returns true iff a log with the given severity is visible according // to the --gmock_verbose flag. GTEST_API_ bool LogIsVisible(LogSeverity severity); // Prints the given message to stdout iff 'severity' >= the level // specified by the --gmock_verbose flag. If stack_frames_to_skip >= // 0, also prints the stack trace excluding the top // stack_frames_to_skip frames. In opt mode, any positive // stack_frames_to_skip is treated as 0, since we don't know which // function calls will be inlined by the compiler and need to be // conservative. GTEST_API_ void Log(LogSeverity severity, const std::string& message, int stack_frames_to_skip); // A marker class that is used to resolve parameterless expectations to the // correct overload. This must not be instantiable, to prevent client code from // accidentally resolving to the overload; for example: // // ON_CALL(mock, Method({}, nullptr))… // class WithoutMatchers { private: WithoutMatchers() {} friend GTEST_API_ WithoutMatchers GetWithoutMatchers(); }; // Internal use only: access the singleton instance of WithoutMatchers. GTEST_API_ WithoutMatchers GetWithoutMatchers(); // TODO(wan@google.com): group all type utilities together. // Type traits. // is_reference::value is non-zero iff T is a reference type. template struct is_reference : public false_type {}; template struct is_reference : public true_type {}; // type_equals::value is non-zero iff T1 and T2 are the same type. template struct type_equals : public false_type {}; template struct type_equals : public true_type {}; // remove_reference::type removes the reference from type T, if any. template struct remove_reference { typedef T type; }; // NOLINT template struct remove_reference { typedef T type; }; // NOLINT // DecayArray::type turns an array type U[N] to const U* and preserves // other types. Useful for saving a copy of a function argument. template struct DecayArray { typedef T type; }; // NOLINT template struct DecayArray { typedef const T* type; }; // Sometimes people use arrays whose size is not available at the use site // (e.g. extern const char kNamePrefix[]). This specialization covers that // case. template struct DecayArray { typedef const T* type; }; // Disable MSVC warnings for infinite recursion, since in this case the // the recursion is unreachable. #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4717) #endif // Invalid() is usable as an expression of type T, but will terminate // the program with an assertion failure if actually run. This is useful // when a value of type T is needed for compilation, but the statement // will not really be executed (or we don't care if the statement // crashes). template inline T Invalid() { Assert(false, "", -1, "Internal error: attempt to return invalid value"); // This statement is unreachable, and would never terminate even if it // could be reached. It is provided only to placate compiler warnings // about missing return statements. return Invalid(); } #ifdef _MSC_VER # pragma warning(pop) #endif // Given a raw type (i.e. having no top-level reference or const // modifier) RawContainer that's either an STL-style container or a // native array, class StlContainerView has the // following members: // // - type is a type that provides an STL-style container view to // (i.e. implements the STL container concept for) RawContainer; // - const_reference is a type that provides a reference to a const // RawContainer; // - ConstReference(raw_container) returns a const reference to an STL-style // container view to raw_container, which is a RawContainer. // - Copy(raw_container) returns an STL-style container view of a // copy of raw_container, which is a RawContainer. // // This generic version is used when RawContainer itself is already an // STL-style container. template class StlContainerView { public: typedef RawContainer type; typedef const type& const_reference; static const_reference ConstReference(const RawContainer& container) { // Ensures that RawContainer is not a const type. testing::StaticAssertTypeEq(); return container; } static type Copy(const RawContainer& container) { return container; } }; // This specialization is used when RawContainer is a native array type. template class StlContainerView { public: typedef GTEST_REMOVE_CONST_(Element) RawElement; typedef internal::NativeArray type; // NativeArray can represent a native array either by value or by // reference (selected by a constructor argument), so 'const type' // can be used to reference a const native array. We cannot // 'typedef const type& const_reference' here, as that would mean // ConstReference() has to return a reference to a local variable. typedef const type const_reference; static const_reference ConstReference(const Element (&array)[N]) { // Ensures that Element is not a const type. testing::StaticAssertTypeEq(); #if GTEST_OS_SYMBIAN // The Nokia Symbian compiler confuses itself in template instantiation // for this call without the cast to Element*: // function call '[testing::internal::NativeArray].NativeArray( // {lval} const char *[4], long, testing::internal::RelationToSource)' // does not match // 'testing::internal::NativeArray::NativeArray( // char *const *, unsigned int, testing::internal::RelationToSource)' // (instantiating: 'testing::internal::ContainsMatcherImpl // ::Matches(const char * (&)[4]) const') // (instantiating: 'testing::internal::StlContainerView:: // ConstReference(const char * (&)[4])') // (and though the N parameter type is mismatched in the above explicit // conversion of it doesn't help - only the conversion of the array). return type(const_cast(&array[0]), N, RelationToSourceReference()); #else return type(array, N, RelationToSourceReference()); #endif // GTEST_OS_SYMBIAN } static type Copy(const Element (&array)[N]) { #if GTEST_OS_SYMBIAN return type(const_cast(&array[0]), N, RelationToSourceCopy()); #else return type(array, N, RelationToSourceCopy()); #endif // GTEST_OS_SYMBIAN } }; // This specialization is used when RawContainer is a native array // represented as a (pointer, size) tuple. template class StlContainerView< ::testing::tuple > { public: typedef GTEST_REMOVE_CONST_( typename internal::PointeeOf::type) RawElement; typedef internal::NativeArray type; typedef const type const_reference; static const_reference ConstReference( const ::testing::tuple& array) { return type(get<0>(array), get<1>(array), RelationToSourceReference()); } static type Copy(const ::testing::tuple& array) { return type(get<0>(array), get<1>(array), RelationToSourceCopy()); } }; // The following specialization prevents the user from instantiating // StlContainer with a reference type. template class StlContainerView; // A type transform to remove constness from the first part of a pair. // Pairs like that are used as the value_type of associative containers, // and this transform produces a similar but assignable pair. template struct RemoveConstFromKey { typedef T type; }; // Partially specialized to remove constness from std::pair. template struct RemoveConstFromKey > { typedef std::pair type; }; // Mapping from booleans to types. Similar to boost::bool_ and // std::integral_constant. template struct BooleanConstant {}; // Emit an assertion failure due to incorrect DoDefault() usage. Out-of-lined to // reduce code size. GTEST_API_ void IllegalDoDefault(const char* file, int line); #if GTEST_LANG_CXX11 // Helper types for Apply() below. template struct int_pack { typedef int_pack type; }; template struct append; template struct append, I> : int_pack {}; template struct make_int_pack : append::type, C - 1> {}; template <> struct make_int_pack<0> : int_pack<> {}; template auto ApplyImpl(F&& f, Tuple&& args, int_pack) -> decltype( std::forward(f)(std::get(std::forward(args))...)) { return std::forward(f)(std::get(std::forward(args))...); } // Apply the function to a tuple of arguments. template auto Apply(F&& f, Tuple&& args) -> decltype(ApplyImpl(std::forward(f), std::forward(args), make_int_pack::value>())) { return ApplyImpl(std::forward(f), std::forward(args), make_int_pack::value>()); } #endif #ifdef _MSC_VER # pragma warning(pop) #endif } // namespace internal } // namespace testing #endif // GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ ././@LongLink0000644000000000000000000000020200000000000011575 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/gmock-generated-internal-utils.h.pumpopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/gmock-ge0000664000175000017500000001147615110656150031554 0ustar meme$$ -*- mode: c++; -*- $$ This is a Pump source file. Please use Pump to convert it to $$ gmock-generated-function-mockers.h. $$ $var n = 10 $$ The maximum arity we support. // Copyright 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: wan@google.com (Zhanyong Wan) // Google Mock - a framework for writing C++ mock classes. // // This file contains template meta-programming utility classes needed // for implementing Google Mock. #ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ #define GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ #include "gmock/internal/gmock-port.h" namespace testing { template class Matcher; namespace internal { // An IgnoredValue object can be implicitly constructed from ANY value. // This is used in implementing the IgnoreResult(a) action. class IgnoredValue { public: // This constructor template allows any value to be implicitly // converted to IgnoredValue. The object has no data member and // doesn't try to remember anything about the argument. We // deliberately omit the 'explicit' keyword in order to allow the // conversion to be implicit. template IgnoredValue(const T& /* ignored */) {} // NOLINT(runtime/explicit) }; // MatcherTuple::type is a tuple type where each field is a Matcher // for the corresponding field in tuple type T. template struct MatcherTuple; $range i 0..n $for i [[ $range j 1..i $var typename_As = [[$for j, [[typename A$j]]]] $var As = [[$for j, [[A$j]]]] $var matcher_As = [[$for j, [[Matcher]]]] template <$typename_As> struct MatcherTuple< ::testing::tuple<$As> > { typedef ::testing::tuple<$matcher_As > type; }; ]] // Template struct Function, where F must be a function type, contains // the following typedefs: // // Result: the function's return type. // ArgumentN: the type of the N-th argument, where N starts with 1. // ArgumentTuple: the tuple type consisting of all parameters of F. // ArgumentMatcherTuple: the tuple type consisting of Matchers for all // parameters of F. // MakeResultVoid: the function type obtained by substituting void // for the return type of F. // MakeResultIgnoredValue: // the function type obtained by substituting Something // for the return type of F. template struct Function; template struct Function { typedef R Result; typedef ::testing::tuple<> ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(); typedef IgnoredValue MakeResultIgnoredValue(); }; $range i 1..n $for i [[ $range j 1..i $var typename_As = [[$for j [[, typename A$j]]]] $var As = [[$for j, [[A$j]]]] $var matcher_As = [[$for j, [[Matcher]]]] $range k 1..i-1 $var prev_As = [[$for k, [[A$k]]]] template struct Function : Function { typedef A$i Argument$i; typedef ::testing::tuple<$As> ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid($As); typedef IgnoredValue MakeResultIgnoredValue($As); }; ]] } // namespace internal } // namespace testing #endif // GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ ././@LongLink0000644000000000000000000000017500000000000011606 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/gmock-generated-internal-utils.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/internal/gmock-ge0000664000175000017500000002656115110656150031555 0ustar meme// This file was GENERATED by command: // pump.py gmock-generated-internal-utils.h.pump // DO NOT EDIT BY HAND!!! // Copyright 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: wan@google.com (Zhanyong Wan) // Google Mock - a framework for writing C++ mock classes. // // This file contains template meta-programming utility classes needed // for implementing Google Mock. #ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ #define GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ #include "gmock/internal/gmock-port.h" namespace testing { template class Matcher; namespace internal { // An IgnoredValue object can be implicitly constructed from ANY value. // This is used in implementing the IgnoreResult(a) action. class IgnoredValue { public: // This constructor template allows any value to be implicitly // converted to IgnoredValue. The object has no data member and // doesn't try to remember anything about the argument. We // deliberately omit the 'explicit' keyword in order to allow the // conversion to be implicit. template IgnoredValue(const T& /* ignored */) {} // NOLINT(runtime/explicit) }; // MatcherTuple::type is a tuple type where each field is a Matcher // for the corresponding field in tuple type T. template struct MatcherTuple; template <> struct MatcherTuple< ::testing::tuple<> > { typedef ::testing::tuple< > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher, Matcher > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher, Matcher, Matcher > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher, Matcher, Matcher, Matcher > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher, Matcher, Matcher, Matcher, Matcher > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher > type; }; template struct MatcherTuple< ::testing::tuple > { typedef ::testing::tuple, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher, Matcher > type; }; // Template struct Function, where F must be a function type, contains // the following typedefs: // // Result: the function's return type. // ArgumentN: the type of the N-th argument, where N starts with 1. // ArgumentTuple: the tuple type consisting of all parameters of F. // ArgumentMatcherTuple: the tuple type consisting of Matchers for all // parameters of F. // MakeResultVoid: the function type obtained by substituting void // for the return type of F. // MakeResultIgnoredValue: // the function type obtained by substituting Something // for the return type of F. template struct Function; template struct Function { typedef R Result; typedef ::testing::tuple<> ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(); typedef IgnoredValue MakeResultIgnoredValue(); }; template struct Function : Function { typedef A1 Argument1; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1); typedef IgnoredValue MakeResultIgnoredValue(A1); }; template struct Function : Function { typedef A2 Argument2; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2); typedef IgnoredValue MakeResultIgnoredValue(A1, A2); }; template struct Function : Function { typedef A3 Argument3; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2, A3); typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3); }; template struct Function : Function { typedef A4 Argument4; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2, A3, A4); typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4); }; template struct Function : Function { typedef A5 Argument5; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2, A3, A4, A5); typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5); }; template struct Function : Function { typedef A6 Argument6; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6); typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6); }; template struct Function : Function { typedef A7 Argument7; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6, A7); typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6, A7); }; template struct Function : Function { typedef A8 Argument8; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6, A7, A8); typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6, A7, A8); }; template struct Function : Function { typedef A9 Argument9; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6, A7, A8, A9); typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6, A7, A8, A9); }; template struct Function : Function { typedef A10 Argument10; typedef ::testing::tuple ArgumentTuple; typedef typename MatcherTuple::type ArgumentMatcherTuple; typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); }; } // namespace internal } // namespace testing #endif // GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ ././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-generated-nice-strict.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-generated-n0000664000175000017500000004376315110656150031542 0ustar meme// This file was GENERATED by command: // pump.py gmock-generated-nice-strict.h.pump // DO NOT EDIT BY HAND!!! // Copyright 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: wan@google.com (Zhanyong Wan) // Implements class templates NiceMock, NaggyMock, and StrictMock. // // Given a mock class MockFoo that is created using Google Mock, // NiceMock is a subclass of MockFoo that allows // uninteresting calls (i.e. calls to mock methods that have no // EXPECT_CALL specs), NaggyMock is a subclass of MockFoo // that prints a warning when an uninteresting call occurs, and // StrictMock is a subclass of MockFoo that treats all // uninteresting calls as errors. // // Currently a mock is naggy by default, so MockFoo and // NaggyMock behave like the same. However, we will soon // switch the default behavior of mocks to be nice, as that in general // leads to more maintainable tests. When that happens, MockFoo will // stop behaving like NaggyMock and start behaving like // NiceMock. // // NiceMock, NaggyMock, and StrictMock "inherit" the constructors of // their respective base class. Therefore you can write // NiceMock(5, "a") to construct a nice mock where MockFoo // has a constructor that accepts (int, const char*), for example. // // A known limitation is that NiceMock, NaggyMock, // and StrictMock only works for mock methods defined using // the MOCK_METHOD* family of macros DIRECTLY in the MockFoo class. // If a mock method is defined in a base class of MockFoo, the "nice" // or "strict" modifier may not affect it, depending on the compiler. // In particular, nesting NiceMock, NaggyMock, and StrictMock is NOT // supported. #ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ #define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ #include "gmock/gmock-spec-builders.h" #include "gmock/internal/gmock-port.h" namespace testing { template class NiceMock : public MockClass { public: NiceMock() : MockClass() { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } #if GTEST_LANG_CXX11 // Ideally, we would inherit base class's constructors through a using // declaration, which would preserve their visibility. However, many existing // tests rely on the fact that current implementation reexports protected // constructors as public. These tests would need to be cleaned up first. // Single argument constructor is special-cased so that it can be // made explicit. template explicit NiceMock(A&& arg) : MockClass(std::forward(arg)) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(A1&& arg1, A2&& arg2, An&&... args) : MockClass(std::forward(arg1), std::forward(arg2), std::forward(args)...) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } #else // C++98 doesn't have variadic templates, so we have to define one // for each arity. template explicit NiceMock(const A1& a1) : MockClass(a1) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4) : MockClass(a1, a2, a3, a4) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : MockClass(a1, a2, a3, a4, a5) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, a6, a7) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } template NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { ::testing::Mock::AllowUninterestingCalls( internal::ImplicitCast_(this)); } #endif // GTEST_LANG_CXX11 ~NiceMock() { ::testing::Mock::UnregisterCallReaction( internal::ImplicitCast_(this)); } private: GTEST_DISALLOW_COPY_AND_ASSIGN_(NiceMock); }; template class NaggyMock : public MockClass { public: NaggyMock() : MockClass() { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } #if GTEST_LANG_CXX11 // Ideally, we would inherit base class's constructors through a using // declaration, which would preserve their visibility. However, many existing // tests rely on the fact that current implementation reexports protected // constructors as public. These tests would need to be cleaned up first. // Single argument constructor is special-cased so that it can be // made explicit. template explicit NaggyMock(A&& arg) : MockClass(std::forward(arg)) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(A1&& arg1, A2&& arg2, An&&... args) : MockClass(std::forward(arg1), std::forward(arg2), std::forward(args)...) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } #else // C++98 doesn't have variadic templates, so we have to define one // for each arity. template explicit NaggyMock(const A1& a1) : MockClass(a1) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4) : MockClass(a1, a2, a3, a4) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : MockClass(a1, a2, a3, a4, a5) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, a6, a7) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } template NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { ::testing::Mock::WarnUninterestingCalls( internal::ImplicitCast_(this)); } #endif // GTEST_LANG_CXX11 ~NaggyMock() { ::testing::Mock::UnregisterCallReaction( internal::ImplicitCast_(this)); } private: GTEST_DISALLOW_COPY_AND_ASSIGN_(NaggyMock); }; template class StrictMock : public MockClass { public: StrictMock() : MockClass() { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } #if GTEST_LANG_CXX11 // Ideally, we would inherit base class's constructors through a using // declaration, which would preserve their visibility. However, many existing // tests rely on the fact that current implementation reexports protected // constructors as public. These tests would need to be cleaned up first. // Single argument constructor is special-cased so that it can be // made explicit. template explicit StrictMock(A&& arg) : MockClass(std::forward(arg)) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(A1&& arg1, A2&& arg2, An&&... args) : MockClass(std::forward(arg1), std::forward(arg2), std::forward(args)...) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } #else // C++98 doesn't have variadic templates, so we have to define one // for each arity. template explicit StrictMock(const A1& a1) : MockClass(a1) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4) : MockClass(a1, a2, a3, a4) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : MockClass(a1, a2, a3, a4, a5) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, a6, a7) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } template StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { ::testing::Mock::FailUninterestingCalls( internal::ImplicitCast_(this)); } #endif // GTEST_LANG_CXX11 ~StrictMock() { ::testing::Mock::UnregisterCallReaction( internal::ImplicitCast_(this)); } private: GTEST_DISALLOW_COPY_AND_ASSIGN_(StrictMock); }; // The following specializations catch some (relatively more common) // user errors of nesting nice and strict mocks. They do NOT catch // all possible errors. // These specializations are declared but not defined, as NiceMock, // NaggyMock, and StrictMock cannot be nested. template class NiceMock >; template class NiceMock >; template class NiceMock >; template class NaggyMock >; template class NaggyMock >; template class NaggyMock >; template class StrictMock >; template class StrictMock >; template class StrictMock >; } // namespace testing #endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ ././@LongLink0000644000000000000000000000017300000000000011604 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-generated-function-mockers.h.pumpopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-generated-f0000664000175000017500000002673415110656150031531 0ustar meme$$ -*- mode: c++; -*- $$ This is a Pump source file. Please use Pump to convert $$ it to gmock-generated-function-mockers.h. $$ $var n = 10 $$ The maximum arity we support. // Copyright 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: wan@google.com (Zhanyong Wan) // Google Mock - a framework for writing C++ mock classes. // // This file implements function mockers of various arities. #ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ #define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ #include "gmock/gmock-spec-builders.h" #include "gmock/internal/gmock-internal-utils.h" #if GTEST_HAS_STD_FUNCTION_ # include #endif namespace testing { namespace internal { template class FunctionMockerBase; // Note: class FunctionMocker really belongs to the ::testing // namespace. However if we define it in ::testing, MSVC will // complain when classes in ::testing::internal declare it as a // friend class template. To workaround this compiler bug, we define // FunctionMocker in ::testing::internal and import it into ::testing. template class FunctionMocker; $range i 0..n $for i [[ $range j 1..i $var typename_As = [[$for j [[, typename A$j]]]] $var As = [[$for j, [[A$j]]]] $var as = [[$for j, [[internal::forward(a$j)]]]] $var Aas = [[$for j, [[A$j a$j]]]] $var ms = [[$for j, [[m$j]]]] $var matchers = [[$for j, [[const Matcher& m$j]]]] template class FunctionMocker : public internal::FunctionMockerBase { public: typedef R F($As); typedef typename internal::Function::ArgumentTuple ArgumentTuple; MockSpec With($matchers) { return MockSpec(this, ::testing::make_tuple($ms)); } R Invoke($Aas) { // Even though gcc and MSVC don't enforce it, 'this->' is required // by the C++ standard [14.6.4] here, as the base class type is // dependent on the template argument (and thus shouldn't be // looked into when resolving InvokeWith). return this->InvokeWith(ArgumentTuple($as)); } }; ]] // Removes the given pointer; this is a helper for the expectation setter method // for parameterless matchers. // // We want to make sure that the user cannot set a parameterless expectation on // overloaded methods, including methods which are overloaded on const. Example: // // class MockClass { // MOCK_METHOD0(GetName, string&()); // MOCK_CONST_METHOD0(GetName, const string&()); // }; // // TEST() { // // This should be an error, as it's not clear which overload is expected. // EXPECT_CALL(mock, GetName).WillOnce(ReturnRef(value)); // } // // Here are the generated expectation-setter methods: // // class MockClass { // // Overload 1 // MockSpec gmock_GetName() { … } // // Overload 2. Declared const so that the compiler will generate an // // error when trying to resolve between this and overload 4 in // // 'gmock_GetName(WithoutMatchers(), nullptr)'. // MockSpec gmock_GetName( // const WithoutMatchers&, const Function*) const { // // Removes const from this, calls overload 1 // return AdjustConstness_(this)->gmock_GetName(); // } // // // Overload 3 // const string& gmock_GetName() const { … } // // Overload 4 // MockSpec gmock_GetName( // const WithoutMatchers&, const Function*) const { // // Does not remove const, calls overload 3 // return AdjustConstness_const(this)->gmock_GetName(); // } // } // template const MockType* AdjustConstness_const(const MockType* mock) { return mock; } // Removes const from and returns the given pointer; this is a helper for the // expectation setter method for parameterless matchers. template MockType* AdjustConstness_(const MockType* mock) { return const_cast(mock); } } // namespace internal // The style guide prohibits "using" statements in a namespace scope // inside a header file. However, the FunctionMocker class template // is meant to be defined in the ::testing namespace. The following // line is just a trick for working around a bug in MSVC 8.0, which // cannot handle it if we define FunctionMocker in ::testing. using internal::FunctionMocker; // GMOCK_RESULT_(tn, F) expands to the result type of function type F. // We define this as a variadic macro in case F contains unprotected // commas (the same reason that we use variadic macros in other places // in this file). // INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! #define GMOCK_RESULT_(tn, ...) \ tn ::testing::internal::Function<__VA_ARGS__>::Result // The type of argument N of the given function type. // INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! #define GMOCK_ARG_(tn, N, ...) \ tn ::testing::internal::Function<__VA_ARGS__>::Argument##N // The matcher type for argument N of the given function type. // INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! #define GMOCK_MATCHER_(tn, N, ...) \ const ::testing::Matcher& // The variable for mocking the given method. // INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! #define GMOCK_MOCKER_(arity, constness, Method) \ GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) $for i [[ $range j 1..i $var arg_as = [[$for j, [[GMOCK_ARG_(tn, $j, __VA_ARGS__) gmock_a$j]]]] $var as = [[$for j, \ [[::testing::internal::forward(gmock_a$j)]]]] $var matcher_arg_as = [[$for j, \ [[GMOCK_MATCHER_(tn, $j, __VA_ARGS__) gmock_a$j]]]] $var matcher_as = [[$for j, [[gmock_a$j]]]] $var anything_matchers = [[$for j, \ [[::testing::A()]]]] // INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! #define GMOCK_METHOD$i[[]]_(tn, constness, ct, Method, ...) \ GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ $arg_as) constness { \ GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value == $i), \ this_method_does_not_take_$i[[]]_argument[[$if i != 1 [[s]]]]); \ GMOCK_MOCKER_($i, constness, Method).SetOwnerAndName(this, #Method); \ return GMOCK_MOCKER_($i, constness, Method).Invoke($as); \ } \ ::testing::MockSpec<__VA_ARGS__> \ gmock_##Method($matcher_arg_as) constness { \ GMOCK_MOCKER_($i, constness, Method).RegisterOwner(this); \ return GMOCK_MOCKER_($i, constness, Method).With($matcher_as); \ } \ ::testing::MockSpec<__VA_ARGS__> gmock_##Method( \ const ::testing::internal::WithoutMatchers&, \ constness ::testing::internal::Function<__VA_ARGS__>* ) const { \ return ::testing::internal::AdjustConstness_##constness(this)-> \ gmock_##Method($anything_matchers); \ } \ mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_($i, constness, Method) ]] $for i [[ #define MOCK_METHOD$i(m, ...) GMOCK_METHOD$i[[]]_(, , , m, __VA_ARGS__) ]] $for i [[ #define MOCK_CONST_METHOD$i(m, ...) GMOCK_METHOD$i[[]]_(, const, , m, __VA_ARGS__) ]] $for i [[ #define MOCK_METHOD$i[[]]_T(m, ...) GMOCK_METHOD$i[[]]_(typename, , , m, __VA_ARGS__) ]] $for i [[ #define MOCK_CONST_METHOD$i[[]]_T(m, ...) \ GMOCK_METHOD$i[[]]_(typename, const, , m, __VA_ARGS__) ]] $for i [[ #define MOCK_METHOD$i[[]]_WITH_CALLTYPE(ct, m, ...) \ GMOCK_METHOD$i[[]]_(, , ct, m, __VA_ARGS__) ]] $for i [[ #define MOCK_CONST_METHOD$i[[]]_WITH_CALLTYPE(ct, m, ...) \ GMOCK_METHOD$i[[]]_(, const, ct, m, __VA_ARGS__) ]] $for i [[ #define MOCK_METHOD$i[[]]_T_WITH_CALLTYPE(ct, m, ...) \ GMOCK_METHOD$i[[]]_(typename, , ct, m, __VA_ARGS__) ]] $for i [[ #define MOCK_CONST_METHOD$i[[]]_T_WITH_CALLTYPE(ct, m, ...) \ GMOCK_METHOD$i[[]]_(typename, const, ct, m, __VA_ARGS__) ]] // A MockFunction class has one mock method whose type is F. It is // useful when you just want your test code to emit some messages and // have Google Mock verify the right messages are sent (and perhaps at // the right times). For example, if you are exercising code: // // Foo(1); // Foo(2); // Foo(3); // // and want to verify that Foo(1) and Foo(3) both invoke // mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write: // // TEST(FooTest, InvokesBarCorrectly) { // MyMock mock; // MockFunction check; // { // InSequence s; // // EXPECT_CALL(mock, Bar("a")); // EXPECT_CALL(check, Call("1")); // EXPECT_CALL(check, Call("2")); // EXPECT_CALL(mock, Bar("a")); // } // Foo(1); // check.Call("1"); // Foo(2); // check.Call("2"); // Foo(3); // } // // The expectation spec says that the first Bar("a") must happen // before check point "1", the second Bar("a") must happen after check // point "2", and nothing should happen between the two check // points. The explicit check points make it easy to tell which // Bar("a") is called by which call to Foo(). // // MockFunction can also be used to exercise code that accepts // std::function callbacks. To do so, use AsStdFunction() method // to create std::function proxy forwarding to original object's Call. // Example: // // TEST(FooTest, RunsCallbackWithBarArgument) { // MockFunction callback; // EXPECT_CALL(callback, Call("bar")).WillOnce(Return(1)); // Foo(callback.AsStdFunction()); // } template class MockFunction; $for i [[ $range j 0..i-1 $var ArgTypes = [[$for j, [[A$j]]]] $var ArgValues = [[$for j, [[::std::move(a$j)]]]] $var ArgDecls = [[$for j, [[A$j a$j]]]] template class MockFunction { public: MockFunction() {} MOCK_METHOD$i[[]]_T(Call, R($ArgTypes)); #if GTEST_HAS_STD_FUNCTION_ ::std::function AsStdFunction() { return [this]($ArgDecls) -> R { return this->Call($ArgValues); }; } #endif // GTEST_HAS_STD_FUNCTION_ private: GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); }; ]] } // namespace testing #endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-generated-matchers.hopentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-generated-m0000664000175000017500000026130015110656150031526 0ustar meme// This file was GENERATED by command: // pump.py gmock-generated-matchers.h.pump // DO NOT EDIT BY HAND!!! // Copyright 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // Google Mock - a framework for writing C++ mock classes. // // This file implements some commonly used variadic matchers. #ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ #define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ #include #include #include #include #include "gmock/gmock-matchers.h" namespace testing { namespace internal { // The type of the i-th (0-based) field of Tuple. #define GMOCK_FIELD_TYPE_(Tuple, i) \ typename ::testing::tuple_element::type // TupleFields is for selecting fields from a // tuple of type Tuple. It has two members: // // type: a tuple type whose i-th field is the ki-th field of Tuple. // GetSelectedFields(t): returns fields k0, ..., and kn of t as a tuple. // // For example, in class TupleFields, 2, 0>, we have: // // type is tuple, and // GetSelectedFields(make_tuple(true, 'a', 42)) is (42, true). template class TupleFields; // This generic version is used when there are 10 selectors. template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t), get(t), get(t), get(t), get(t), get(t), get(t), get(t), get(t)); } }; // The following specialization is used for 0 ~ 9 selectors. template class TupleFields { public: typedef ::testing::tuple<> type; static type GetSelectedFields(const Tuple& /* t */) { return type(); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t)); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t)); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t), get(t)); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t), get(t), get(t)); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t), get(t), get(t), get(t)); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t), get(t), get(t), get(t), get(t)); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t), get(t), get(t), get(t), get(t), get(t)); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t), get(t), get(t), get(t), get(t), get(t), get(t)); } }; template class TupleFields { public: typedef ::testing::tuple type; static type GetSelectedFields(const Tuple& t) { return type(get(t), get(t), get(t), get(t), get(t), get(t), get(t), get(t), get(t)); } }; #undef GMOCK_FIELD_TYPE_ // Implements the Args() matcher. template class ArgsMatcherImpl : public MatcherInterface { public: // ArgsTuple may have top-level const or reference modifiers. typedef GTEST_REMOVE_REFERENCE_AND_CONST_(ArgsTuple) RawArgsTuple; typedef typename internal::TupleFields::type SelectedArgs; typedef Matcher MonomorphicInnerMatcher; template explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) : inner_matcher_(SafeMatcherCast(inner_matcher)) {} virtual bool MatchAndExplain(ArgsTuple args, MatchResultListener* listener) const { const SelectedArgs& selected_args = GetSelectedArgs(args); if (!listener->IsInterested()) return inner_matcher_.Matches(selected_args); PrintIndices(listener->stream()); *listener << "are " << PrintToString(selected_args); StringMatchResultListener inner_listener; const bool match = inner_matcher_.MatchAndExplain(selected_args, &inner_listener); PrintIfNotEmpty(inner_listener.str(), listener->stream()); return match; } virtual void DescribeTo(::std::ostream* os) const { *os << "are a tuple "; PrintIndices(os); inner_matcher_.DescribeTo(os); } virtual void DescribeNegationTo(::std::ostream* os) const { *os << "are a tuple "; PrintIndices(os); inner_matcher_.DescribeNegationTo(os); } private: static SelectedArgs GetSelectedArgs(ArgsTuple args) { return TupleFields::GetSelectedFields(args); } // Prints the indices of the selected fields. static void PrintIndices(::std::ostream* os) { *os << "whose fields ("; const int indices[10] = { k0, k1, k2, k3, k4, k5, k6, k7, k8, k9 }; for (int i = 0; i < 10; i++) { if (indices[i] < 0) break; if (i >= 1) *os << ", "; *os << "#" << indices[i]; } *os << ") "; } const MonomorphicInnerMatcher inner_matcher_; GTEST_DISALLOW_ASSIGN_(ArgsMatcherImpl); }; template class ArgsMatcher { public: explicit ArgsMatcher(const InnerMatcher& inner_matcher) : inner_matcher_(inner_matcher) {} template operator Matcher() const { return MakeMatcher(new ArgsMatcherImpl(inner_matcher_)); } private: const InnerMatcher inner_matcher_; GTEST_DISALLOW_ASSIGN_(ArgsMatcher); }; // A set of metafunctions for computing the result type of AllOf. // AllOf(m1, ..., mN) returns // AllOfResultN::type. // Although AllOf isn't defined for one argument, AllOfResult1 is defined // to simplify the implementation. template struct AllOfResult1 { typedef M1 type; }; template struct AllOfResult2 { typedef BothOfMatcher< typename AllOfResult1::type, typename AllOfResult1::type > type; }; template struct AllOfResult3 { typedef BothOfMatcher< typename AllOfResult1::type, typename AllOfResult2::type > type; }; template struct AllOfResult4 { typedef BothOfMatcher< typename AllOfResult2::type, typename AllOfResult2::type > type; }; template struct AllOfResult5 { typedef BothOfMatcher< typename AllOfResult2::type, typename AllOfResult3::type > type; }; template struct AllOfResult6 { typedef BothOfMatcher< typename AllOfResult3::type, typename AllOfResult3::type > type; }; template struct AllOfResult7 { typedef BothOfMatcher< typename AllOfResult3::type, typename AllOfResult4::type > type; }; template struct AllOfResult8 { typedef BothOfMatcher< typename AllOfResult4::type, typename AllOfResult4::type > type; }; template struct AllOfResult9 { typedef BothOfMatcher< typename AllOfResult4::type, typename AllOfResult5::type > type; }; template struct AllOfResult10 { typedef BothOfMatcher< typename AllOfResult5::type, typename AllOfResult5::type > type; }; // A set of metafunctions for computing the result type of AnyOf. // AnyOf(m1, ..., mN) returns // AnyOfResultN::type. // Although AnyOf isn't defined for one argument, AnyOfResult1 is defined // to simplify the implementation. template struct AnyOfResult1 { typedef M1 type; }; template struct AnyOfResult2 { typedef EitherOfMatcher< typename AnyOfResult1::type, typename AnyOfResult1::type > type; }; template struct AnyOfResult3 { typedef EitherOfMatcher< typename AnyOfResult1::type, typename AnyOfResult2::type > type; }; template struct AnyOfResult4 { typedef EitherOfMatcher< typename AnyOfResult2::type, typename AnyOfResult2::type > type; }; template struct AnyOfResult5 { typedef EitherOfMatcher< typename AnyOfResult2::type, typename AnyOfResult3::type > type; }; template struct AnyOfResult6 { typedef EitherOfMatcher< typename AnyOfResult3::type, typename AnyOfResult3::type > type; }; template struct AnyOfResult7 { typedef EitherOfMatcher< typename AnyOfResult3::type, typename AnyOfResult4::type > type; }; template struct AnyOfResult8 { typedef EitherOfMatcher< typename AnyOfResult4::type, typename AnyOfResult4::type > type; }; template struct AnyOfResult9 { typedef EitherOfMatcher< typename AnyOfResult4::type, typename AnyOfResult5::type > type; }; template struct AnyOfResult10 { typedef EitherOfMatcher< typename AnyOfResult5::type, typename AnyOfResult5::type > type; }; } // namespace internal // Args(a_matcher) matches a tuple if the selected // fields of it matches a_matcher. C++ doesn't support default // arguments for function templates, so we have to overload it. template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } template inline internal::ArgsMatcher Args(const InnerMatcher& matcher) { return internal::ArgsMatcher(matcher); } // ElementsAre(e_1, e_2, ... e_n) matches an STL-style container with // n elements, where the i-th element in the container must // match the i-th argument in the list. Each argument of // ElementsAre() can be either a value or a matcher. We support up to // 10 arguments. // // The use of DecayArray in the implementation allows ElementsAre() // to accept string literals, whose type is const char[N], but we // want to treat them as const char*. // // NOTE: Since ElementsAre() cares about the order of the elements, it // must not be used with containers whose elements's order is // undefined (e.g. hash_map). inline internal::ElementsAreMatcher< ::testing::tuple<> > ElementsAre() { typedef ::testing::tuple<> Args; return internal::ElementsAreMatcher(Args()); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type> > ElementsAre(const T1& e1) { typedef ::testing::tuple< typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2, const T3& e3) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2, e3)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2, e3, e4)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6, const T7& e7) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6, const T7& e7, const T8& e8) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, e8)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, e8, e9)); } template inline internal::ElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9, const T10& e10) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10)); } // UnorderedElementsAre(e_1, e_2, ..., e_n) is an ElementsAre extension // that matches n elements in any order. We support up to n=10 arguments. // // If you have >10 elements, consider UnorderedElementsAreArray() or // UnorderedPointwise() instead. inline internal::UnorderedElementsAreMatcher< ::testing::tuple<> > UnorderedElementsAre() { typedef ::testing::tuple<> Args; return internal::UnorderedElementsAreMatcher(Args()); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1) { typedef ::testing::tuple< typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6, const T7& e7) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6, const T7& e7, const T8& e8) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, e8)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, e8, e9)); } template inline internal::UnorderedElementsAreMatcher< ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> > UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9, const T10& e10) { typedef ::testing::tuple< typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type, typename internal::DecayArray::type> Args; return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10)); } // AllOf(m1, m2, ..., mk) matches any value that matches all of the given // sub-matchers. AllOf is called fully qualified to prevent ADL from firing. template inline typename internal::AllOfResult2::type AllOf(M1 m1, M2 m2) { return typename internal::AllOfResult2::type( m1, m2); } template inline typename internal::AllOfResult3::type AllOf(M1 m1, M2 m2, M3 m3) { return typename internal::AllOfResult3::type( m1, ::testing::AllOf(m2, m3)); } template inline typename internal::AllOfResult4::type AllOf(M1 m1, M2 m2, M3 m3, M4 m4) { return typename internal::AllOfResult4::type( ::testing::AllOf(m1, m2), ::testing::AllOf(m3, m4)); } template inline typename internal::AllOfResult5::type AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5) { return typename internal::AllOfResult5::type( ::testing::AllOf(m1, m2), ::testing::AllOf(m3, m4, m5)); } template inline typename internal::AllOfResult6::type AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6) { return typename internal::AllOfResult6::type( ::testing::AllOf(m1, m2, m3), ::testing::AllOf(m4, m5, m6)); } template inline typename internal::AllOfResult7::type AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7) { return typename internal::AllOfResult7::type( ::testing::AllOf(m1, m2, m3), ::testing::AllOf(m4, m5, m6, m7)); } template inline typename internal::AllOfResult8::type AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8) { return typename internal::AllOfResult8::type( ::testing::AllOf(m1, m2, m3, m4), ::testing::AllOf(m5, m6, m7, m8)); } template inline typename internal::AllOfResult9::type AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9) { return typename internal::AllOfResult9::type( ::testing::AllOf(m1, m2, m3, m4), ::testing::AllOf(m5, m6, m7, m8, m9)); } template inline typename internal::AllOfResult10::type AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9, M10 m10) { return typename internal::AllOfResult10::type( ::testing::AllOf(m1, m2, m3, m4, m5), ::testing::AllOf(m6, m7, m8, m9, m10)); } // AnyOf(m1, m2, ..., mk) matches any value that matches any of the given // sub-matchers. AnyOf is called fully qualified to prevent ADL from firing. template inline typename internal::AnyOfResult2::type AnyOf(M1 m1, M2 m2) { return typename internal::AnyOfResult2::type( m1, m2); } template inline typename internal::AnyOfResult3::type AnyOf(M1 m1, M2 m2, M3 m3) { return typename internal::AnyOfResult3::type( m1, ::testing::AnyOf(m2, m3)); } template inline typename internal::AnyOfResult4::type AnyOf(M1 m1, M2 m2, M3 m3, M4 m4) { return typename internal::AnyOfResult4::type( ::testing::AnyOf(m1, m2), ::testing::AnyOf(m3, m4)); } template inline typename internal::AnyOfResult5::type AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5) { return typename internal::AnyOfResult5::type( ::testing::AnyOf(m1, m2), ::testing::AnyOf(m3, m4, m5)); } template inline typename internal::AnyOfResult6::type AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6) { return typename internal::AnyOfResult6::type( ::testing::AnyOf(m1, m2, m3), ::testing::AnyOf(m4, m5, m6)); } template inline typename internal::AnyOfResult7::type AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7) { return typename internal::AnyOfResult7::type( ::testing::AnyOf(m1, m2, m3), ::testing::AnyOf(m4, m5, m6, m7)); } template inline typename internal::AnyOfResult8::type AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8) { return typename internal::AnyOfResult8::type( ::testing::AnyOf(m1, m2, m3, m4), ::testing::AnyOf(m5, m6, m7, m8)); } template inline typename internal::AnyOfResult9::type AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9) { return typename internal::AnyOfResult9::type( ::testing::AnyOf(m1, m2, m3, m4), ::testing::AnyOf(m5, m6, m7, m8, m9)); } template inline typename internal::AnyOfResult10::type AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9, M10 m10) { return typename internal::AnyOfResult10::type( ::testing::AnyOf(m1, m2, m3, m4, m5), ::testing::AnyOf(m6, m7, m8, m9, m10)); } } // namespace testing // The MATCHER* family of macros can be used in a namespace scope to // define custom matchers easily. // // Basic Usage // =========== // // The syntax // // MATCHER(name, description_string) { statements; } // // defines a matcher with the given name that executes the statements, // which must return a bool to indicate if the match succeeds. Inside // the statements, you can refer to the value being matched by 'arg', // and refer to its type by 'arg_type'. // // The description string documents what the matcher does, and is used // to generate the failure message when the match fails. Since a // MATCHER() is usually defined in a header file shared by multiple // C++ source files, we require the description to be a C-string // literal to avoid possible side effects. It can be empty, in which // case we'll use the sequence of words in the matcher name as the // description. // // For example: // // MATCHER(IsEven, "") { return (arg % 2) == 0; } // // allows you to write // // // Expects mock_foo.Bar(n) to be called where n is even. // EXPECT_CALL(mock_foo, Bar(IsEven())); // // or, // // // Verifies that the value of some_expression is even. // EXPECT_THAT(some_expression, IsEven()); // // If the above assertion fails, it will print something like: // // Value of: some_expression // Expected: is even // Actual: 7 // // where the description "is even" is automatically calculated from the // matcher name IsEven. // // Argument Type // ============= // // Note that the type of the value being matched (arg_type) is // determined by the context in which you use the matcher and is // supplied to you by the compiler, so you don't need to worry about // declaring it (nor can you). This allows the matcher to be // polymorphic. For example, IsEven() can be used to match any type // where the value of "(arg % 2) == 0" can be implicitly converted to // a bool. In the "Bar(IsEven())" example above, if method Bar() // takes an int, 'arg_type' will be int; if it takes an unsigned long, // 'arg_type' will be unsigned long; and so on. // // Parameterizing Matchers // ======================= // // Sometimes you'll want to parameterize the matcher. For that you // can use another macro: // // MATCHER_P(name, param_name, description_string) { statements; } // // For example: // // MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } // // will allow you to write: // // EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); // // which may lead to this message (assuming n is 10): // // Value of: Blah("a") // Expected: has absolute value 10 // Actual: -9 // // Note that both the matcher description and its parameter are // printed, making the message human-friendly. // // In the matcher definition body, you can write 'foo_type' to // reference the type of a parameter named 'foo'. For example, in the // body of MATCHER_P(HasAbsoluteValue, value) above, you can write // 'value_type' to refer to the type of 'value'. // // We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P10 to // support multi-parameter matchers. // // Describing Parameterized Matchers // ================================= // // The last argument to MATCHER*() is a string-typed expression. The // expression can reference all of the matcher's parameters and a // special bool-typed variable named 'negation'. When 'negation' is // false, the expression should evaluate to the matcher's description; // otherwise it should evaluate to the description of the negation of // the matcher. For example, // // using testing::PrintToString; // // MATCHER_P2(InClosedRange, low, hi, // std::string(negation ? "is not" : "is") + " in range [" + // PrintToString(low) + ", " + PrintToString(hi) + "]") { // return low <= arg && arg <= hi; // } // ... // EXPECT_THAT(3, InClosedRange(4, 6)); // EXPECT_THAT(3, Not(InClosedRange(2, 4))); // // would generate two failures that contain the text: // // Expected: is in range [4, 6] // ... // Expected: is not in range [2, 4] // // If you specify "" as the description, the failure message will // contain the sequence of words in the matcher name followed by the // parameter values printed as a tuple. For example, // // MATCHER_P2(InClosedRange, low, hi, "") { ... } // ... // EXPECT_THAT(3, InClosedRange(4, 6)); // EXPECT_THAT(3, Not(InClosedRange(2, 4))); // // would generate two failures that contain the text: // // Expected: in closed range (4, 6) // ... // Expected: not (in closed range (2, 4)) // // Types of Matcher Parameters // =========================== // // For the purpose of typing, you can view // // MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } // // as shorthand for // // template // FooMatcherPk // Foo(p1_type p1, ..., pk_type pk) { ... } // // When you write Foo(v1, ..., vk), the compiler infers the types of // the parameters v1, ..., and vk for you. If you are not happy with // the result of the type inference, you can specify the types by // explicitly instantiating the template, as in Foo(5, // false). As said earlier, you don't get to (or need to) specify // 'arg_type' as that's determined by the context in which the matcher // is used. You can assign the result of expression Foo(p1, ..., pk) // to a variable of type FooMatcherPk. This // can be useful when composing matchers. // // While you can instantiate a matcher template with reference types, // passing the parameters by pointer usually makes your code more // readable. If, however, you still want to pass a parameter by // reference, be aware that in the failure message generated by the // matcher you will see the value of the referenced object but not its // address. // // Explaining Match Results // ======================== // // Sometimes the matcher description alone isn't enough to explain why // the match has failed or succeeded. For example, when expecting a // long string, it can be very helpful to also print the diff between // the expected string and the actual one. To achieve that, you can // optionally stream additional information to a special variable // named result_listener, whose type is a pointer to class // MatchResultListener: // // MATCHER_P(EqualsLongString, str, "") { // if (arg == str) return true; // // *result_listener << "the difference: " /// << DiffStrings(str, arg); // return false; // } // // Overloading Matchers // ==================== // // You can overload matchers with different numbers of parameters: // // MATCHER_P(Blah, a, description_string1) { ... } // MATCHER_P2(Blah, a, b, description_string2) { ... } // // Caveats // ======= // // When defining a new matcher, you should also consider implementing // MatcherInterface or using MakePolymorphicMatcher(). These // approaches require more work than the MATCHER* macros, but also // give you more control on the types of the value being matched and // the matcher parameters, which may leads to better compiler error // messages when the matcher is used wrong. They also allow // overloading matchers based on parameter types (as opposed to just // based on the number of parameters). // // MATCHER*() can only be used in a namespace scope. The reason is // that C++ doesn't yet allow function-local types to be used to // instantiate templates. The up-coming C++0x standard will fix this. // Once that's done, we'll consider supporting using MATCHER*() inside // a function. // // More Information // ================ // // To learn more about using these macros, please search for 'MATCHER' // on https://github.com/google/googletest/blob/master/googlemock/docs/ // CookBook.md #define MATCHER(name, description)\ class name##Matcher {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl()\ {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple<>()));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl());\ }\ name##Matcher() {\ }\ private:\ };\ inline name##Matcher name() {\ return name##Matcher();\ }\ template \ bool name##Matcher::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P(name, p0, description)\ template \ class name##MatcherP {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ explicit gmock_Impl(p0##_type gmock_p0)\ : p0(::testing::internal::move(gmock_p0)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0));\ }\ explicit name##MatcherP(p0##_type gmock_p0) : \ p0(::testing::internal::move(gmock_p0)) {\ }\ p0##_type const p0;\ private:\ };\ template \ inline name##MatcherP name(p0##_type p0) {\ return name##MatcherP(p0);\ }\ template \ template \ bool name##MatcherP::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P2(name, p0, p1, description)\ template \ class name##MatcherP2 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1));\ }\ name##MatcherP2(p0##_type gmock_p0, \ p1##_type gmock_p1) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ private:\ };\ template \ inline name##MatcherP2 name(p0##_type p0, \ p1##_type p1) {\ return name##MatcherP2(p0, p1);\ }\ template \ template \ bool name##MatcherP2::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P3(name, p0, p1, p2, description)\ template \ class name##MatcherP3 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1, \ p2)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1, p2));\ }\ name##MatcherP3(p0##_type gmock_p0, p1##_type gmock_p1, \ p2##_type gmock_p2) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ private:\ };\ template \ inline name##MatcherP3 name(p0##_type p0, \ p1##_type p1, p2##_type p2) {\ return name##MatcherP3(p0, p1, p2);\ }\ template \ template \ bool name##MatcherP3::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P4(name, p0, p1, p2, p3, description)\ template \ class name##MatcherP4 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ p3##_type gmock_p3)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1, p2, p3)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1, p2, p3));\ }\ name##MatcherP4(p0##_type gmock_p0, p1##_type gmock_p1, \ p2##_type gmock_p2, \ p3##_type gmock_p3) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ private:\ };\ template \ inline name##MatcherP4 name(p0##_type p0, p1##_type p1, p2##_type p2, \ p3##_type p3) {\ return name##MatcherP4(p0, \ p1, p2, p3);\ }\ template \ template \ bool name##MatcherP4::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P5(name, p0, p1, p2, p3, p4, description)\ template \ class name##MatcherP5 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ p3##_type gmock_p3, p4##_type gmock_p4)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1, p2, p3, p4)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1, p2, p3, p4));\ }\ name##MatcherP5(p0##_type gmock_p0, p1##_type gmock_p1, \ p2##_type gmock_p2, p3##_type gmock_p3, \ p4##_type gmock_p4) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ private:\ };\ template \ inline name##MatcherP5 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ p4##_type p4) {\ return name##MatcherP5(p0, p1, p2, p3, p4);\ }\ template \ template \ bool name##MatcherP5::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P6(name, p0, p1, p2, p3, p4, p5, description)\ template \ class name##MatcherP6 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1, p2, p3, p4, p5)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1, p2, p3, p4, p5));\ }\ name##MatcherP6(p0##_type gmock_p0, p1##_type gmock_p1, \ p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ p5##_type gmock_p5) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ private:\ };\ template \ inline name##MatcherP6 name(p0##_type p0, p1##_type p1, p2##_type p2, \ p3##_type p3, p4##_type p4, p5##_type p5) {\ return name##MatcherP6(p0, p1, p2, p3, p4, p5);\ }\ template \ template \ bool name##MatcherP6::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P7(name, p0, p1, p2, p3, p4, p5, p6, description)\ template \ class name##MatcherP7 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ p6##_type gmock_p6)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)), \ p6(::testing::internal::move(gmock_p6)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ p6##_type const p6;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1, p2, p3, p4, p5, \ p6)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1, p2, p3, p4, p5, p6));\ }\ name##MatcherP7(p0##_type gmock_p0, p1##_type gmock_p1, \ p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ p5##_type gmock_p5, \ p6##_type gmock_p6) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)), \ p6(::testing::internal::move(gmock_p6)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ p6##_type const p6;\ private:\ };\ template \ inline name##MatcherP7 name(p0##_type p0, p1##_type p1, \ p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ p6##_type p6) {\ return name##MatcherP7(p0, p1, p2, p3, p4, p5, p6);\ }\ template \ template \ bool name##MatcherP7::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P8(name, p0, p1, p2, p3, p4, p5, p6, p7, description)\ template \ class name##MatcherP8 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ p6##_type gmock_p6, p7##_type gmock_p7)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)), \ p6(::testing::internal::move(gmock_p6)), \ p7(::testing::internal::move(gmock_p7)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ p6##_type const p6;\ p7##_type const p7;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1, p2, \ p3, p4, p5, p6, p7)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7));\ }\ name##MatcherP8(p0##_type gmock_p0, p1##_type gmock_p1, \ p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ p5##_type gmock_p5, p6##_type gmock_p6, \ p7##_type gmock_p7) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)), \ p6(::testing::internal::move(gmock_p6)), \ p7(::testing::internal::move(gmock_p7)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ p6##_type const p6;\ p7##_type const p7;\ private:\ };\ template \ inline name##MatcherP8 name(p0##_type p0, \ p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ p6##_type p6, p7##_type p7) {\ return name##MatcherP8(p0, p1, p2, p3, p4, p5, \ p6, p7);\ }\ template \ template \ bool name##MatcherP8::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, description)\ template \ class name##MatcherP9 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)), \ p6(::testing::internal::move(gmock_p6)), \ p7(::testing::internal::move(gmock_p7)), \ p8(::testing::internal::move(gmock_p8)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ p6##_type const p6;\ p7##_type const p7;\ p8##_type const p8;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1, p2, p3, p4, p5, p6, p7, p8)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, p8));\ }\ name##MatcherP9(p0##_type gmock_p0, p1##_type gmock_p1, \ p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ p8##_type gmock_p8) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)), \ p6(::testing::internal::move(gmock_p6)), \ p7(::testing::internal::move(gmock_p7)), \ p8(::testing::internal::move(gmock_p8)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ p6##_type const p6;\ p7##_type const p7;\ p8##_type const p8;\ private:\ };\ template \ inline name##MatcherP9 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, \ p8##_type p8) {\ return name##MatcherP9(p0, p1, p2, \ p3, p4, p5, p6, p7, p8);\ }\ template \ template \ bool name##MatcherP9::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #define MATCHER_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, description)\ template \ class name##MatcherP10 {\ public:\ template \ class gmock_Impl : public ::testing::MatcherInterface<\ GTEST_REFERENCE_TO_CONST_(arg_type)> {\ public:\ gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ p9##_type gmock_p9)\ : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)), \ p6(::testing::internal::move(gmock_p6)), \ p7(::testing::internal::move(gmock_p7)), \ p8(::testing::internal::move(gmock_p8)), \ p9(::testing::internal::move(gmock_p9)) {}\ virtual bool MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener) const;\ virtual void DescribeTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(false);\ }\ virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ *gmock_os << FormatDescription(true);\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ p6##_type const p6;\ p7##_type const p7;\ p8##_type const p8;\ p9##_type const p9;\ private:\ ::std::string FormatDescription(bool negation) const {\ ::std::string gmock_description = (description);\ if (!gmock_description.empty())\ return gmock_description;\ return ::testing::internal::FormatMatcherDescription(\ negation, #name, \ ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ ::testing::tuple(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)));\ }\ };\ template \ operator ::testing::Matcher() const {\ return ::testing::Matcher(\ new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9));\ }\ name##MatcherP10(p0##_type gmock_p0, p1##_type gmock_p1, \ p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ p8##_type gmock_p8, \ p9##_type gmock_p9) : p0(::testing::internal::move(gmock_p0)), \ p1(::testing::internal::move(gmock_p1)), \ p2(::testing::internal::move(gmock_p2)), \ p3(::testing::internal::move(gmock_p3)), \ p4(::testing::internal::move(gmock_p4)), \ p5(::testing::internal::move(gmock_p5)), \ p6(::testing::internal::move(gmock_p6)), \ p7(::testing::internal::move(gmock_p7)), \ p8(::testing::internal::move(gmock_p8)), \ p9(::testing::internal::move(gmock_p9)) {\ }\ p0##_type const p0;\ p1##_type const p1;\ p2##_type const p2;\ p3##_type const p3;\ p4##_type const p4;\ p5##_type const p5;\ p6##_type const p6;\ p7##_type const p7;\ p8##_type const p8;\ p9##_type const p9;\ private:\ };\ template \ inline name##MatcherP10 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ p9##_type p9) {\ return name##MatcherP10(p0, \ p1, p2, p3, p4, p5, p6, p7, p8, p9);\ }\ template \ template \ bool name##MatcherP10::gmock_Impl::MatchAndExplain(\ GTEST_REFERENCE_TO_CONST_(arg_type) arg,\ ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ const #endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ opentimelineio-0.18.1/src/deps/rapidjson/thirdparty/gtest/googlemock/include/gmock/gmock-matchers.h0000664000175000017500000056351015110656150031402 0ustar meme// Copyright 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER 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. // // Author: wan@google.com (Zhanyong Wan) // Google Mock - a framework for writing C++ mock classes. // // This file implements some commonly used argument matchers. More // matchers can be defined by the user implementing the // MatcherInterface interface if necessary. #ifndef GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ #define GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ #include #include #include #include #include // NOLINT #include #include #include #include #include "gtest/gtest.h" #include "gmock/internal/gmock-internal-utils.h" #include "gmock/internal/gmock-port.h" #if GTEST_HAS_STD_INITIALIZER_LIST_ # include // NOLINT -- must be after gtest.h #endif namespace testing { // To implement a matcher Foo for type T, define: // 1. a class FooMatcherImpl that implements the // MatcherInterface interface, and // 2. a factory function that creates a Matcher object from a // FooMatcherImpl*. // // The two-level delegation design makes it possible to allow a user // to write "v" instead of "Eq(v)" where a Matcher is expected, which // is impossible if we pass matchers by pointers. It also eases // ownership management as Matcher objects can now be copied like // plain values. // MatchResultListener is an abstract class. Its << operator can be // used by a matcher to explain why a value matches or doesn't match. // // TODO(wan@google.com): add method // bool InterestedInWhy(bool result) const; // to indicate whether the listener is interested in why the match // result is 'result'. class MatchResultListener { public: // Creates a listener object with the given underlying ostream. The // listener does not own the ostream, and does not dereference it // in the constructor or destructor. explicit MatchResultListener(::std::ostream* os) : stream_(os) {} virtual ~MatchResultListener() = 0; // Makes this class abstract. // Streams x to the underlying ostream; does nothing if the ostream // is NULL. template MatchResultListener& operator<<(const T& x) { if (stream_ != NULL) *stream_ << x; return *this; } // Returns the underlying ostream. ::std::ostream* stream() { return stream_; } // Returns true iff the listener is interested in an explanation of // the match result. A matcher's MatchAndExplain() method can use // this information to avoid generating the explanation when no one // intends to hear it. bool IsInterested() const { return stream_ != NULL; } private: ::std::ostream* const stream_; GTEST_DISALLOW_COPY_AND_ASSIGN_(MatchResultListener); }; inline MatchResultListener::~MatchResultListener() { } // An instance of a subclass of this knows how to describe itself as a // matcher. class MatcherDescriberInterface { public: virtual ~MatcherDescriberInterface() {} // Describes this matcher to an ostream. The function should print // a verb phrase that describes the property a value matching this // matcher should have. The subject of the verb phrase is the value // being matched. For example, the DescribeTo() method of the Gt(7) // matcher prints "is greater than 7". virtual void DescribeTo(::std::ostream* os) const = 0; // Describes the negation of this matcher to an ostream. For // example, if the description of this matcher is "is greater than // 7", the negated description could be "is not greater than 7". // You are not required to override this when implementing // MatcherInterface, but it is highly advised so that your matcher // can produce good error messages. virtual void DescribeNegationTo(::std::ostream* os) const { *os << "not ("; DescribeTo(os); *os << ")"; } }; // The implementation of a matcher. template class MatcherInterface : public MatcherDescriberInterface { public: // Returns true iff the matcher matches x; also explains the match // result to 'listener' if necessary (see the next paragraph), in // the form of a non-restrictive relative clause ("which ...", // "whose ...", etc) that describes x. For example, the // MatchAndExplain() method of the Pointee(...) matcher should // generate an explanation like "which points to ...". // // Implementations of MatchAndExplain() should add an explanation of // the match result *if and only if* they can provide additional // information that's not already present (or not obvious) in the // print-out of x and the matcher's description. Whether the match // succeeds is not a factor in deciding whether an explanation is // needed, as sometimes the caller needs to print a failure message // when the match succeeds (e.g. when the matcher is used inside // Not()). // // For example, a "has at least 10 elements" matcher should explain // what the actual element count is, regardless of the match result, // as it is useful information to the reader; on the other hand, an // "is empty" matcher probably only needs to explain what the actual // size is when the match fails, as it's redundant to say that the // size is 0 when the value is already known to be empty. // // You should override this method when defining a new matcher. // // It's the responsibility of the caller (Google Mock) to guarantee // that 'listener' is not NULL. This helps to simplify a matcher's // implementation when it doesn't care about the performance, as it // can talk to 'listener' without checking its validity first. // However, in order to implement dummy listeners efficiently, // listener->stream() may be NULL. virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0; // Inherits these methods from MatcherDescriberInterface: // virtual void DescribeTo(::std::ostream* os) const = 0; // virtual void DescribeNegationTo(::std::ostream* os) const; }; namespace internal { // Converts a MatcherInterface to a MatcherInterface. template class MatcherInterfaceAdapter : public MatcherInterface { public: explicit MatcherInterfaceAdapter(const MatcherInterface* impl) : impl_(impl) {} virtual ~MatcherInterfaceAdapter() { delete impl_; } virtual void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } virtual void DescribeNegationTo(::std::ostream* os) const { impl_->DescribeNegationTo(os); } virtual bool MatchAndExplain(const T& x, MatchResultListener* listener) const { return impl_->MatchAndExplain(x, listener); } private: const MatcherInterface* const impl_; GTEST_DISALLOW_COPY_AND_ASSIGN_(MatcherInterfaceAdapter); }; } // namespace internal // A match result listener that stores the explanation in a string. class StringMatchResultListener : public MatchResultListener { public: StringMatchResultListener() : MatchResultListener(&ss_) {} // Returns the explanation accumulated so far. std::string str() const { return ss_.str(); } // Clears the explanation accumulated so far. void Clear() { ss_.str(""); } private: ::std::stringstream ss_; GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener); }; namespace internal { struct AnyEq { template bool operator()(const A& a, const B& b) const { return a == b; } }; struct AnyNe { template bool operator()(const A& a, const B& b) const { return a != b; } }; struct AnyLt { template bool operator()(const A& a, const B& b) const { return a < b; } }; struct AnyGt { template bool operator()(const A& a, const B& b) const { return a > b; } }; struct AnyLe { template bool operator()(const A& a, const B& b) const { return a <= b; } }; struct AnyGe { template bool operator()(const A& a, const B& b) const { return a >= b; } }; // A match result listener that ignores the explanation. class DummyMatchResultListener : public MatchResultListener { public: DummyMatchResultListener() : MatchResultListener(NULL) {} private: GTEST_DISALLOW_COPY_AND_ASSIGN_(DummyMatchResultListener); }; // A match result listener that forwards the explanation to a given // ostream. The difference between this and MatchResultListener is // that the former is concrete. class StreamMatchResultListener : public MatchResultListener { public: explicit StreamMatchResultListener(::std::ostream* os) : MatchResultListener(os) {} private: GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener); }; // An internal class for implementing Matcher, which will derive // from it. We put functionalities common to all Matcher // specializations here to avoid code duplication. template class MatcherBase { public: // Returns true iff the matcher matches x; also explains the match // result to 'listener'. bool MatchAndExplain(GTEST_REFERENCE_TO_CONST_(T) x, MatchResultListener* listener) const { return impl_->MatchAndExplain(x, listener); } // Returns true iff this matcher matches x. bool Matches(GTEST_REFERENCE_TO_CONST_(T) x) const { DummyMatchResultListener dummy; return MatchAndExplain(x, &dummy); } // Describes this matcher to an ostream. void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } // Describes the negation of this matcher to an ostream. void DescribeNegationTo(::std::ostream* os) const { impl_->DescribeNegationTo(os); } // Explains why x matches, or doesn't match, the matcher. void ExplainMatchResultTo(GTEST_REFERENCE_TO_CONST_(T) x, ::std::ostream* os) const { StreamMatchResultListener listener(os); MatchAndExplain(x, &listener); } // Returns the describer for this matcher object; retains ownership // of the describer, which is only guaranteed to be alive when // this matcher object is alive. const MatcherDescriberInterface* GetDescriber() const { return impl_.get(); } protected: MatcherBase() {} // Constructs a matcher from its implementation. explicit MatcherBase( const MatcherInterface* impl) : impl_(impl) {} template explicit MatcherBase( const MatcherInterface* impl, typename internal::EnableIf< !internal::IsSame::value>::type* = NULL) : impl_(new internal::MatcherInterfaceAdapter(impl)) {} virtual ~MatcherBase() {} private: // shared_ptr (util/gtl/shared_ptr.h) and linked_ptr have similar // interfaces. The former dynamically allocates a chunk of memory // to hold the reference count, while the latter tracks all // references using a circular linked list without allocating // memory. It has been observed that linked_ptr performs better in // typical scenarios. However, shared_ptr can out-perform // linked_ptr when there are many more uses of the copy constructor // than the default constructor. // // If performance becomes a problem, we should see if using // shared_ptr helps. ::testing::internal::linked_ptr< const MatcherInterface > impl_; }; } // namespace internal // A Matcher is a copyable and IMMUTABLE (except by assignment) // object that can check whether a value of type T matches. The // implementation of Matcher is just a linked_ptr to const // MatcherInterface, so copying is fairly cheap. Don't inherit // from Matcher! template class Matcher : public internal::MatcherBase { public: // Constructs a null matcher. Needed for storing Matcher objects in STL // containers. A default-constructed matcher is not yet initialized. You // cannot use it until a valid value has been assigned to it. explicit Matcher() {} // NOLINT // Constructs a matcher from its implementation. explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase(impl) {} template explicit Matcher(const MatcherInterface* impl, typename internal::EnableIf::value>::type* = NULL) : internal::MatcherBase(impl) {} // Implicit constructor here allows people to write // EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes Matcher(T value); // NOLINT }; // The following two specializations allow the user to write str // instead of Eq(str) and "foo" instead of Eq("foo") when a std::string // matcher is expected. template <> class GTEST_API_ Matcher : public internal::MatcherBase { public: Matcher() {} explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase(impl) {} // Allows the user to write str instead of Eq(str) sometimes, where // str is a std::string object. Matcher(const std::string& s); // NOLINT #if GTEST_HAS_GLOBAL_STRING // Allows the user to write str instead of Eq(str) sometimes, where // str is a ::string object. Matcher(const ::string& s); // NOLINT #endif // GTEST_HAS_GLOBAL_STRING // Allows the user to write "foo" instead of Eq("foo") sometimes. Matcher(const char* s); // NOLINT }; template <> class GTEST_API_ Matcher : public internal::MatcherBase { public: Matcher() {} explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase(impl) {} explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase(impl) {} // Allows the user to write str instead of Eq(str) sometimes, where // str is a string object. Matcher(const std::string& s); // NOLINT #if GTEST_HAS_GLOBAL_STRING // Allows the user to write str instead of Eq(str) sometimes, where // str is a ::string object. Matcher(const ::string& s); // NOLINT #endif // GTEST_HAS_GLOBAL_STRING // Allows the user to write "foo" instead of Eq("foo") sometimes. Matcher(const char* s); // NOLINT }; #if GTEST_HAS_GLOBAL_STRING // The following two specializations allow the user to write str // instead of Eq(str) and "foo" instead of Eq("foo") when a ::string // matcher is expected. template <> class GTEST_API_ Matcher : public internal::MatcherBase { public: Matcher() {} explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase(impl) {} // Allows the user to write str instead of Eq(str) sometimes, where // str is a std::string object. Matcher(const std::string& s); // NOLINT // Allows the user to write str instead of Eq(str) sometimes, where // str is a ::string object. Matcher(const ::string& s); // NOLINT // Allows the user to write "foo" instead of Eq("foo") sometimes. Matcher(const char* s); // NOLINT }; template <> class GTEST_API_ Matcher< ::string> : public internal::MatcherBase< ::string> { public: Matcher() {} explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase< ::string>(impl) {} explicit Matcher(const MatcherInterface< ::string>* impl) : internal::MatcherBase< ::string>(impl) {} // Allows the user to write str instead of Eq(str) sometimes, where // str is a std::string object. Matcher(const std::string& s); // NOLINT // Allows the user to write str instead of Eq(str) sometimes, where // str is a ::string object. Matcher(const ::string& s); // NOLINT // Allows the user to write "foo" instead of Eq("foo") sometimes. Matcher(const char* s); // NOLINT }; #endif // GTEST_HAS_GLOBAL_STRING #if GTEST_HAS_ABSL // The following two specializations allow the user to write str // instead of Eq(str) and "foo" instead of Eq("foo") when a absl::string_view // matcher is expected. template <> class GTEST_API_ Matcher : public internal::MatcherBase { public: Matcher() {} explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase(impl) {} // Allows the user to write str instead of Eq(str) sometimes, where // str is a std::string object. Matcher(const std::string& s); // NOLINT #if GTEST_HAS_GLOBAL_STRING // Allows the user to write str instead of Eq(str) sometimes, where // str is a ::string object. Matcher(const ::string& s); // NOLINT #endif // GTEST_HAS_GLOBAL_STRING // Allows the user to write "foo" instead of Eq("foo") sometimes. Matcher(const char* s); // NOLINT // Allows the user to pass absl::string_views directly. Matcher(absl::string_view s); // NOLINT }; template <> class GTEST_API_ Matcher : public internal::MatcherBase { public: Matcher() {} explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase(impl) {} explicit Matcher(const MatcherInterface* impl) : internal::MatcherBase(impl) {} // Allows the user to write str instead of Eq(str) sometimes, where // str is a std::string object. Matcher(const std::string& s); // NOLINT #if GTEST_HAS_GLOBAL_STRING // Allows the user to write str instead of Eq(str) sometimes, where // str is a ::string object. Matcher(const ::string& s); // NOLINT #endif // GTEST_HAS_GLOBAL_STRING // Allows the user to write "foo" instead of Eq("foo") sometimes. Matcher(const char* s); // NOLINT // Allows the user to pass absl::string_views directly. Matcher(absl::string_view s); // NOLINT }; #endif // GTEST_HAS_ABSL // Prints a matcher in a human-readable format. template std::ostream& operator<<(std::ostream& os, const Matcher& matcher) { matcher.DescribeTo(&os); return os; } // The PolymorphicMatcher class template makes it easy to implement a // polymorphic matcher (i.e. a matcher that can match values of more // than one type, e.g. Eq(n) and NotNull()). // // To define a polymorphic matcher, a user should provide an Impl // class that has a DescribeTo() method and a DescribeNegationTo() // method, and define a member function (or member function template) // // bool MatchAndExplain(const Value& value, // MatchResultListener* listener) const; // // See the definition of NotNull() for a complete example. template class PolymorphicMatcher { public: explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {} // Returns a mutable reference to the underlying matcher // implementation object. Impl& mutable_impl() { return impl_; } // Returns an immutable reference to the underlying matcher // implementation object. const Impl& impl() const { return impl_; } template operator Matcher() const { return Matcher(new MonomorphicImpl(impl_)); } private: template class MonomorphicImpl : public MatcherInterface { public: explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} virtual void DescribeTo(::std::ostream* os) const { impl_.DescribeTo(os); } virtual void DescribeNegationTo(::std::ostream* os) const { impl_.DescribeNegationTo(os); } virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { return impl_.MatchAndExplain(x, listener); } private: const Impl impl_; GTEST_DISALLOW_ASSIGN_(MonomorphicImpl); }; Impl impl_; GTEST_DISALLOW_ASSIGN_(PolymorphicMatcher); }; // Creates a matcher from its implementation. This is easier to use // than the Matcher constructor as it doesn't require you to // explicitly write the template argument, e.g. // // MakeMatcher(foo); // vs // Matcher(foo); template inline Matcher MakeMatcher(const MatcherInterface* impl) { return Matcher(impl); } // Creates a polymorphic matcher from its implementation. This is // easier to use than the PolymorphicMatcher constructor as it // doesn't require you to explicitly write the template argument, e.g. // // MakePolymorphicMatcher(foo); // vs // PolymorphicMatcher(foo); template inline PolymorphicMatcher MakePolymorphicMatcher(const Impl& impl) { return PolymorphicMatcher(impl); } // Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION // and MUST NOT BE USED IN USER CODE!!! namespace internal { // The MatcherCastImpl class template is a helper for implementing // MatcherCast(). We need this helper in order to partially // specialize the implementation of MatcherCast() (C++ allows // class/struct templates to be partially specialized, but not // function templates.). // This general version is used when MatcherCast()'s argument is a // polymorphic matcher (i.e. something that can be converted to a // Matcher but is not one yet; for example, Eq(value)) or a value (for // example, "hello"). template class MatcherCastImpl { public: static Matcher Cast(const M& polymorphic_matcher_or_value) { // M can be a polymorphic matcher, in which case we want to use // its conversion operator to create Matcher. Or it can be a value // that should be passed to the Matcher's constructor. // // We can't call Matcher(polymorphic_matcher_or_value) when M is a // polymorphic matcher because it'll be ambiguous if T has an implicit // constructor from M (this usually happens when T has an implicit // constructor from any type). // // It won't work to unconditionally implict_cast // polymorphic_matcher_or_value to Matcher because it won't trigger // a user-defined conversion from M to T if one exists (assuming M is // a value). return CastImpl( polymorphic_matcher_or_value, BooleanConstant< internal::ImplicitlyConvertible >::value>(), BooleanConstant< internal::ImplicitlyConvertible::value>()); } private: template static Matcher CastImpl(const M& polymorphic_matcher_or_value, BooleanConstant /* convertible_to_matcher */, BooleanConstant) { // M is implicitly convertible to Matcher, which means that either // M is a polymorphic matcher or Matcher has an implicit constructor // from M. In both cases using the implicit conversion will produce a // matcher. // // Even if T has an implicit constructor from M, it won't be called because // creating Matcher would require a chain of two user-defined conversions // (first to create T from M and then to create Matcher from T). return polymorphic_matcher_or_value; } // M can't be implicitly converted to Matcher, so M isn't a polymorphic // matcher. It's a value of a type implicitly convertible to T. Use direct // initialization to create a matcher. static Matcher CastImpl( const M& value, BooleanConstant /* convertible_to_matcher */, BooleanConstant /* convertible_to_T */) { return Matcher(ImplicitCast_(value)); } // M can't be implicitly converted to either Matcher or T. Attempt to use // polymorphic matcher Eq(value) in this case. // // Note that we first attempt to perform an implicit cast on the value and // only fall back to the polymorphic Eq() matcher afterwards because the // latter calls bool operator==(const Lhs& lhs, const Rhs& rhs) in the end // which might be undefined even when Rhs is implicitly convertible to Lhs // (e.g. std::pair vs. std::pair). // // We don't define this method inline as we need the declaration of Eq(). static Matcher CastImpl( const M& value, BooleanConstant /* convertible_to_matcher */, BooleanConstant /* convertible_to_T */); }; // This more specialized version is used when MatcherCast()'s argument // is already a Matcher. This only compiles when type T can be // statically converted to type U. template class MatcherCastImpl > { public: static Matcher Cast(const Matcher& source_matcher) { return Matcher(new Impl(source_matcher)); } private: class Impl : public MatcherInterface { public: explicit Impl(const Matcher& source_matcher) : source_matcher_(source_matcher) {} // We delegate the matching logic to the source matcher. virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { #if GTEST_LANG_CXX11 using FromType = typename std::remove_cv::type>::type>::type; using ToType = typename std::remove_cv::type>::type>::type; // Do not allow implicitly converting base*/& to derived*/&. static_assert( // Do not trigger if only one of them is a pointer. That implies a // regular conversion and not a down_cast. (std::is_pointer::type>::value != std::is_pointer::type>::value) || std::is_same::value || !std::is_base_of::value, "Can't implicitly convert from to "); #endif // GTEST_LANG_CXX11 return source_matcher_.MatchAndExplain(static_cast(x), listener); } virtual void DescribeTo(::std::ostream* os) const { source_matcher_.DescribeTo(os); } virtual void DescribeNegationTo(::std::ostream* os) const { source_matcher_.DescribeNegationTo(os); } private: const Matcher source_matcher_; GTEST_DISALLOW_ASSIGN_(Impl); }; }; // This even more specialized version is used for efficiently casting // a matcher to its own type. template class MatcherCastImpl > { public: static Matcher Cast(const Matcher& matcher) { return matcher; } }; } // namespace internal // In order to be safe and clear, casting between different matcher // types is done explicitly via MatcherCast(m), which takes a // matcher m and returns a Matcher. It compiles only when T can be // statically converted to the argument type of m. template inline Matcher MatcherCast(const M& matcher) { return internal::MatcherCastImpl::Cast(matcher); } // Implements SafeMatcherCast(). // // We use an intermediate class to do the actual safe casting as Nokia's // Symbian compiler cannot decide between // template ... (M) and // template ... (const Matcher&) // for function templates but can for member function templates. template class SafeMatcherCastImpl { public: // This overload handles polymorphic matchers and values only since // monomorphic matchers are handled by the next one. template static inline Matcher Cast(const M& polymorphic_matcher_or_value) { return internal::MatcherCastImpl::Cast(polymorphic_matcher_or_value); } // This overload handles monomorphic matchers. // // In general, if type T can be implicitly converted to type U, we can // safely convert a Matcher to a Matcher (i.e. Matcher is // contravariant): just keep a copy of the original Matcher, convert the // argument from type T to U, and then pass it to the underlying Matcher. // The only exception is when U is a reference and T is not, as the // underlying Matcher may be interested in the argument's address, which // is not preserved in the conversion from T to U. template static inline Matcher Cast(const Matcher& matcher) { // Enforce that T can be implicitly converted to U. GTEST_COMPILE_ASSERT_((internal::ImplicitlyConvertible::value), T_must_be_implicitly_convertible_to_U); // Enforce that we are not converting a non-reference type T to a reference // type U. GTEST_COMPILE_ASSERT_( internal::is_reference::value || !internal::is_reference::value, cannot_convert_non_reference_arg_to_reference); // In case both T and U are arithmetic types, enforce that the // conversion is not lossy. typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT; typedef GTEST_REMOVE_REFERENCE_AND_CONST_(U) RawU; const bool kTIsOther = GMOCK_KIND_OF_(RawT) == internal::kOther; const bool kUIsOther = GMOCK_KIND_OF_(RawU) == internal::kOther; GTEST_COMPILE_ASSERT_( kTIsOther || kUIsOther || (internal::LosslessArithmeticConvertible::value), conversion_of_arithmetic_types_must_be_lossless); return MatcherCast(matcher); } }; template inline Matcher SafeMatcherCast(const M& polymorphic_matcher) { return SafeMatcherCastImpl::Cast(polymorphic_matcher); } // A() returns a matcher that matches any value of type T. template Matcher A(); // Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION // and MUST NOT BE USED IN USER CODE!!! namespace internal { // If the explanation is not empty, prints it to the ostream. inline void PrintIfNotEmpty(const std::string& explanation, ::std::ostream* os) { if (explanation != "" && os != NULL) { *os << ", " << explanation; } } // Returns true if the given type name is easy to read by a human. // This is used to decide whether printing the type of a value might // be helpful. inline bool IsReadableTypeName(const std::string& type_name) { // We consider a type name readable if it's short or doesn't contain // a template or function type. return (type_name.length() <= 20 || type_name.find_first_of("<(") == std::string::npos); } // Matches the value against the given matcher, prints the value and explains // the match result to the listener. Returns the match result. // 'listener' must not be NULL. // Value cannot be passed by const reference, because some matchers take a // non-const argument. template bool MatchPrintAndExplain(Value& value, const Matcher& matcher, MatchResultListener* listener) { if (!listener->IsInterested()) { // If the listener is not interested, we do not need to construct the // inner explanation. return matcher.Matches(value); } StringMatchResultListener inner_listener; const bool match = matcher.MatchAndExplain(value, &inner_listener); UniversalPrint(value, listener->stream()); #if GTEST_HAS_RTTI const std::string& type_name = GetTypeName(); if (IsReadableTypeName(type_name)) *listener->stream() << " (of type " << type_name << ")"; #endif PrintIfNotEmpty(inner_listener.str(), listener->stream()); return match; } // An internal helper class for doing compile-time loop on a tuple's // fields. template class TuplePrefix { public: // TuplePrefix::Matches(matcher_tuple, value_tuple) returns true // iff the first N fields of matcher_tuple matches the first N // fields of value_tuple, respectively. template static bool Matches(const MatcherTuple& matcher_tuple, const ValueTuple& value_tuple) { return TuplePrefix::Matches(matcher_tuple, value_tuple) && get(matcher_tuple).Matches(get(value_tuple)); } // TuplePrefix::ExplainMatchFailuresTo(matchers, values, os) // describes failures in matching the first N fields of matchers // against the first N fields of values. If there is no failure, // nothing will be streamed to os. template static void ExplainMatchFailuresTo(const MatcherTuple& matchers, const ValueTuple& values, ::std::ostream* os) { // First, describes failures in the first N - 1 fields. TuplePrefix::ExplainMatchFailuresTo(matchers, values, os); // Then describes the failure (if any) in the (N - 1)-th (0-based) // field. typename tuple_element::type matcher = get(matchers); typedef typename tuple_element::type Value; GTEST_REFERENCE_TO_CONST_(Value) value = get(values); StringMatchResultListener listener; if (!matcher.MatchAndExplain(value, &listener)) { // TODO(wan): include in the message the name of the parameter // as used in MOCK_METHOD*() when possible. *os << " Expected arg #" << N - 1 << ": "; get(matchers).DescribeTo(os); *os << "\n Actual: "; // We remove the reference in type Value to prevent the // universal printer from printing the address of value, which // isn't interesting to the user most of the time. The // matcher's MatchAndExplain() method handles the case when // the address is interesting. internal::UniversalPrint(value, os); PrintIfNotEmpty(listener.str(), os); *os << "\n"; } } }; // The base case. template <> class TuplePrefix<0> { public: template static bool Matches(const MatcherTuple& /* matcher_tuple */, const ValueTuple& /* value_tuple */) { return true; } template static void ExplainMatchFailuresTo(const MatcherTuple& /* matchers */, const ValueTuple& /* values */, ::std::ostream* /* os */) {} }; // TupleMatches(matcher_tuple, value_tuple) returns true iff all // matchers in matcher_tuple match the corresponding fields in // value_tuple. It is a compiler error if matcher_tuple and // value_tuple have different number of fields or incompatible field // types. template bool TupleMatches(const MatcherTuple& matcher_tuple, const ValueTuple& value_tuple) { // Makes sure that matcher_tuple and value_tuple have the same // number of fields. GTEST_COMPILE_ASSERT_(tuple_size::value == tuple_size::value, matcher_and_value_have_different_numbers_of_fields); return TuplePrefix::value>:: Matches(matcher_tuple, value_tuple); } // Describes failures in matching matchers against values. If there // is no failure, nothing will be streamed to os. template void ExplainMatchFailureTupleTo(const MatcherTuple& matchers, const ValueTuple& values, ::std::ostream* os) { TuplePrefix::value>::ExplainMatchFailuresTo( matchers, values, os); } // TransformTupleValues and its helper. // // TransformTupleValuesHelper hides the internal machinery that // TransformTupleValues uses to implement a tuple traversal. template class TransformTupleValuesHelper { private: typedef ::testing::tuple_size TupleSize; public: // For each member of tuple 't', taken in order, evaluates '*out++ = f(t)'. // Returns the final value of 'out' in case the caller needs it. static OutIter Run(Func f, const Tuple& t, OutIter out) { return IterateOverTuple()(f, t, out); } private: template struct IterateOverTuple { OutIter operator() (Func f, const Tup& t, OutIter out) const { *out++ = f(::testing::get(t)); return IterateOverTuple()(f, t, out); } }; template struct IterateOverTuple { OutIter operator() (Func /* f */, const Tup& /* t */, OutIter out) const { return out; } }; }; // Successively invokes 'f(element)' on each element of the tuple 't', // appending each result to the 'out' iterator. Returns the final value // of 'out'. template OutIter TransformTupleValues(Func f, const Tuple& t, OutIter out) { return TransformTupleValuesHelper::Run(f, t, out); } // Implements A(). template class AnyMatcherImpl : public MatcherInterface { public: virtual bool MatchAndExplain(GTEST_REFERENCE_TO_CONST_(T) /* x */, MatchResultListener* /* listener */) const { return true; } virtual void DescribeTo(::std::ostream* os) const { *os << "is anything"; } virtual void DescribeNegationTo(::std::ostream* os) const { // This is mostly for completeness' safe, as it's not very useful // to write Not(A()). However we cannot completely rule out // such a possibility, and it doesn't hurt to be prepared. *os << "never matches"; } }; // Implements _, a matcher that matches any value of any // type. This is a polymorphic matcher, so we need a template type // conversion operator to make it appearing as a Matcher for any // type T. class AnythingMatcher { public: template operator Matcher() const { return A(); } }; // Implements a matcher that compares a given value with a // pre-supplied value using one of the ==, <=, <, etc, operators. The // two values being compared don't have to have the same type. // // The matcher defined here is polymorphic (for example, Eq(5) can be // used to match an int, a short, a double, etc). Therefore we use // a template type conversion operator in the implementation. // // The following template definition assumes that the Rhs parameter is // a "bare" type (i.e. neither 'const T' nor 'T&'). template class ComparisonBase { public: explicit ComparisonBase(const Rhs& rhs) : rhs_(rhs) {} template operator Matcher() const { return MakeMatcher(new Impl(rhs_)); } private: template class Impl : public MatcherInterface { public: explicit Impl(const Rhs& rhs) : rhs_(rhs) {} virtual bool MatchAndExplain( Lhs lhs, MatchResultListener* /* listener */) const { return Op()(lhs, rhs_); } virtual void DescribeTo(::std::ostream* os) const { *os << D::Desc() << " "; UniversalPrint(rhs_, os); } virtual void DescribeNegationTo(::std::ostream* os) const { *os << D::NegatedDesc() << " "; UniversalPrint(rhs_, os); } private: Rhs rhs_; GTEST_DISALLOW_ASSIGN_(Impl); }; Rhs rhs_; GTEST_DISALLOW_ASSIGN_(ComparisonBase); }; template class EqMatcher : public ComparisonBase, Rhs, AnyEq> { public: explicit EqMatcher(const Rhs& rhs) : ComparisonBase, Rhs, AnyEq>(rhs) { } static const char* Desc() { return "is equal to"; } static const char* NegatedDesc() { return "isn't equal to"; } }; template class NeMatcher : public ComparisonBase, Rhs, AnyNe> { public: explicit NeMatcher(const Rhs& rhs) : ComparisonBase, Rhs, AnyNe>(rhs) { } static const char* Desc() { return "isn't equal to"; } static const char* NegatedDesc() { return "is equal to"; } }; template class LtMatcher : public ComparisonBase, Rhs, AnyLt> { public: explicit LtMatcher(const Rhs& rhs) : ComparisonBase, Rhs, AnyLt>(rhs) { } static const char* Desc() { return "is <"; } static const char* NegatedDesc() { return "isn't <"; } }; template class GtMatcher : public ComparisonBase, Rhs, AnyGt> { public: explicit GtMatcher(const Rhs& rhs) : ComparisonBase, Rhs, AnyGt>(rhs) { } static const char* Desc() { return "is >"; } static const char* NegatedDesc() { return "isn't >"; } }; template class LeMatcher : public ComparisonBase, Rhs, AnyLe> { public: explicit LeMatcher(const Rhs& rhs) : ComparisonBase, Rhs, AnyLe>(rhs) { } static const char* Desc() { return "is <="; } static const char* NegatedDesc() { return "isn't <="; } }; template class GeMatcher : public ComparisonBase, Rhs, AnyGe> { public: explicit GeMatcher(const Rhs& rhs) : ComparisonBase, Rhs, AnyGe>(rhs) { } static const char* Desc() { return "is >="; } static const char* NegatedDesc() { return "isn't >="; } }; // Implements the polymorphic IsNull() matcher, which matches any raw or smart // pointer that is NULL. class IsNullMatcher { public: template bool MatchAndExplain(const Pointer& p, MatchResultListener* /* listener */) const { #if GTEST_LANG_CXX11 return p == nullptr; #else // GTEST_LANG_CXX11 return GetRawPointer(p) == NULL; #endif // GTEST_LANG_CXX11 } void DescribeTo(::std::ostream* os) const { *os << "is NULL"; } void DescribeNegationTo(::std::ostream* os) const { *os << "isn't NULL"; } }; // Implements the polymorphic NotNull() matcher, which matches any raw or smart // pointer that is not NULL. class NotNullMatcher { public: template bool MatchAndExplain(const Pointer& p, MatchResultListener* /* listener */) const { #if GTEST_LANG_CXX11 return p != nullptr; #else // GTEST_LANG_CXX11 return GetRawPointer(p) != NULL; #endif // GTEST_LANG_CXX11 } void DescribeTo(::std::ostream* os) const { *os << "isn't NULL"; } void DescribeNegationTo(::std::ostream* os) const { *os << "is NULL"; } }; // Ref(variable) matches any argument that is a reference to // 'variable'. This matcher is polymorphic as it can match any // super type of the type of 'variable'. // // The RefMatcher template class implements Ref(variable). It can // only be instantiated with a reference type. This prevents a user // from mistakenly using Ref(x) to match a non-reference function // argument. For example, the following will righteously cause a // compiler error: // // int n; // Matcher m1 = Ref(n); // This won't compile. // Matcher m2 = Ref(n); // This will compile. template class RefMatcher; template class RefMatcher { // Google Mock is a generic framework and thus needs to support // mocking any function types, including those that take non-const // reference arguments. Therefore the template parameter T (and // Super below) can be instantiated to either a const type or a // non-const type. public: // RefMatcher() takes a T& instead of const T&, as we want the // compiler to catch using Ref(const_value) as a matcher for a // non-const reference. explicit RefMatcher(T& x) : object_(x) {} // NOLINT template operator Matcher() const { // By passing object_ (type T&) to Impl(), which expects a Super&, // we make sure that Super is a super type of T. In particular, // this catches using Ref(const_value) as a matcher for a // non-const reference, as you cannot implicitly convert a const // reference to a non-const reference. return MakeMatcher(new Impl(object_)); } private: template class Impl : public MatcherInterface { public: explicit Impl(Super& x) : object_(x) {} // NOLINT // MatchAndExplain() takes a Super& (as opposed to const Super&) // in order to match the interface MatcherInterface. virtual bool MatchAndExplain( Super& x, MatchResultListener* listener) const { *listener << "which is located @" << static_cast(&x); return &x == &object_; } virtual void DescribeTo(::std::ostream* os) const { *os << "references the variable "; UniversalPrinter::Print(object_, os); } virtual void DescribeNegationTo(::std::ostream* os) const { *os << "does not reference the variable "; UniversalPrinter::Print(object_, os); } private: const Super& object_; GTEST_DISALLOW_ASSIGN_(Impl); }; T& object_; GTEST_DISALLOW_ASSIGN_(RefMatcher); }; // Polymorphic helper functions for narrow and wide string matchers. inline bool CaseInsensitiveCStringEquals(const char* lhs, const char* rhs) { return String::CaseInsensitiveCStringEquals(lhs, rhs); } inline bool CaseInsensitiveCStringEquals(const wchar_t* lhs, const wchar_t* rhs) { return String::CaseInsensitiveWideCStringEquals(lhs, rhs); } // String comparison for narrow or wide strings that can have embedded NUL // characters. template bool CaseInsensitiveStringEquals(const StringType& s1, const StringType& s2) { // Are the heads equal? if (!CaseInsensitiveCStringEquals(s1.c_str(), s2.c_str())) { return false; } // Skip the equal heads. const typename StringType::value_type nul = 0; const size_t i1 = s1.find(nul), i2 = s2.find(nul); // Are we at the end of either s1 or s2? if (i1 == StringType::npos || i2 == StringType::npos) { return i1 == i2; } // Are the tails equal? return CaseInsensitiveStringEquals(s1.substr(i1 + 1), s2.substr(i2 + 1)); } // String matchers. // Implements equality-based string matchers like StrEq, StrCaseNe, and etc. template class StrEqualityMatcher { public: StrEqualityMatcher(const StringType& str, bool expect_eq, bool case_sensitive) : string_(str), expect_eq_(expect_eq), case_sensitive_(case_sensitive) {} #if GTEST_HAS_ABSL bool MatchAndExplain(const absl::string_view& s, MatchResultListener* listener) const { if (s.data() == NULL) { return !expect_eq_; } // This should fail to compile if absl::string_view is used with wide // strings. const StringType& str = string(s); return MatchAndExplain(str, listener); } #endif // GTEST_HAS_ABSL // Accepts pointer types, particularly: // const char* // char* // const wchar_t* // wchar_t* template bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { if (s == NULL) { return !expect_eq_; } return MatchAndExplain(StringType(s), listener); } // Matches anything that can convert to StringType. // // This is a template, not just a plain function with const StringType&, // because absl::string_view has some interfering non-explicit constructors. template bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { const StringType& s2(s); const bool eq = case_sensitive_ ? s2 == string_ : CaseInsensitiveStringEquals(s2, string_); return expect_eq_ == eq; } void DescribeTo(::std::ostream* os) const { DescribeToHelper(expect_eq_, os); } void DescribeNegationTo(::std::ostream* os) const { DescribeToHelper(!expect_eq_, os); } private: void DescribeToHelper(bool expect_eq, ::std::ostream* os) const { *os << (expect_eq ? "is " : "isn't "); *os << "equal to "; if (!case_sensitive_) { *os << "(ignoring case) "; } UniversalPrint(string_, os); } const StringType string_; const bool expect_eq_; const bool case_sensitive_; GTEST_DISALLOW_ASSIGN_(StrEqualityMatcher); }; // Implements the polymorphic HasSubstr(substring) matcher, which // can be used as a Matcher as long as T can be converted to a // string. template class HasSubstrMatcher { public: explicit HasSubstrMatcher(const StringType& substring) : substring_(substring) {} #if GTEST_HAS_ABSL bool MatchAndExplain(const absl::string_view& s, MatchResultListener* listener) const { if (s.data() == NULL) { return false; } // This should fail to compile if absl::string_view is used with wide // strings. const StringType& str = string(s); return MatchAndExplain(str, listener); } #endif // GTEST_HAS_ABSL // Accepts pointer types, particularly: // const char* // char* // const wchar_t* // wchar_t* template bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { return s != NULL && MatchAndExplain(StringType(s), listener); } // Matches anything that can convert to StringType. // // This is a template, not just a plain function with const StringType&, // because absl::string_view has some interfering non-explicit constructors. template bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { const StringType& s2(s); return s2.find(substring_) != StringType::npos; } // Describes what this matcher matches. void DescribeTo(::std::ostream* os) const { *os << "has substring "; UniversalPrint(substring_, os); } void DescribeNegationTo(::std::ostream* os) const { *os << "has no substring "; UniversalPrint(substring_, os); } private: const StringType substring_; GTEST_DISALLOW_ASSIGN_(HasSubstrMatcher); }; // Implements the polymorphic StartsWith(substring) matcher, which // can be used as a Matcher as long as T can be converted to a // string. template class StartsWithMatcher { public: explicit StartsWithMatcher(const StringType& prefix) : prefix_(prefix) { } #if GTEST_HAS_ABSL bool MatchAndExplain(const absl::string_view& s, MatchResultListener* listener) const { if (s.data() == NULL) { return false; } // This should fail to compile if absl::string_view is used with wide // strings. const StringType& str = string(s); return MatchAndExplain(str, listener); } #endif // GTEST_HAS_ABSL // Accepts pointer types, particularly: // const char* // char* // const wchar_t* // wchar_t* template bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { return s != NULL && MatchAndExplain(StringType(s), listener); } // Matches anything that can convert to StringType. // // This is a template, not just a plain function with const StringType&, // because absl::string_view has some interfering non-explicit constructors. template bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { const StringType& s2(s); return s2.length() >= prefix_.length() && s2.substr(0, prefix_.length()) == prefix_; } void DescribeTo(::std::ostream* os) const { *os << "starts with "; UniversalPrint(prefix_, os); } void DescribeNegationTo(::std::ostream* os) const { *os << "doesn't start with "; UniversalPrint(prefix_, os); } private: const StringType prefix_; GTEST_DISALLOW_ASSIGN_(StartsWithMatcher); }; // Implements the polymorphic EndsWith(substring) matcher, which // can be used as a Matcher as long as T can be converted to a // string. template class EndsWithMatcher { public: explicit EndsWithMatcher(const StringType& suffix) : suffix_(suffix) {} #if GTEST_HAS_ABSL bool MatchAndExplain(const absl::string_view& s, MatchResultListener* listener) const { if (s.data() == NULL) { return false; } // This should fail to compile if absl::string_view is used with wide // strings. const StringType& str = string(s); return MatchAndExplain(str, listener); } #endif // GTEST_HAS_ABSL // Accepts pointer types, particularly: // const char* // char* // const wchar_t* // wchar_t* template bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { return s != NULL && MatchAndExplain(StringType(s), listener); } // Matches anything that can convert to StringType. // // This is a template, not just a plain function with const StringType&, // because absl::string_view has some interfering non-explicit constructors. template bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { const StringType& s2(s); return s2.length() >= suffix_.length() && s2.substr(s2.length() - suffix_.length()) == suffix_; } void DescribeTo(::std::ostream* os) const { *os << "ends with "; UniversalPrint(suffix_, os); } void DescribeNegationTo(::std::ostream* os) const { *os << "doesn't end with "; UniversalPrint(suffix_, os); } private: const StringType suffix_; GTEST_DISALLOW_ASSIGN_(EndsWithMatcher); }; // Implements polymorphic matchers MatchesRegex(regex) and // ContainsRegex(regex), which can be used as a Matcher as long as // T can be converted to a string. class MatchesRegexMatcher { public: MatchesRegexMatcher(const RE* regex, bool full_match) : regex_(regex), full_match_(full_match) {} #if GTEST_HAS_ABSL bool MatchAndExplain(const absl::string_view& s, MatchResultListener* listener) const { return s.data() && MatchAndExplain(string(s), listener); } #endif // GTEST_HAS_ABSL // Accepts pointer types, particularly: // const char* // char* // const wchar_t* // wchar_t* template bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { return s != NULL && MatchAndExplain(std::string(s), listener); } // Matches anything that can convert to std::string. // // This is a template, not just a plain function with const std::string&, // because absl::string_view has some interfering non-explicit constructors. template bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { const std::string& s2(s); return full_match_ ? RE::FullMatch(s2, *regex_) : RE::PartialMatch(s2, *regex_); } void DescribeTo(::std::ostream* os) const { *os << (full_match_ ? "matches" : "contains") << " regular expression "; UniversalPrinter::Print(regex_->pattern(), os); } void DescribeNegationTo(::std::ostream* os) const { *os << "doesn't " << (full_match_ ? "match" : "contain") << " regular expression "; UniversalPrinter::Print(regex_->pattern(), os); } private: const internal::linked_ptr regex_; const bool full_match_; GTEST_DISALLOW_ASSIGN_(MatchesRegexMatcher); }; // Implements a matcher that compares the two fields of a 2-tuple // using one of the ==, <=, <, etc, operators. The two fields being // compared don't have to have the same type. // // The matcher defined here is polymorphic (for example, Eq() can be // used to match a tuple, a tuple, // etc). Therefore we use a template type conversion operator in the // implementation. template class PairMatchBase { public: template operator Matcher< ::testing::tuple >() const { return MakeMatcher(new Impl< ::testing::tuple >); } template operator Matcher&>() const { return MakeMatcher(new Impl&>); } private: static ::std::ostream& GetDesc(::std::ostream& os) { // NOLINT return os << D::Desc(); } template class Impl : public MatcherInterface { public: virtual bool MatchAndExplain( Tuple args, MatchResultListener* /* listener */) const { return Op()(::testing::get<0>(args), ::testing::get<1>(args)); } virtual void DescribeTo(::std::ostream* os) const { *os << "are " << GetDesc; } virtual void DescribeNegationTo(::std::ostream* os) const { *os << "aren't " << GetDesc; } }; }; class Eq2Matcher : public PairMatchBase { public: static const char* Desc() { return "an equal pair"; } }; class Ne2Matcher : public PairMatchBase { public: static const char* Desc() { return "an unequal pair"; } }; class Lt2Matcher : public PairMatchBase { public: static const char* Desc() { return "a pair where the first < the second"; } }; class Gt2Matcher : public PairMatchBase { public: static const char* Desc() { return "a pair where the first > the second"; } }; class Le2Matcher : public PairMatchBase { public: static const char* Desc() { return "a pair where the first <= the second"; } }; class Ge2Matcher : public PairMatchBase { public: static const char* Desc() { return "a pair where the first >= the second"; } }; // Implements the Not(...) matcher for a particular argument type T. // We do not nest it inside the NotMatcher class template, as that // will prevent different instantiations of NotMatcher from sharing // the same NotMatcherImpl class. template class NotMatcherImpl : public MatcherInterface { public: explicit NotMatcherImpl(const Matcher& matcher) : matcher_(matcher) {} virtual bool MatchAndExplain(GTEST_REFERENCE_TO_CONST_(T) x, MatchResultListener* listener) const { return !matcher_.MatchAndExplain(x, listener); } virtual void DescribeTo(::std::ostream* os) const { matcher_.DescribeNegationTo(os); } virtual void DescribeNegationTo(::std::ostream* os) const { matcher_.DescribeTo(os); } private: const Matcher matcher_; GTEST_DISALLOW_ASSIGN_(NotMatcherImpl); }; // Implements the Not(m) matcher, which matches a value that doesn't // match matcher m. template class NotMatcher { public: explicit NotMatcher(InnerMatcher matcher) : matcher_(matcher) {} // This template type conversion operator allows Not(m) to be used // to match any type m can match. template operator Matcher() const { return Matcher(new NotMatcherImpl(SafeMatcherCast(matcher_))); } private: InnerMatcher matcher_; GTEST_DISALLOW_ASSIGN_(NotMatcher); }; // Implements the AllOf(m1, m2) matcher for a particular argument type // T. We do not nest it inside the BothOfMatcher class template, as // that will prevent different instantiations of BothOfMatcher from // sharing the same BothOfMatcherImpl class. template class AllOfMatcherImpl : public MatcherInterface { public: explicit AllOfMatcherImpl(std::vector > matchers) : matchers_(internal::move(matchers)) {} virtual void DescribeTo(::std::ostream* os) const { *os << "("; for (size_t i = 0; i < matchers_.size(); ++i) { if (i != 0) *os << ") and ("; matchers_[i].DescribeTo(os); } *os << ")"; } virtual void DescribeNegationTo(::std::ostream* os) const { *os << "("; for (size_t i = 0; i < matchers_.size(); ++i) { if (i != 0) *os << ") or ("; matchers_[i].DescribeNegationTo(os); } *os << ")"; } virtual bool MatchAndExplain(GTEST_REFERENCE_TO_CONST_(T) x, MatchResultListener* listener) const { // If either matcher1_ or matcher2_ doesn't match x, we only need // to explain why one of them fails. std::string all_match_result; for (size_t i = 0; i < matchers_.size(); ++i) { StringMatchResultListener slistener; if (matchers_[i].MatchAndExplain(x, &slistener)) { if (all_match_result.empty()) { all_match_result = slistener.str(); } else { std::string result = slistener.str(); if (!result.empty()) { all_match_result += ", and "; all_match_result += result; } } } else { *listener << slistener.str(); return false; } } // Otherwise we need to explain why *both* of them match. *listener << all_match_result; return true; } private: const std::vector > matchers_; GTEST_DISALLOW_ASSIGN_(AllOfMatcherImpl); }; #if GTEST_LANG_CXX11 // VariadicMatcher is used for the variadic implementation of // AllOf(m_1, m_2, ...) and AnyOf(m_1, m_2, ...). // CombiningMatcher is used to recursively combine the provided matchers // (of type Args...). template