From d48163e464cd37ae272d98ef5dca0c804f840475 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Morin <38703886+JeanChristopheMorinPerso@users.noreply.github.com> Date: Sun, 19 Jun 2022 13:56:14 -0400 Subject: [PATCH] Improve Python reference documentation (#1209) * Improve Python reference documentation * Fix lint * Add some missing docstrings * Use new ReadTheDocs config format, bake sphinx dependencies and move to MyST * Re-generate plugins and schema docs * Add spatial coordinates doc to main doctree * Simplify the process for building the docs locally * Fix Sphinx warnings * enable GH builds * Modify make.bat to match with the docs Makefile * Add job to detect doc warnings/errors Signed-off-by: Jean-Christophe Morin --- .github/workflows/docs.yml | 105 +++++ .gitignore | 1 + .readthedocs.yml | 15 +- Makefile | 5 +- docs/Makefile | 13 +- docs/_templates/autosummary/module.rst | 18 + docs/conf.py | 389 ++++++------------ docs/index.rst | 30 +- docs/make.bat | 4 +- docs/python_reference.rst | 8 + docs/requirements.txt | 4 + docs/tutorials/adapters.md | 39 +- docs/tutorials/architecture.md | 6 +- docs/tutorials/otio-env-variables.md | 2 +- .../otio-file-format-specification.md | 4 +- docs/tutorials/otio-plugins.md | 30 +- docs/tutorials/otio-serialized-schema.md | 120 ++++-- docs/tutorials/quickstart.md | 15 +- docs/tutorials/write-a-media-linker.md | 2 +- docs/use-cases/animation-shot-frame-ranges.md | 10 +- .../use-cases/shots-added-removed-from-cut.md | 2 +- .../opentime_rationalTime.cpp | 42 +- .../opentime-bindings/opentime_timeRange.cpp | 123 ++++-- .../opentime_timeTransform.cpp | 2 +- .../opentimelineio-bindings/otio_bindings.cpp | 6 +- .../otio_serializableObjects.cpp | 139 +++++-- .../opentimelineio/adapters/__init__.py | 33 +- .../opentimelineio/adapters/adapter.py | 11 +- .../opentimelineio/adapters/fcp_xml.py | 4 +- .../opentimelineio/adapters/svg.py | 7 +- .../opentimelineio/algorithms/filter.py | 83 ++-- .../opentimelineio/algorithms/stack_algo.py | 20 +- .../algorithms/timeline_algo.py | 18 +- .../opentimelineio/algorithms/track_algo.py | 35 +- .../console/autogen_plugin_documentation.py | 15 +- .../opentimelineio/core/__init__.py | 62 ++- .../opentimelineio/core/_core_utils.py | 12 + .../opentimelineio/core/composition.py | 20 +- .../opentimelineio/exceptions.py | 19 + src/py-opentimelineio/opentimelineio/hooks.py | 91 ++-- .../opentimelineio/media_linker.py | 33 +- .../opentimelineio/opentime.py | 25 ++ .../opentimelineio/plugins/manifest.py | 11 +- .../opentimelineio/schema/__init__.py | 22 + .../schema/image_sequence_reference.py | 9 +- .../opentimelineio/schema/schemadef.py | 3 +- .../schema/serializable_collection.py | 22 +- .../opentimelineio/schema/stack.py | 11 +- .../opentimelineio/schema/timeline.py | 26 +- .../opentimelineio/schema/track.py | 9 +- 50 files changed, 1041 insertions(+), 694 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/_templates/autosummary/module.rst create mode 100644 docs/python_reference.rst create mode 100644 docs/requirements.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..9b6ce5157c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,105 @@ +name: docs + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + check-links: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - uses: actions/setup-python@v3 + with: + python-version: '3.10' + + - name: Create virtualenv + run: python3 -m venv .venv + + - name: Install dependencies + run: | + source .venv/bin/activate + python -m pip install . + python -m pip install -r docs/requirements.txt + + - name: Linkcheck + working-directory: docs + run: | + source ../.venv/bin/activate + + set +e + make linkcheck + exit_code=$? + + set -e + + if [ $exit_code -eq 0 ]; then + echo -e "\n\n=================\nAll links are valid!" + + echo "# :heavy_check_mark: Sphinx links" >> $GITHUB_STEP_SUMMARY + echo "All links are valid!" >> $GITHUB_STEP_SUMMARY + else + echo -e "\n\n=================\nFound broken links. Look at the build logs.\n" + + echo "# :x: Sphinx links" >> $GITHUB_STEP_SUMMARY + echo "Found broken links. Look at the build logs for additional information." >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat _build/linkcheck/output.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + fi + + exit $exit_code + + check-warnings: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - uses: actions/setup-python@v3 + with: + python-version: '3.10' + + - name: Create virtualenv + run: python3 -m venv .venv + + - name: Install dependencies + run: | + source .venv/bin/activate + python -m pip install . + python -m pip install -r docs/requirements.txt + + - name: Check warnings/errors + working-directory: docs + run: | + source ../.venv/bin/activate + + set +e + make htmlstrict + + exit_code=$? + + set -e + + if [ $exit_code -eq 0 ]; then + echo -e "\n\n=================\nNo warnings or errors detected!" + echo "# :heavy_check_mark: Sphinx warnings/errors" >> $GITHUB_STEP_SUMMARY + echo "No errors or warnings detected!" >> $GITHUB_STEP_SUMMARY + else + echo -e "\n\n=================\nWarnings and or errors detected; See the summary bellow:\n" + cat _build/htmlstrict/output.txt + + echo "# :x: Sphinx warnings/errors" >> $GITHUB_STEP_SUMMARY + echo "Found some warnings or errors:" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat _build/htmlstrict/output.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + fi + + exit $exit_code diff --git a/.gitignore b/.gitignore index 9cf64c081d..053fa5a82a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ xcuserdata/ # These files are generated, don't put them into source control docs/api +docs/_build .tox diff --git a/.readthedocs.yml b/.readthedocs.yml index 77fddc6b86..f0873ba44f 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,19 +2,16 @@ version: 2 build: - image: latest + os: "ubuntu-20.04" + tools: + python: "3.10" python: - version: 3.7 install: - - method: pip - path: . - extra_requirements: - - cmake + - method: pip + path: . + - requirements: docs/requirements.txt submodules: include: all recursive: true - -conda: - environment: readthedocs-conda.yml diff --git a/Makefile b/Makefile index 1dc597e3a4..c89d0f4db4 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,6 @@ $(ccblue) pip install -e .[dev]$(newline)$(ccend) endef # variables -DOC_OUTPUT_DIR ?= /var/tmp/otio-docs MAKE_PROG ?= make # external programs @@ -117,6 +116,7 @@ ifdef COV_PROG endif @${MAKE_PROG} -C contrib/opentimelineio_contrib/adapters clean VERBOSE=$(VERBOSE) rm -vf *.whl + @cd docs; ${MAKE_PROG} clean # conform all files to pep8 -- WILL CHANGE FILES IN PLACE # autopep8: @@ -185,8 +185,7 @@ doc-plugins-update: # generate documentation in html doc-html: @# if you just want to build the docs yourself outside of RTD - @echo "Writing documentation to $(DOC_OUTPUT_DIR), set variable DOC_OUTPUT_DIR to change output directory." - @cd docs ; sphinx-build -j8 -E -b html -d $(DOC_OUTPUT_DIR)/doctrees . $(DOC_OUTPUT_DIR)/html + @cd docs; ${MAKE_PROG} html doc-cpp: @cd doxygen ; doxygen config/dox_config ; cd .. diff --git a/docs/Makefile b/docs/Makefile index 9e00093cec..e3e7947561 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,15 +2,15 @@ # # You can set these variables from the command line. -SPHINXOPTS = +SPHINXOPTS = -n -j8 SPHINXBUILD = sphinx-build PAPER = -BUILDDIR = build +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) source +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source @@ -18,6 +18,7 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 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 " pickle to make pickle files" @@ -54,6 +55,12 @@ 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 diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst new file mode 100644 index 0000000000..7792d1f711 --- /dev/null +++ b/docs/_templates/autosummary/module.rst @@ -0,0 +1,18 @@ +{{ fullname }} +{{ underline }} + +.. automodule:: {{ fullname }} + :members: + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} diff --git a/docs/conf.py b/docs/conf.py index 671bddc906..0bb1a9afe2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,306 +2,189 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project +import re import sphinx_rtd_theme import opentimelineio -PACKAGE_TITLE = 'OpenTimelineIO' -PACKAGE_NAME = 'opentimelineio' -PACKAGE_DIR = 'src/py-opentimelineio/opentimelineio' -AUTHOR_NAME = 'Contributors to the OpenTimelineIO project' +# -- 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' -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - # this plugin is used to format our markdown correctly - 'recommonmark', - # uncomment the next line if you are writing in Google Napoleon docstrings - # 'sphinx.ext.napoleon' -] - -autodoc_mock_imports = ['aaf'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -source_parsers = { - '.md': 'recommonmark.parser.CommonMarkParser', -} - -# The suffix of source filenames. -source_suffix = ['.rst', '.md'] - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The main toctree document. -master_doc = 'index' - -# General information about the project. -project = PACKAGE_TITLE -copyright = u"Copyright Contributors to the OpenTimelineIO project" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# # The short X.Y version. version = RELEASE.split('-')[0] # The full version, including alpha/beta/rc tags. release = RELEASE -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' - -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None +# -- General configuration ------------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.intersphinx', + 'myst_parser', # This plugin is used to format our markdown correctly +] -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True +templates_path = ['_templates'] -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False +exclude_patterns = ['_build', '_templates', '.venv'] -# The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] +# -- Options for HTML output ----------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +htmlhelp_basename = '{}doc'.format(project.lower()) -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -html_extra_path = [] -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' +# -- Options for LaTeX output ---------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = '{}doc'.format(PACKAGE_NAME) - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', '{}.tex'.format(PACKAGE_NAME), - u'{} Documentation'.format(PACKAGE_TITLE), - AUTHOR_NAME, 'manual'), + ('index', '{}.tex'.format(project.lower()), + u'{} Documentation'.format(project), + author, 'manual'), ] -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] +# -- Options for manual page output ---------------------------------------------------- +# sphinx-doc.org/en/master/usage/configuration.html#options-for-manual-page-output -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). man_pages = [ - ('index', PACKAGE_NAME, u'{} Documentation'.format({PACKAGE_TITLE}), - [AUTHOR_NAME], 1) + ('index', project.lower(), '{} Documentation'.format(project), + [author], 1) ] -# If true, show URL addresses after external links. -# man_show_urls = False - +# -- Options for Texinfo output -------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-texinfo-output -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) texinfo_documents = [ - ('index', PACKAGE_NAME, u'{} Documentation'.format(PACKAGE_TITLE), - AUTHOR_NAME, PACKAGE_TITLE, 'One line description of project.', + ('index', project.lower(), '{} Documentation'.format(project), + author, project, 'One line description of project.', 'Miscellaneous'), ] -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] +# -- Options for intersphinx ----------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} -# If false, no module index is generated. -# texinfo_domain_indices = True +# -- Options for Autodoc --------------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' +# Both the class’ and the __init__ method’s docstring are concatenated and inserted. +# Pybind11 generates class signatures on the __init__ method. +autoclass_content = "both" -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False +autodoc_default_options = { + 'undoc-members': True +} +# -- Options for linkcheck ------------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder -def run_apidoc(_): - """This method is required by the setup method below.""" - ignore_paths = ['opentimelineio_contrib.adapters', 'tests', 'setup.py'] - # https://github.com/sphinx-doc/sphinx/blob/master/sphinx/ext/apidoc.py - argv = [ - '--force', - '--no-toc', - # '--separate', - '--module-first', - '--output-dir', - './api/modules', - # this is expected to be run from the `docs` directory. Its possible - # to set it up to run from other places, but so far this has worked for - # us - '../src/py-opentimelineio/opentimelineio', - ] + ignore_paths +linkcheck_exclude_documents = [ + r'cxx/cxx' +] - from sphinx.ext import apidoc - apidoc.main(argv) +# -- 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(f"^\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("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(f'\d+\. {name.split("."[0])}', line): + line = re.sub("self\: [a-zA-Z0-9._]+(,\s)?", "", line) + lines[index] = line def setup(app): - """This method is a hook into the Sphinx builder system and injects the - apidoc module into it so it runs autodoc before running build. - - If you mess with this, you may not see any effect in a local build, this - was added to get api documentation building on the ReadTheDocs server. - """ - app.connect('builder-inited', run_apidoc) + app.connect("autodoc-process-signature", process_signature) + app.connect("autodoc-process-docstring", process_docstring) diff --git a/docs/index.rst b/docs/index.rst index efaaf3a98d..2a70981578 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,6 +24,7 @@ Quick Start ------------ .. toctree:: :maxdepth: 2 + :caption: Quick Start tutorials/quickstart tutorials/otio-env-variables @@ -32,6 +33,7 @@ Tutorials ------------ .. toctree:: :maxdepth: 2 + :caption: Tutorials tutorials/adapters tutorials/architecture @@ -44,40 +46,39 @@ Tutorials tutorials/write-a-media-linker tutorials/write-a-hookscript tutorials/write-a-schemadef + tutorials/spatial-coordinates 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 Reference -------------- +API References +-------------- -.. toctree:: - :maxdepth: 2 - - api/modules/opentimelineio - -C++ Implementation Reference ----------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 3 + :caption: API References + + python_reference - cxx/bridges.md - cxx/cxx.md - cxx/older.md + 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 @@ -88,9 +89,10 @@ Autogenerated Plugin Reference .. toctree:: :maxdepth: 2 + :caption: Plugins Reference tutorials/otio-plugins.md - + Indices and tables ------------------ diff --git a/docs/make.bat b/docs/make.bat index 4dbbf2887a..13fd7f9501 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -5,8 +5,8 @@ REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +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% diff --git a/docs/python_reference.rst b/docs/python_reference.rst new file mode 100644 index 0000000000..4cc068b58c --- /dev/null +++ b/docs/python_reference.rst @@ -0,0 +1,8 @@ +Python +====== + +.. autosummary:: + :toctree: api/python + :recursive: + + opentimelineio diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000000..608a525b8e --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +sphinx==4.5.0 +readthedocs-sphinx-ext==2.1.5 # ?? +sphinx-rtd-theme +myst-parser==0.17.2 diff --git a/docs/tutorials/adapters.md b/docs/tutorials/adapters.md index 3438ab717f..5f9aa2a72a 100644 --- a/docs/tutorials/adapters.md +++ b/docs/tutorials/adapters.md @@ -3,48 +3,47 @@ OpenTimelineIO supports, or plans to support, conversion adapters for many existing file formats. -### Final Cut Pro XML ### +## Final Cut Pro XML Final Cut 7 XML Format - Status: Supported via the `fcp_xml` adapter -- Reference +- [Reference](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/FinalCutPro_XML/AboutThisDoc/AboutThisDoc.html#//apple_ref/doc/uid/TP30001152-TPXREF101) Final Cut Pro X XML Format: - Status: Supported via the `fcpx_xml` adapter -- Intro to FCP X XML +- [Intro to FCP X XML](https://developer.apple.com/library/mac/documentation/FinalCutProX/Reference/FinalCutProXXMLFormat/Introduction/Introduction.html) -### Adobe Premiere Project ### +## Adobe Premiere Project - Based on guidance from Adobe, we support interchange with Adobe Premiere via the FCP 7 XML format (see above). -### CMX3600 EDL ### +## CMX3600 EDL - Status: Supported via the `cmx_3600` adapter - Includes support for ASC_CDL color correction metadata - Full specification: SMPTE 258M-2004 "For Television −− Transfer of Edit Decision Lists" - http://xmil.biz/EDL-X/CMX3600.pdf -- Reference +- [Reference](https://prohelp.apple.com/finalcutpro_help-r01/English/en/finalcutpro/usermanual/chapter_96_section_0.html) -### Avid AAF ### +## Avid AAF - Status: Reads and writes AAF compositions - includes clip, gaps, transitions but not markers or effects - - This adapter is still in progress, see the ongoing work here: AAF Project -- Spec -- Protocol + - This adapter is still in progress, see the ongoing work here: [AAF Project](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/projects/1) +- [Spec](https://static.amwa.tv/ms-01-aaf-object-spec.pdf) +- [Protocol](https://static.amwa.tv/as-01-aaf-edit-protocol-spec.pdf) -- Depends on the `PyAAF2` module, so either: +- Depends on the [PyAAF2](https://github.com/markreidvfx/pyaaf2) module, so either: - `pip install pyaaf2` - ...or set `${OTIO_AAF_PYTHON_LIB}` to point the location of the PyAAF2 module -Contrib Adapters ----------------- +# Contrib Adapters The contrib area hosts adapters which come from the community (_not_ supported by the core-otio team) and may require extra dependencies. -### RV Session File ### +## RV Session File - Status: write-only adapter supported via the `rv_session` adapter. - need to set environment variables to locate `py-interp` and `rvSession.py` @@ -55,30 +54,30 @@ The contrib area hosts adapters which come from the community (_not_ supported - set `${OTIO_RV_PYTHON_LIB}` to point at the parent directory of `rvSession.py`: `setenv OTIO_RV_PYTHON_LIB /Applications/RV64.app/Contents/src/python` -### Maya Sequencer ### +## Maya Sequencer - Status: supported via the `maya_sequencer` adapter. - set `${OTIO_MAYA_PYTHON_BIN}` to point the location of `mayapy` within the maya installation. -### HLS Playlist ### +## HLS Playlist - Status: supported via the `hls_playlist` adapter. -### Avid Log Exchange (ALE) ### +## Avid Log Exchange (ALE) - Status: supported via the `ale` adapter. -### Text Burn-in Adapter ### +## Text Burn-in Adapter Uses FFmpeg to burn text overlays into video media. - Status: supported via the `burnins` adapter. -### GStreamer Editing Services Adapter ### +## GStreamer Editing Services Adapter - Status: supported via the `xges` adapter. -### Kdenlive Adapter ### +## Kdenlive Adapter - Status: supported via the kdenlive adapter diff --git a/docs/tutorials/architecture.md b/docs/tutorials/architecture.md index b451385431..e7ea1b1490 100644 --- a/docs/tutorials/architecture.md +++ b/docs/tutorials/architecture.md @@ -121,7 +121,7 @@ The native format serialization (`.otio` files) is handled via the "otio_json" a 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 +For more information, see [How To Write An OpenTimelineIO Adapter](write-an-adapter). ## otio.media_linkers @@ -129,9 +129,9 @@ Media linkers run on the otio file after an adapter calls `.read_from_file()` or 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 `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 +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. +Example scripts are located in the [examples subdirectory](https://github.com/AcademySoftwareFoundation/OpenTimelineIO/tree/main/examples). diff --git a/docs/tutorials/otio-env-variables.md b/docs/tutorials/otio-env-variables.md index b20285a1ae..3c8a113dff 100644 --- a/docs/tutorials/otio-env-variables.md +++ b/docs/tutorials/otio-env-variables.md @@ -7,7 +7,7 @@ various aspects of OTIO. These variables must be set _before_ the OpenTimelineIO python library is imported. -- `OTIO_PLUGIN_MANIFEST_PATH`: a ":" separated string with paths to .manifest.json files that contain OTIO plugin manifests. See: Tutorial on how to write an adapter plugin. +- `OTIO_PLUGIN_MANIFEST_PATH`: a ":" separated string with paths to .manifest.json files that contain OTIO plugin manifests. See: [Tutorial on how to write an adapter plugin](write-an-adapter). - `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_PKG_RESOURCE_PLUGINS`: By default, OTIO will use the pkg_resource entry_points mechanism to discover plugins that have been installed into the current python environment. pkg_resources, however, can be slow in certain cases, so for users who wish to disable this behavior, this variable can be set to 1. diff --git a/docs/tutorials/otio-file-format-specification.md b/docs/tutorials/otio-file-format-specification.md index 4a8192e007..9d284fb9ba 100644 --- a/docs/tutorials/otio-file-format-specification.md +++ b/docs/tutorials/otio-file-format-specification.md @@ -83,7 +83,7 @@ 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. +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: @@ -273,4 +273,4 @@ Metadata can also be useful when prototyping new OTIO schemas. An existing objec ## Schema Specification -To see an autogenerated documentation of the serialized types and their fields, see this: Autogenerated Serialized File Format +To see an autogenerated documentation of the serialized types and their fields, see this: [Autogenerated Serialized File Format](otio-serialized-schema). diff --git a/docs/tutorials/otio-plugins.md b/docs/tutorials/otio-plugins.md index eb2dc917c1..a3c538e9a9 100644 --- a/docs/tutorials/otio-plugins.md +++ b/docs/tutorials/otio-plugins.md @@ -28,11 +28,9 @@ Manifest path: `opentimelineio/adapters/builtin_adapters.plugin_manifest.json` Adapter plugins convert to and from OpenTimelineIO. - Adapters documentation page for more -information +[Adapters documentation page for more information](./adapters). -Tutorial on how to write an -adapter. +[Tutorial on how to write an adapter](write-an-adapter). ### cmx_3600 @@ -269,8 +267,7 @@ Points in SVG are y-down. 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 +[Tutorial on how to write a Media Linker](write-a-media-linker). @@ -278,8 +275,7 @@ Media Linker SchemaDef plugins define new external schema. - Tutorial on how to write a -schemadef +[Tutorial on how to write a schemadef](write-a-schemadef). @@ -287,8 +283,7 @@ schemadef HookScripts are extra plugins that run on _hooks_. -Tutorial on how to write a -hookscript. +[Tutorial on how to write a hookscript](write-a-hookscript). @@ -312,11 +307,9 @@ Manifest path: `opentimelineio_contrib/adapters/contrib_adapters.plugin_manifest Adapter plugins convert to and from OpenTimelineIO. - Adapters documentation page for more -information +[Adapters documentation page for more information](./adapters). -Tutorial on how to write an -adapter. +[Tutorial on how to write an adapter](write-an-adapter). ### AAF @@ -682,8 +675,7 @@ Necessary write method for otio adapter 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 +[Tutorial on how to write a Media Linker](write-a-media-linker). @@ -691,8 +683,7 @@ Media Linker SchemaDef plugins define new external schema. - Tutorial on how to write a -schemadef +[Tutorial on how to write a schemadef](write-a-schemadef). ### xges @@ -790,8 +781,7 @@ An OpenTimelineIO Schema for storing a GESTrack. HookScripts are extra plugins that run on _hooks_. -Tutorial on how to write a -hookscript. +[Tutorial on how to write a hookscript](write-a-hookscript). diff --git a/docs/tutorials/otio-serialized-schema.md b/docs/tutorials/otio-serialized-schema.md index 4206b6bf33..e59b68a90b 100644 --- a/docs/tutorials/otio-serialized-schema.md +++ b/docs/tutorials/otio-serialized-schema.md @@ -28,9 +28,11 @@ changes. If it needs to be updated and this file regenerated, run: ``` Adapters convert between OTIO and other formats. - Note that this class is not subclassed by adapters. Rather, an adapter is + 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) @@ -41,8 +43,7 @@ Adapters convert between OTIO and other formats. 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 + For more information: https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html. # noqa ``` @@ -61,7 +62,9 @@ parameters: *documentation*: ``` -None + +An object that can be composed within a :class:`~Composition` (such as :class:`~Track` or :class:`.Stack`). + ``` parameters: @@ -75,12 +78,16 @@ parameters: *documentation*: ``` -None + +Base class for an :class:`~Item` that contains other :class:`~Item`\s. + +Should be subclassed (for example by :class:`.Track` and :class:`.Stack`), not used directly. + ``` parameters: - *effects*: -- *enabled*: If true, an Item contributes to compositions. Analogous to Mute in various NLEs. +- *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*: @@ -98,7 +105,7 @@ None parameters: - *effects*: -- *enabled*: If true, an Item contributes to compositions. Analogous to Mute in various NLEs. +- *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*: @@ -177,7 +184,10 @@ parameters: *documentation*: ``` -None + +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: @@ -191,7 +201,11 @@ parameters: *documentation*: ``` -None + +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: @@ -205,7 +219,7 @@ parameters: *documentation*: ``` -None +1D transform for :class:`~RationalTime`. Has offset and scale. ``` parameters: @@ -267,13 +281,17 @@ parameters: *documentation*: ``` -None + +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*: - *effects*: -- *enabled*: If true, an Item contributes to compositions. Analogous to Mute in various NLEs. +- *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*: @@ -319,14 +337,18 @@ parameters: *documentation*: ``` -None +Hold the first frame of the clip for the duration of the clip. ``` parameters: - *effect_name*: - *metadata*: - *name*: -- *time_scalar*: +- *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 @@ -340,7 +362,7 @@ None parameters: - *effects*: -- *enabled*: If true, an Item contributes to compositions. Analogous to Mute in various NLEs. +- *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*: @@ -372,13 +394,15 @@ parameters: ``` -An ImageSequenceReference refers to a numbered series of single-frame image files. Each file can be referred to by a URL generated by the ImageSequenceReference. +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 sequncences 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. +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:: +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": { @@ -400,7 +424,9 @@ An example for 24fps media with a sample provided each frame numbered 1-1000 wit "frame_zero_padding": 4, } -The same duration sequence but with only every 2nd frame available in the sequence would be:: +The same duration sequence but with only every 2nd frame available in the sequence would be + +.. code-block:: json { "available_range": { @@ -422,7 +448,9 @@ The same duration sequence but with only every 2nd frame available in the sequen "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:: +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())] @@ -440,7 +468,7 @@ parameters: - *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*: Enum ``ImageSequenceReference.MissingFramePolicy`` directive for how frames in sequence not found on disk should be handled. +- *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. @@ -455,14 +483,20 @@ parameters: *documentation*: ``` -None + +A time warp that applies a linear speed up or slow down across the entire clip. + ``` parameters: - *effect_name*: - *metadata*: - *name*: -- *time_scalar*: +- *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 @@ -471,12 +505,16 @@ parameters: *documentation*: ``` -None + +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*: -- *marked_range*: +- *color*: Color string for this marker (for example: 'RED'), based on the :class:`~Color` enum. +- *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*: @@ -487,7 +525,11 @@ parameters: *documentation*: ``` -None + +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: @@ -503,7 +545,15 @@ parameters: *documentation*: ``` -None + +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: @@ -522,7 +572,7 @@ None parameters: - *effects*: -- *enabled*: If true, an Item contributes to compositions. Analogous to Mute in various NLEs. +- *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*: @@ -535,7 +585,7 @@ parameters: *documentation*: ``` -None +Base class for all effects that alter the timing of an item. ``` parameters: @@ -571,7 +621,7 @@ None parameters: - *effects*: -- *enabled*: If true, an Item contributes to compositions. Analogous to Mute in various NLEs. +- *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*: @@ -585,15 +635,15 @@ parameters: *documentation*: ``` -None +Represents a transition between the two adjacent items in a :class:`.Track`. For example, a cross dissolve or wipe. ``` parameters: -- *in_offset*: +- *in_offset*: Amount of the previous clip this transition overlaps, exclusive. - *metadata*: - *name*: -- *out_offset*: -- *transition_type*: +- *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 diff --git a/docs/tutorials/quickstart.md b/docs/tutorials/quickstart.md index 0c8ab27e4c..34a80fe3cd 100644 --- a/docs/tutorials/quickstart.md +++ b/docs/tutorials/quickstart.md @@ -9,9 +9,7 @@ This is for users who wish to get started using the "OTIOView" application to in OTIOView has an additional prerequisite to OTIO: - Try `python -m pip install PySide2` or `python -m pip install PySide6` -- If that doesn't work, try downloading PySide here: https://wiki.qt.io/Qt_for_Python - -You probably want the prebuilt binary for your platform. PySide generally includes a link to the appropriate version of Qt as well. +- If difficulties are encountered, please file an issue on OpenTimelineIO's github for assistance. ## Install OTIO @@ -19,8 +17,9 @@ You probably want the prebuilt binary for your platform. PySide generally inclu ## Configure Environment Variables for extra adapters -By default, when you install OTIO you will only get the "Core" adapters, which include CMX EDL, Final Cut Pro 7 XML, and the built in JSON format. In order to get access to the "contrib" adapters (which includes the maya sequencer, rv and others) you'll need to set some environment variables. If you need support for these formats, please consult the - Adapters documentation page for details +A default OTIO installation includes only the "Core" adapters, which include CMX EDL, Final Cut Pro 7 XML, and the built in JSON format. +In order to get access to the "contrib" adapters (which includes the maya sequencer, rv and others), please consult the +[Adapters documentation page for details](./adapters). ## Run OTIOView @@ -106,7 +105,7 @@ To use opentime without opentimelineio, link with -lopentime instead, and compil # Debugging Quickstart -### Linux / GDB / LLDB +## 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`. @@ -129,9 +128,9 @@ One handy tip is that you can trigger a breakpoint in gdb by inserting a SIGINT: GDB will automatically break when it hits the SIGINT line. -## How to Generate the C++ Documentation: +# How to Generate the C++ Documentation: -### Mac / Linux +## Mac / Linux The doxygen docs can be generated with the following commands: diff --git a/docs/tutorials/write-a-media-linker.md b/docs/tutorials/write-a-media-linker.md index 086019c4cf..0ec4089609 100644 --- a/docs/tutorials/write-a-media-linker.md +++ b/docs/tutorials/write-a-media-linker.md @@ -36,7 +36,7 @@ Finally, to specify this linker as the default media linker, set `OTIO_DEFAULT_M setenv OTIO_DEFAULT_MEDIA_LINKER "awesome_studios_media_linker" -To package and share your media linker, follow [these instructions](write-an-adapter.html#packaging-and-sharing-custom-adapters). +To package and share your media linker, follow [these instructions](write-an-adapter.md#packaging-and-sharing-custom-adapters). ## Writing a Media Linker diff --git a/docs/use-cases/animation-shot-frame-ranges.md b/docs/use-cases/animation-shot-frame-ranges.md index 3406a21eb4..ecaf71fd5a 100644 --- a/docs/use-cases/animation-shot-frame-ranges.md +++ b/docs/use-cases/animation-shot-frame-ranges.md @@ -5,7 +5,7 @@ ## Summary This case is very similar to the -Shots Added/Removed from the Cut Use Case. +[](/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 @@ -26,7 +26,7 @@ runs a Python script which compares the frame range of each shot used in the cut 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 Case: Conform New Renders into the Cut). +[](/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. @@ -35,8 +35,8 @@ those shots is now longer. - EDL reading - Clip names for video track - Source frame range for each clip - - Timing effects -- AAF reading + - [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 @@ -48,7 +48,7 @@ those shots is now longer. - Name - Metadata - Timing effects -- 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. diff --git a/docs/use-cases/shots-added-removed-from-cut.md b/docs/use-cases/shots-added-removed-from-cut.md index 766d1a609d..34e55690eb 100644 --- a/docs/use-cases/shots-added-removed-from-cut.md +++ b/docs/use-cases/shots-added-removed-from-cut.md @@ -36,7 +36,7 @@ deliver the new shot to editorial when it is ready. - EDL reading (done) - Clip names across all tracks -- AAF reading +- [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 diff --git a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp index 1e219a8194..6c5c523fa4 100644 --- a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp +++ b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp @@ -60,9 +60,15 @@ RationalTime _type_checked(py::object const& rhs, char const* op) { } void opentime_rationalTime_bindings(py::module m) { - py::class_(m, "RationalTime") + py::class_(m, "RationalTime", R"docstring( +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. +)docstring") .def(py::init(), "value"_a = 0, "rate"_a = 1) - .def("is_invalid_time", &RationalTime::is_invalid_time) + .def("is_invalid_time", &RationalTime::is_invalid_time, R"docstring( +Returns true if the time is invalid. The time is considered invalid if the value or the rate are a NaN value +or if the rate is less than or equal to zero. +)docstring") .def_property_readonly("value", &RationalTime::value) .def_property_readonly("rate", &RationalTime::rate) .def("rescaled_to", (RationalTime (RationalTime::*)(double) const) &RationalTime::rescaled_to, @@ -70,9 +76,9 @@ void opentime_rationalTime_bindings(py::module m) { .def("rescaled_to", (RationalTime (RationalTime::*)(RationalTime) const) &RationalTime::rescaled_to, "other"_a, R"docstring(Returns the time for time converted to new_rate.)docstring") .def("value_rescaled_to", (double (RationalTime::*)(double) const) &RationalTime::value_rescaled_to, - "new_rate"_a, R"docstring(Returns the time value for self converted to new_rate.)docstring") + "new_rate"_a, R"docstring(Returns the time value for "self" converted to new_rate.)docstring") .def("value_rescaled_to", (double (RationalTime::*)(RationalTime) const) &RationalTime::value_rescaled_to, - "other"_a, R"docstring(Returns the time value for self converted to new_rate.)docstring") + "other"_a) .def("almost_equal", &RationalTime::almost_equal, "other"_a, "delta"_a = 0) .def("__copy__", [](RationalTime rt) { return rt; @@ -81,17 +87,25 @@ void opentime_rationalTime_bindings(py::module m) { return rt; }, "copier"_a = py::none()) .def_static("duration_from_start_end_time", &RationalTime::duration_from_start_end_time, - "start_time"_a, "end_time_exclusive"_a) + "start_time"_a, "end_time_exclusive"_a, R"docstring( +Compute the duration of samples from first to last (excluding last). This is not the same as distance. + +For example, the duration of a clip from frame 10 to frame 15 is 5 frames. Result will be in the rate of start_time. +)docstring") .def_static("duration_from_start_end_time_inclusive", &RationalTime::duration_from_start_end_time_inclusive, - "start_time"_a, "end_time_inclusive"_a) - .def_static("is_valid_timecode_rate", &RationalTime::is_valid_timecode_rate, "rate"_a) + "start_time"_a, "end_time_inclusive"_a, R"docstring( +Compute the duration of samples from first to last (including last). 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. +)docstring") + .def_static("is_valid_timecode_rate", &RationalTime::is_valid_timecode_rate, "rate"_a, "Returns true if the rate is valid for use with timecode.") .def_static("nearest_valid_timecode_rate", &RationalTime::nearest_valid_timecode_rate, "rate"_a, - R"docstring(Returns the first valid timecode rate that has the least difference from the given value.)docstring") - .def_static("from_frames", &RationalTime::from_frames, "frame"_a, "rate"_a) + "Returns the first valid timecode rate that has the least difference from the given value.") + .def_static("from_frames", &RationalTime::from_frames, "frame"_a, "rate"_a, "Turn a frame number and rate into a :class:`~RationalTime` object.") .def_static("from_seconds", static_cast (&RationalTime::from_seconds), "seconds"_a, "rate"_a) .def_static("from_seconds", static_cast (&RationalTime::from_seconds), "seconds"_a) - .def("to_frames", (int (RationalTime::*)() const) &RationalTime::to_frames) - .def("to_frames", (int (RationalTime::*)(double) const) &RationalTime::to_frames, "rate"_a) + .def("to_frames", (int (RationalTime::*)() const) &RationalTime::to_frames, "Returns the frame number based on the current rate.") + .def("to_frames", (int (RationalTime::*)(double) const) &RationalTime::to_frames, "rate"_a, "Returns the frame number based on the given rate.") .def("to_seconds", &RationalTime::to_seconds) .def("to_timecode", [](RationalTime rt, double rate, py::object drop_frame) { return rt.to_timecode( @@ -99,7 +113,7 @@ void opentime_rationalTime_bindings(py::module m) { df_enum_converter(drop_frame), ErrorStatusConverter() ); - }, "rate"_a, "drop_frame"_a) + }, "rate"_a, "drop_frame"_a, "Convert to timecode (``HH:MM:SS;FRAME``)") .def("to_timecode", [](RationalTime rt, double rate) { return rt.to_timecode( rate, @@ -116,10 +130,10 @@ void opentime_rationalTime_bindings(py::module m) { .def("to_time_string", &RationalTime::to_time_string) .def_static("from_timecode", [](std::string s, double rate) { return RationalTime::from_timecode(s, rate, ErrorStatusConverter()); - }, "timecode"_a, "rate"_a) + }, "timecode"_a, "rate"_a, "Convert a timecode string (``HH:MM:SS;FRAME``) into a :class:`~RationalTime`.") .def_static("from_time_string", [](std::string s, double rate) { return RationalTime::from_time_string(s, rate, ErrorStatusConverter()); - }, "time_string"_a, "rate"_a) + }, "time_string"_a, "rate"_a, "Convert a time with microseconds string (``HH:MM:ss`` where ``ss`` is an integer or a decimal number) into a :class:`~RationalTime`.") .def("__str__", &opentime_python_str) .def("__repr__", &opentime_python_repr) .def(- py::self) diff --git a/src/py-opentimelineio/opentime-bindings/opentime_timeRange.cpp b/src/py-opentimelineio/opentime-bindings/opentime_timeRange.cpp index de6611d955..d010cf8800 100644 --- a/src/py-opentimelineio/opentime-bindings/opentime_timeRange.cpp +++ b/src/py-opentimelineio/opentime-bindings/opentime_timeRange.cpp @@ -14,7 +14,11 @@ using namespace opentime; void opentime_timeRange_bindings(py::module m) { - py::class_(m, "TimeRange") + py::class_(m, "TimeRange", R"docstring( +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. +)docstring") // matches the python constructor behavior .def(py::init( [](RationalTime* start_time, RationalTime* duration) { @@ -41,12 +45,12 @@ void opentime_timeRange_bindings(py::module m) { .def_property_readonly("start_time", &TimeRange::start_time) .def_property_readonly("duration", &TimeRange::duration) .def("end_time_inclusive", &TimeRange::end_time_inclusive, R"docstring( -The time of the last sample containing data in the TimeRange. +The time of the last sample containing data in the time range. -If the TimeRange starts at (0,24) with duration (10,24), this will be -(9,24) +If the time range starts at (0, 24) with duration (10, 24), this will be +(9, 24) -If the TimeRange starts at (0,24) with duration (10.5, 24): +If the time range starts at (0, 24) with duration (10.5, 24): (10, 24) In other words, the last frame with data, even if the last frame is fractional. @@ -54,30 +58,31 @@ In other words, the last frame with data, even if the last frame is fractional. .def("end_time_exclusive", &TimeRange::end_time_exclusive, R"docstring( Time of the first sample outside the time range. -If Start frame is 10 and duration is 5, then end_time_exclusive is 15, +If start frame is 10 and duration is 5, then end_time_exclusive is 15, because the last time with data in this range is 14. -If Start frame is 10 and duration is 5.5, then end_time_exclusive is +If start frame is 10 and duration is 5.5, then end_time_exclusive is 15.5, because the last time with data in this range is 15. )docstring") .def("duration_extended_by", &TimeRange::duration_extended_by, "other"_a) - .def("extended_by", &TimeRange::extended_by, "other"_a, R"docstring(Construct a new TimeRange that is this one extended by other.)docstring") + .def("extended_by", &TimeRange::extended_by, "other"_a, "Construct a new :class:`~TimeRange` that is this one extended by other.") .def("clamped", (RationalTime (TimeRange::*)(RationalTime) const) &TimeRange::clamped, "other"_a, R"docstring( -Clamp 'other' (RationalTime) according to -self.start_time/end_time_exclusive and bound arguments. +Clamp 'other' (:class:`~RationalTime`) according to +:attr:`start_time`/:attr:`end_time_exclusive` and bound arguments. )docstring") .def("clamped", (TimeRange (TimeRange::*)(TimeRange) const) &TimeRange::clamped, "other"_a, R"docstring( -Clamp 'other' (TimeRange) according to -self.start_time/end_time_exclusive and bound arguments. +Clamp 'other' (:class:`~TimeRange`) according to +:attr:`start_time`/:attr:`end_time_exclusive` and bound arguments. )docstring") .def("contains", (bool (TimeRange::*)(RationalTime) const) &TimeRange::contains, "other"_a, R"docstring( The start of `this` precedes `other`. `other` precedes the end of `this`. :: - other - ↓ - * - [ this ] + + other + ↓ + * + [ this ] )docstring") .def("contains", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::contains, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( @@ -85,86 +90,106 @@ The start of `this` precedes start of `other`. The end of `this` antecedes end of `other`. :: - [ other ] - [ this ] + [ other ] + [ this ] The converse would be ``other.contains(this)`` )docstring") .def("overlaps", (bool (TimeRange::*)(RationalTime) const) &TimeRange::overlaps, "other"_a, R"docstring( `this` contains `other`. :: - other - ↓ - * - [ this ] + + other + ↓ + * + [ this ] + )docstring") .def("overlaps", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::overlaps, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( 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 ] + + [ this ] + [ other ] + The converse would be ``other.overlaps(this)`` )docstring") .def("before", (bool (TimeRange::*)(RationalTime, double ) const) &TimeRange::before, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( The end of `this` strictly precedes `other` by a value >= `epsilon_s`. :: - other - ↓ - [ this ] * + + other + ↓ + [ this ] * + )docstring") .def("before", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::before, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( The end of `this` strictly equals the start of `other` and the start of `this` strictly equals the end of `other`. :: - [this][other] + + [this][other] + The converse would be ``other.meets(this)`` )docstring") .def("meets", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::meets, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( The end of `this` strictly equals the start of `other` and the start of `this` strictly equals the end of `other`. :: - [this][other] + + [this][other] + The converse would be ``other.meets(this)`` )docstring") .def("begins", (bool (TimeRange::*)(RationalTime, double) const) &TimeRange::begins, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( The start of `this` strictly equals `other`. :: - other - ↓ - * - [ this ] + + other + ↓ + * + [ this ] + )docstring") .def("begins", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::begins, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( 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 ] + + [ this ] + [ other ] + The converse would be ``other.begins(this)`` )docstring") .def("finishes", (bool (TimeRange::*)(RationalTime, double) const) &TimeRange::finishes, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( The end of `this` strictly equals `other`. :: - other - ↓ - * - [ this ] + + other + ↓ + * + [ this ] + )docstring") .def("finishes", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::finishes, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( 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 ] + + [ this ] + [ other ] + The converse would be ``other.finishes(this)`` )docstring") .def("intersects", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::intersects, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s, R"docstring( 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 ] + + [ this ] OR [ other ] + [ other ] [ this ] + The converse would be ``other.finishes(this)`` )docstring") .def("__copy__", [](TimeRange tr) { @@ -174,9 +199,17 @@ The converse would be ``other.finishes(this)`` return tr; }) .def_static("range_from_start_end_time", &TimeRange::range_from_start_end_time, - "start_time"_a, "end_time_exclusive"_a) + "start_time"_a, "end_time_exclusive"_a, R"docstring( +Creates a :class:`~TimeRange` from start and end :class:`~RationalTime`\s (exclusive). + +For example, if start_time is 1 and end_time is 10, the returned will have a duration of 9. +)docstring") .def_static("range_from_start_end_time_inclusive", &TimeRange::range_from_start_end_time_inclusive, - "start_time"_a, "end_time_inclusive"_a) + "start_time"_a, "end_time_inclusive"_a, R"docstring( +Creates a :class:`~TimeRange` from start and end :class:`~RationalTime`\s (inclusive). + +For example, if start_time is 1 and end_time is 10, the returned will have a duration of 10. +)docstring") .def(py::self == py::self) .def(py::self != py::self) .def("__str__", [](TimeRange tr) { diff --git a/src/py-opentimelineio/opentime-bindings/opentime_timeTransform.cpp b/src/py-opentimelineio/opentime-bindings/opentime_timeTransform.cpp index ebfd31189f..26a05b2453 100644 --- a/src/py-opentimelineio/opentime-bindings/opentime_timeTransform.cpp +++ b/src/py-opentimelineio/opentime-bindings/opentime_timeTransform.cpp @@ -14,7 +14,7 @@ using namespace opentime; void opentime_timeTransform_bindings(py::module m) { - py::class_(m, "TimeTransform") + py::class_(m, "TimeTransform", R"docstring(1D transform for :class:`~RationalTime`. Has offset and scale.)docstring") .def(py::init(), "offset"_a = RationalTime(), "scale"_a = 1, "rate"_a = -1) .def_property_readonly("offset", &TimeTransform::offset) diff --git a/src/py-opentimelineio/opentimelineio-bindings/otio_bindings.cpp b/src/py-opentimelineio/opentimelineio-bindings/otio_bindings.cpp index 1ead464309..ae8472c7f3 100644 --- a/src/py-opentimelineio/opentimelineio-bindings/otio_bindings.cpp +++ b/src/py-opentimelineio/opentimelineio-bindings/otio_bindings.cpp @@ -128,7 +128,11 @@ PYBIND11_MODULE(_otio, m) { m.def("install_external_keepalive_monitor", &install_external_keepalive_monitor, "so"_a, "apply_now"_a); m.def("instance_from_schema", &instance_from_schema, - "schema_name"_a, "schema_version"_a, "data"_a); + "schema_name"_a, "schema_version"_a, "data"_a, R"docstring( +Return an instance of the schema from data in the data_dict. + +:raises UnsupportedSchemaError: when the requested schema version is greater than the registered schema version. +)docstring"); m.def("register_upgrade_function", ®ister_upgrade_function, "schema_name"_a, "version_to_upgrade_to"_a, diff --git a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp index 9e843ceb64..ffa8616d5e 100644 --- a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp +++ b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp @@ -154,7 +154,7 @@ class ContainerIterator { }; static void define_bases1(py::module m) { - py::class_>(m, "SerializableObject", py::dynamic_attr()) + py::class_>(m, "SerializableObject", py::dynamic_attr(), "Superclass for all classes whose instances can be serialized.") .def(py::init<>()) .def_property_readonly("_dynamic_fields", [](SerializableObject* s) { auto ptr = s->dynamic_fields().get_or_create_mutation_stamp(); @@ -203,20 +203,12 @@ static void define_bases2(py::module m) { MarkerVectorProxy::define_py_class(m, "MarkerVector"); EffectVectorProxy::define_py_class(m, "EffectVector"); - py::class_>(m, "Composable", py::dynamic_attr()) - .def(py::init([](std::string const& name, - py::object metadata) { - return new Composable(name, py_to_any_dictionary(metadata)); - }), - py::arg_v("name"_a = std::string()), - py::arg_v("metadata"_a = py::none())) - .def("parent", &Composable::parent) - .def("visible", &Composable::visible) - .def("overlapping", &Composable::overlapping); - auto marker_class = - py::class_>(m, "Marker", py::dynamic_attr()) + py::class_>(m, "Marker", py::dynamic_attr(), R"docstring( +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. +)docstring") .def(py::init([]( py::object name, TimeRange marked_range, @@ -232,8 +224,8 @@ static void define_bases2(py::module m) { "marked_range"_a = TimeRange(), "color"_a = std::string(Marker::Color::red), py::arg_v("metadata"_a = py::none())) - .def_property("color", &Marker::color, &Marker::set_color) - .def_property("marked_range", &Marker::marked_range, &Marker::set_marked_range); + .def_property("color", &Marker::color, &Marker::set_color, "Color string for this marker (for example: 'RED'), based on the :class:`~Color` enum.") + .def_property("marked_range", &Marker::marked_range, &Marker::set_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)."); py::class_(marker_class, "Color") .def_property_readonly_static("PINK", [](py::object /* self */) { return Marker::Color::pink; }) @@ -255,7 +247,15 @@ static void define_bases2(py::module m) { .def("next", &SerializableCollectionIterator::next); py::class_>(m, "SerializableCollection", py::dynamic_attr()) + managing_ptr>(m, "SerializableCollection", py::dynamic_attr(), R"docstring( +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. +)docstring") .def(py::init([](std::string const& name, py::object children, py::object metadata) { return new SerializableCollection(name, @@ -299,6 +299,11 @@ static void define_bases2(py::module m) { } static void define_items_and_compositions(py::module m) { + auto composable_class = py::class_>(m, "Composable", py::dynamic_attr(), R"docstring( +An object that can be composed within a :class:`~Composition` (such as :class:`~Track` or :class:`.Stack`). +)docstring"); + py::class_>(m, "Item", py::dynamic_attr()) .def(py::init([](std::string name, optional source_range, py::object effects, py::object markers, py::bool_ enabled, py::object metadata) { @@ -313,7 +318,7 @@ static void define_items_and_compositions(py::module m) { "markers"_a = py::none(), "enabled"_a = true, py::arg_v("metadata"_a = py::none())) - .def_property("enabled", &Item::enabled, &Item::set_enabled, "If true, an Item contributes to compositions. Analogous to Mute in various NLEs.") + .def_property("enabled", &Item::enabled, &Item::set_enabled, "If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden.") .def_property("source_range", &Item::source_range, &Item::set_source_range) .def("available_range", [](Item* item) { return item->available_range(ErrorStatusHandler()); @@ -350,7 +355,7 @@ static void define_items_and_compositions(py::module m) { }); auto transition_class = - py::class_>(m, "Transition", py::dynamic_attr()) + py::class_>(m, "Transition", py::dynamic_attr(), "Represents a transition between the two adjacent items in a :class:`.Track`. For example, a cross dissolve or wipe.") .def(py::init([](std::string const& name, std::string const& transition_type, RationalTime in_offset, RationalTime out_offset, py::object metadata) { @@ -362,21 +367,24 @@ static void define_items_and_compositions(py::module m) { "in_offset"_a = RationalTime(), "out_offset"_a = RationalTime(), py::arg_v("metadata"_a = py::none())) - .def_property("transition_type", &Transition::transition_type, &Transition::set_transition_type) - .def_property("in_offset", &Transition::in_offset, &Transition::set_in_offset) - .def_property("out_offset", &Transition::out_offset, &Transition::set_out_offset) + .def_property("transition_type", &Transition::transition_type, &Transition::set_transition_type, "Kind of transition, as defined by the :class:`Type` enum.") + .def_property("in_offset", &Transition::in_offset, &Transition::set_in_offset, "Amount of the previous clip this transition overlaps, exclusive.") + .def_property("out_offset", &Transition::out_offset, &Transition::set_out_offset, "Amount of the next clip this transition overlaps, exclusive.") .def("duration", [](Transition* t) { return t->duration(ErrorStatusHandler()); }) .def("range_in_parent", [](Transition* t) { return t->range_in_parent(ErrorStatusHandler()); - }) + }, "Find and return the range of this item in the parent.") .def("trimmed_range_in_parent", [](Transition* t) { return t->trimmed_range_in_parent(ErrorStatusHandler()); - }); + }, "Find and return the timmed range of this item in the parent."); + py::class_(transition_class, "Type", R"docstring( +Enum encoding types of transitions. - py::class_(transition_class, "Type") +Other effects are handled by the :class:`Effect` class. +)docstring") .def_property_readonly_static("SMPTE_Dissolve", [](py::object /* self */) { return Transition::Type::SMPTE_Dissolve; }) .def_property_readonly_static("Custom", [](py::object /* self */) { return Transition::Type::Custom; }); @@ -405,7 +413,11 @@ static void define_items_and_compositions(py::module m) { "markers"_a = py::none(), py::arg_v("metadata"_a = py::none())); - auto clip_class = py::class_>(m, "Clip", py::dynamic_attr()) + auto clip_class = py::class_>(m, "Clip", py::dynamic_attr(), R"docstring( +A :class:`~Clip` is a segment of editable media (usually audio or video). + +Contains a :class:`.MediaReference` and a trim on that media reference. +)docstring") .def(py::init([](std::string name, MediaReference* media_reference, optional source_range, py::object metadata, const std::string& active_media_reference) { @@ -433,7 +445,11 @@ static void define_items_and_compositions(py::module m) { .def("__iter__", &CompositionIterator::iter) .def("next", &CompositionIterator::next); - py::class_>(m, "Composition", py::dynamic_attr()) + py::class_>(m, "Composition", py::dynamic_attr(), R"docstring( +Base class for an :class:`~Item` that contains other :class:`~Item`\s. + +Should be subclassed (for example by :class:`.Track` and :class:`.Stack`), not used directly. +)docstring") .def(py::init([](std::string name, py::object children, optional source_range, py::object metadata) { @@ -481,14 +497,14 @@ static void define_items_and_compositions(py::module m) { l.push_back(child.value); } return l; - }) + }, "search_range"_a) .def("children_if", [](Composition* t, py::object descended_from_type, optional const& search_range, bool shallow_search) { return children_if(t, descended_from_type, search_range, shallow_search); }, "descended_from_type"_a = py::none(), "search_range"_a = nullopt, "shallow_search"_a = false) .def("handles_of_child", [](Composition* c, Composable* child) { auto result = c->handles_of_child(child, ErrorStatusHandler()); return py::make_tuple(py::cast(result.first), py::cast(result.second)); - }, "child_a") + }, "child"_a) .def("has_clips", &Composition::has_clips) .def("__internal_getitem__", [](Composition* c, int index) { index = adjusted_vector_index(index, c->children()); @@ -517,6 +533,17 @@ static void define_items_and_compositions(py::module m) { return new CompositionIterator(c); }); + composable_class + .def(py::init([](std::string const& name, + py::object metadata) { + return new Composable(name, py_to_any_dictionary(metadata)); + }), + py::arg_v("name"_a = std::string()), + py::arg_v("metadata"_a = py::none())) + .def("parent", &Composable::parent) + .def("visible", &Composable::visible) + .def("overlapping", &Composable::overlapping); + auto track_class = py::class_>(m, "Track", py::dynamic_attr()); py::enum_(track_class, "NeighborGapPolicy") @@ -633,7 +660,7 @@ static void define_effects(py::module m) { py::arg_v("metadata"_a = py::none())) .def_property("effect_name", &Effect::effect_name, &Effect::set_effect_name); - py::class_>(m, "TimeEffect", py::dynamic_attr()) + py::class_>(m, "TimeEffect", py::dynamic_attr(), "Base class for all effects that alter the timing of an item.") .def(py::init([](std::string name, std::string effect_name, py::object metadata) { @@ -642,7 +669,9 @@ static void define_effects(py::module m) { "effect_name"_a = std::string(), py::arg_v("metadata"_a = py::none())); - py::class_>(m, "LinearTimeWarp", py::dynamic_attr()) + py::class_>(m, "LinearTimeWarp", py::dynamic_attr(), R"docstring( +A time warp that applies a linear speed up or slow down across the entire clip. +)docstring") .def(py::init([](std::string name, double time_scalar, py::object metadata) { @@ -651,9 +680,15 @@ static void define_effects(py::module m) { py::arg_v("name"_a = std::string()), "time_scalar"_a = 1.0, py::arg_v("metadata"_a = py::none())) - .def_property("time_scalar", &LinearTimeWarp::time_scalar, &LinearTimeWarp::set_time_scalar); + .def_property("time_scalar", &LinearTimeWarp::time_scalar, &LinearTimeWarp::set_time_scalar, R"docstring( +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. +)docstring"); - py::class_>(m, "FreezeFrame", py::dynamic_attr()) + py::class_>(m, "FreezeFrame", py::dynamic_attr(), "Hold the first frame of the clip for the duration of the clip.") .def(py::init([](std::string name, py::object metadata) { return new FreezeFrame(name, py_to_any_dictionary(metadata)); }), py::arg_v("name"_a = std::string()), @@ -701,7 +736,11 @@ static void define_media_references(py::module m) { py::class_>(m, "MissingReference", py::dynamic_attr()) + managing_ptr>(m, "MissingReference", py::dynamic_attr(), R"docstring( +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. +)docstring") .def(py::init([]( py::object name, optional available_range, @@ -737,13 +776,15 @@ static void define_media_references(py::module m) { auto imagesequencereference_class = py:: class_>(m, "ImageSequenceReference", py::dynamic_attr(), R"docstring( -An ImageSequenceReference refers to a numbered series of single-frame image files. Each file can be referred to by a URL generated by the ImageSequenceReference. +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 sequncences 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. +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:: +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": { @@ -765,7 +806,9 @@ An example for 24fps media with a sample provided each frame numbered 1-1000 wit "frame_zero_padding": 4, } -The same duration sequence but with only every 2nd frame available in the sequence would be:: +The same duration sequence but with only every 2nd frame available in the sequence would be + +.. code-block:: json { "available_range": { @@ -787,7 +830,9 @@ The same duration sequence but with only every 2nd frame available in the sequen "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:: +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())] @@ -844,12 +889,12 @@ Negative ``start_frame`` is also handled. The above example with a ``start_frame .def_property("frame_step", &ImageSequenceReference::frame_step, &ImageSequenceReference::set_frame_step, "Step between frame numbers in file names.") .def_property("rate", &ImageSequenceReference::rate, &ImageSequenceReference::set_rate, "Frame rate if every frame in the sequence were played back.") .def_property("frame_zero_padding", &ImageSequenceReference::frame_zero_padding, &ImageSequenceReference::set_frame_zero_padding, "Number of digits to pad zeros out to in frame numbers.") - .def_property("missing_frame_policy", &ImageSequenceReference::missing_frame_policy, &ImageSequenceReference::set_missing_frame_policy, "Enum ``ImageSequenceReference.MissingFramePolicy`` directive for how frames in sequence not found on disk should be handled.") - .def("end_frame", &ImageSequenceReference::end_frame, "Last frame number in the sequence based on the ``rate`` and ``available_range``.") - .def("number_of_images_in_sequence", &ImageSequenceReference::number_of_images_in_sequence, "Returns the number of images based on the ``rate`` and ``available_range``.") + .def_property("missing_frame_policy", &ImageSequenceReference::missing_frame_policy, &ImageSequenceReference::set_missing_frame_policy, "Directive for how frames in sequence not found during playback or rendering should be handled.") + .def("end_frame", &ImageSequenceReference::end_frame, "Last frame number in the sequence based on the :attr:`rate` and :attr:`.available_range`.") + .def("number_of_images_in_sequence", &ImageSequenceReference::number_of_images_in_sequence, "Returns the number of images based on the :attr:`rate` and :attr:`.available_range`.") .def("frame_for_time", [](ImageSequenceReference *seq_ref, RationalTime time) { return seq_ref->frame_for_time(time, ErrorStatusHandler()); - }, "time"_a, "Given a :class:`RationalTime` within the available range, returns the frame number.") + }, "time"_a, "Given a :class:`.RationalTime` within the available range, returns the frame number.") .def("target_url_for_image_number", [](ImageSequenceReference *seq_ref, int image_number) { return seq_ref->target_url_for_image_number( image_number, @@ -858,14 +903,18 @@ Negative ``start_frame`` is also handled. The above example with a ``start_frame }, "image_number"_a, R"docstring(Given an image number, returns the ``target_url`` for that image. This is roughly equivalent to: - ``f"{target_url_prefix}{(start_frame + (image_number * frame_step)):0{value_zero_padding}}{target_url_postfix}`` + +.. code-block:: python + + f"{target_url_prefix}{(start_frame + (image_number * frame_step)):0{value_zero_padding}}{target_url_postfix}" + )docstring") .def("presentation_time_for_image_number", [](ImageSequenceReference *seq_ref, int image_number) { return seq_ref->presentation_time_for_image_number( image_number, ErrorStatusHandler() ); - }, "image_number"_a, "Given an image number, returns the :class:`RationalTime` at which that image should be shown in the space of `available_range`."); + }, "image_number"_a, "Given an image number, returns the :class:`.RationalTime` at which that image should be shown in the space of :attr:`.available_range`."); } diff --git a/src/py-opentimelineio/opentimelineio/adapters/__init__.py b/src/py-opentimelineio/opentimelineio/adapters/__init__.py index ead4e4974c..6ed93684d6 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/__init__.py +++ b/src/py-opentimelineio/opentimelineio/adapters/__init__.py @@ -30,6 +30,20 @@ file_bundle_utils, # utilities for working with OTIO file bundles ) +__all__ = [ + 'Adapter', + 'otio_json', + 'file_bundle_utils', + 'suffixes_with_defined_adapters', + 'available_adapter_names', + 'from_filepath', + 'from_name', + 'read_from_file', + 'read_from_string', + 'write_to_file', + 'write_to_string' +] + def suffixes_with_defined_adapters(read=False, write=False): """Return a set of all the suffixes that have adapters defined for them.""" @@ -70,8 +84,7 @@ def _from_filepath_or_name(filepath, adapter_name): def from_filepath(filepath): """Guess the adapter object to use for a given filepath. - example: - "foo.otio" returns the "otio_json" adapter. + For example, ``foo.otio`` returns the ``otio_json`` adapter. """ outext = os.path.splitext(filepath)[1][1:] @@ -112,7 +125,9 @@ def read_from_file( If adapter_name is None, try and infer the adapter name from the filepath. - For example: + .. code-block:: python + :caption: Example + timeline = read_from_file("example_trailer.otio") timeline = read_from_file("file_with_no_extension", "cmx_3600") """ @@ -139,7 +154,9 @@ def read_from_string( This is useful if you obtain a timeline from someplace other than the filesystem. - Example: + .. code-block:: python + :caption: Example + raw_text = urlopen(my_url).read() timeline = read_from_string(raw_text, "otio_json") """ @@ -164,7 +181,9 @@ def write_to_file( If adapter_name is None, infer the adapter_name to use based on the filepath. - Example: + .. code-block:: python + :caption: Example + otio.adapters.write_to_file(my_timeline, "output.otio") """ @@ -184,7 +203,9 @@ def write_to_string( ): """Return input_otio written to a string using adapter_name. - Example: + .. code-block:: python + :caption: Example + raw_text = otio.adapters.write_to_string(my_timeline, "otio_json") """ diff --git a/src/py-opentimelineio/opentimelineio/adapters/adapter.py b/src/py-opentimelineio/opentimelineio/adapters/adapter.py index 1f12e35a1d..30be763a51 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/adapter.py +++ b/src/py-opentimelineio/opentimelineio/adapters/adapter.py @@ -4,7 +4,7 @@ """Implementation of the OTIO internal `Adapter` system. For information on writing adapters, please consult: - https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html# # noqa +https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html # noqa """ import inspect @@ -30,9 +30,11 @@ class Adapter(plugins.PythonPlugin): """Adapters convert between OTIO and other formats. - Note that this class is not subclassed by adapters. Rather, an adapter is + 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) @@ -43,8 +45,7 @@ class Adapter(plugins.PythonPlugin): 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 + For more information: https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html. # noqa """ _serializable_label = "Adapter.1" @@ -75,7 +76,7 @@ def has_feature(self, feature_string): return true if adapter supports feature_string, which must be a key of the _FEATURE_MAP dictionary. - Will trigger a call to self.module(), which imports the plugin. + Will trigger a call to :meth:`.PythonPlugin.module`, which imports the plugin. """ if feature_string.lower() not in _FEATURE_MAP: diff --git a/src/py-opentimelineio/opentimelineio/adapters/fcp_xml.py b/src/py-opentimelineio/opentimelineio/adapters/fcp_xml.py index 9653421ab2..ae1bf708eb 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/fcp_xml.py +++ b/src/py-opentimelineio/opentimelineio/adapters/fcp_xml.py @@ -561,9 +561,7 @@ class FCP7XMLParser: 1. Inheritance 2. The id Attribute - .. seealso:: https://developer.apple.com/library/archive/documentation/\ - AppleApplications/Reference/FinalCutPro_XML/Basics/Basics.html\ - #//apple_ref/doc/uid/TP30001154-TPXREF102 + .. seealso:: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/FinalCutPro_XML/Basics/Basics.html#//apple_ref/doc/uid/TP30001154-TPXREF102 # noqa Inheritance is implemented using a _Context object that is pushed down through layers of parsing. A given parsing method is passed the element to diff --git a/src/py-opentimelineio/opentimelineio/adapters/svg.py b/src/py-opentimelineio/opentimelineio/adapters/svg.py index 25c3fdb58c..2f8565c483 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/svg.py +++ b/src/py-opentimelineio/opentimelineio/adapters/svg.py @@ -12,8 +12,7 @@ # python import math -from random import seed -from random import random +import random random_colors_used = [] @@ -48,7 +47,7 @@ def __generate_new_color(): @staticmethod def __get_random_color(): - return Color(random(), random(), random(), 1.0) + return Color(random.random(), random.random(), random.random(), 1.0) @staticmethod def __color_distance(c1, c2): @@ -1101,7 +1100,7 @@ def convert_otio_to_svg(timeline, width, height): font_family='sans-serif', image_margin=20.0, font_size=15.0, arrow_label_margin=5.0) random_colors_used = [] - seed(100) + random.seed(100) draw_item(timeline, svg_writer, ()) return svg_writer.get_image() diff --git a/src/py-opentimelineio/opentimelineio/algorithms/filter.py b/src/py-opentimelineio/opentimelineio/algorithms/filter.py index 5ffe8ba78a..2f294fc000 100644 --- a/src/py-opentimelineio/opentimelineio/algorithms/filter.py +++ b/src/py-opentimelineio/opentimelineio/algorithms/filter.py @@ -23,23 +23,31 @@ def filtered_composition( unary_filter_fn, types_to_prune=None, ): - """Filter a deep copy of root (and children) with unary_filter_fn. + """ + Filter a deep copy of root (and children) with ``unary_filter_fn``. + + The ``unary_filter_fn`` must have this signature: + + .. py:function:: func(item: typing.Any) -> list[typing.Any] + :noindex: - types_to_prune:: tuple of types, example: (otio.schema.Gap,...) 1. Make a deep copy of root 2. Starting with root, perform a depth first traversal 3. For each item (including root): - a. if types_to_prune is not None and item is an instance of a type - in types_to_prune, prune it from the copy, continue. - b. Otherwise, pass the copy to unary_filter_fn. If unary_filter_fn: - I. returns an object: add it to the copy, replacing original - II. returns a tuple: insert it into the list, replacing original - III. returns None: prune it + + a. If ``types_to_prune`` is not None and item is an instance of a type in + ``types_to_prune``, prune it from the copy, continue. + b. Otherwise, pass the copy to ``unary_filter_fn``. If ``unary_filter_fn``: + + I. Returns an object: add it to the copy, replacing original + II. Returns a tuple: insert it into the list, replacing original + III. Returns None: prune it 4. If an item is pruned, do not traverse its children 5. Return the new deep copy. - EXAMPLE 1 (filter): + Example 1 (filter):: + If your unary function is: def fn(thing): if thing.name == B: @@ -51,7 +59,8 @@ def fn(thing): filtered_composition(track, fn) => [A,B',C] - EXAMPLE 2 (prune): + Example 2 (prune):: + If your unary function is: def fn(thing): if thing.name == B: @@ -61,7 +70,8 @@ def fn(thing): filtered_composition(track, fn) => [A,C] - EXAMPLE 3 (expand): + Example 3 (expand):: + If your unary function is: def fn(thing): if thing.name == B: @@ -71,10 +81,15 @@ def fn(thing): filtered_composition(track, fn) => [A,B_1,B_2,B_3,C] - EXAMPLE 4 (prune gaps): + Example 4 (prune gaps):: + track :: [Gap, A, Gap] filtered_composition( track, lambda _:_, types_to_prune=(otio.schema.Gap,)) => [A] + + :param SerializableObjectWithMetadata root: Object to filter on + :param unary_filter_fn: Filter function + :param tuple(type) types_to_prune: Types to prune. Example: (otio.schema.Gap,...) """ # deep copy everything @@ -135,27 +150,35 @@ def filtered_with_sequence_context( reduce_fn, types_to_prune=None, ): - """Filter a deep copy of root (and children) with reduce_fn. + """Filter a deep copy of root (and children) with ``reduce_fn``. - reduce_fn::function(previous_item, current, next_item) (see below) - types_to_prune:: tuple of types, example: (otio.schema.Gap,...) + The ``reduce_fn`` must have this signature: + + .. currentmodule:: _ + + .. py:function:: func(previous_item: typing.Any, current: typing.Any, next_item: typing.Any) -> list[typing.Any] # noqa + :noindex: 1. Make a deep copy of root 2. Starting with root, perform a depth first traversal 3. For each item (including root): - a. if types_to_prune is not None and item is an instance of a type - in types_to_prune, prune it from the copy, continue. - b. Otherwise, pass (prev, copy, and next) to reduce_fn. If reduce_fn: - I. returns an object: add it to the copy, replacing original - II. returns a tuple: insert it into the list, replacing original - III. returns None: prune it - - ** note that reduce_fn is always passed objects from the original - deep copy, not what prior calls return. See below for examples + + a. if types_to_prune is not None and item is an instance of a type + in types_to_prune, prune it from the copy, continue. + b. Otherwise, pass (prev, copy, and next) to reduce_fn. If ``reduce_fn``: + + I. returns an object: add it to the copy, replacing original + II. returns a tuple: insert it into the list, replacing original + III. returns None: prune it + + .. note:: ``reduce_fn`` is always passed objects from the original + deep copy, not what prior calls return. See below for examples + 4. If an item is pruned, do not traverse its children 5. Return the new deep copy. - EXAMPLE 1 (filter): + Example 1 (filter):: + >>> track = [A,B,C] >>> def fn(prev_item, thing, next_item): ... if prev_item.name == A: @@ -169,7 +192,8 @@ def filtered_with_sequence_context( fn(A, B, C) => D fn(B, C, D) => C # !! note that it was passed B instead of D. - EXAMPLE 2 (prune): + Example 2 (prune):: + >>> track = [A,B,C] >>> def fn(prev_item, thing, next_item): ... if prev_item.name == A: @@ -183,7 +207,8 @@ def filtered_with_sequence_context( fn(A, B, C) => None fn(B, C, D) => C # !! note that it was passed B instead of D. - EXAMPLE 3 (expand): + Example 3 (expand):: + >>> def fn(prev_item, thing, next_item): ... if prev_item.name == A: ... return (D, E) # tuple of new clips @@ -195,6 +220,10 @@ def filtered_with_sequence_context( fn(None, A, B) => A fn(A, B, C) => (D, E) fn(B, C, D) => C # !! note that it was passed B instead of D. + + :param SerializableObjectWithMetadata root: Object to filter on + :param reduce_fn: Filter function + :param tuple(type) types_to_prune: Types to prune. Example: (otio.schema.Gap,...) """ # deep copy everything diff --git a/src/py-opentimelineio/opentimelineio/algorithms/stack_algo.py b/src/py-opentimelineio/opentimelineio/algorithms/stack_algo.py index 3c45325021..ab22b5b3e0 100644 --- a/src/py-opentimelineio/opentimelineio/algorithms/stack_algo.py +++ b/src/py-opentimelineio/opentimelineio/algorithms/stack_algo.py @@ -11,15 +11,21 @@ def top_clip_at_time(in_stack, t): - """Return the topmost visible child that overlaps with time t. + """Return the topmost visible child that overlaps with time ``t``. - Example: - tr1: G1, A, G2 - tr2: [B------] - G1, and G2 are gaps, A and B are clips. + Example:: - If t is within A, a will be returned. If t is within G1 or G2, B will be - returned. + tr1: G1, A, G2 + tr2: [B------] + G1, and G2 are gaps, A and B are clips. + + If ``t`` is within ``A``, ``A`` will be returned. If ``t`` is within ``G1`` or + ``G2``, ``B`` will be returned. + + :param Stack in_stack: Stack + :param RationalTime t: Time + :returns: Top clip + :rtype: Clip """ # ensure that it only runs on stacks diff --git a/src/py-opentimelineio/opentimelineio/algorithms/timeline_algo.py b/src/py-opentimelineio/opentimelineio/algorithms/timeline_algo.py index 706fac6b2e..3c97d55943 100644 --- a/src/py-opentimelineio/opentimelineio/algorithms/timeline_algo.py +++ b/src/py-opentimelineio/opentimelineio/algorithms/timeline_algo.py @@ -11,12 +11,22 @@ def timeline_trimmed_to_range(in_timeline, trim_range): - """Returns a new timeline that is a copy of the in_timeline, but with items + """ + Returns a new timeline that is a copy of the in_timeline, but with items outside the trim_range removed and items on the ends trimmed to the - trim_range. Note that the timeline is never expanded, only shortened. + trim_range. + + .. note:: the timeline is never expanded, only shortened. + Please note that you could do nearly the same thing non-destructively by - just setting the Track's source_range but sometimes you want to really cut - away the stuff outside and that's what this function is meant for.""" + just setting the :py:class:`.Track`\'s source_range but sometimes you want to + really cut away the stuff outside and that's what this function is meant for. + + :param Timeline in_timeline: Timeline to trim + :param TimeRange trim_range: + :returnd: New trimmed timeline + :rtype: Timeline + """ new_timeline = copy.deepcopy(in_timeline) for track_num, child_track in enumerate(in_timeline.tracks): diff --git a/src/py-opentimelineio/opentimelineio/algorithms/track_algo.py b/src/py-opentimelineio/opentimelineio/algorithms/track_algo.py index 0644bc37d4..ae61b250f0 100644 --- a/src/py-opentimelineio/opentimelineio/algorithms/track_algo.py +++ b/src/py-opentimelineio/opentimelineio/algorithms/track_algo.py @@ -13,12 +13,22 @@ def track_trimmed_to_range(in_track, trim_range): - """Returns a new track that is a copy of the in_track, but with items + """ + Returns a new track that is a copy of the in_track, but with items outside the trim_range removed and items on the ends trimmed to the - trim_range. Note that the track is never expanded, only shortened. + trim_range. + + .. note:: The track is never expanded, only shortened. + Please note that you could do nearly the same thing non-destructively by - just setting the Track's source_range but sometimes you want to really cut - away the stuff outside and that's what this function is meant for.""" + just setting the :py:class:`.Track`\'s source_range but sometimes you want + to really cut away the stuff outside and that's what this function is meant for. + + :param Track in_track: Track to trim + :param TimeRange trim_range: + :returns: New trimmed track + :rtype: Track + """ new_track = copy.deepcopy(in_track) track_map = new_track.range_of_all_children() @@ -71,15 +81,22 @@ def track_with_expanded_transitions(in_track): """Expands transitions such that neighboring clips are trimmed into regions of overlap. - For example, if your track is: + For example, if your track is:: + Clip1, T, Clip2 - will return: + will return:: + Clip1', (Clip1_t, T, Clip2_t), Clip2' - Where Clip1' is the part of Clip1 not in the transition, Clip1_t is the - part inside the transition and so on. Please note that the items used in - a transition are encapsulated in `tuple`s + Where ``Clip1'`` is the part of ``Clip1`` not in the transition, ``Clip1_t`` is the + part inside the transition and so on. + + .. note:: The items used in a transition are encapsulated in tuples. + + :param Track in_track: Track to expand + :returns: Track + :rtype: list[Track] """ result_track = [] diff --git a/src/py-opentimelineio/opentimelineio/console/autogen_plugin_documentation.py b/src/py-opentimelineio/opentimelineio/console/autogen_plugin_documentation.py index 27a13e91c1..093e733a2e 100644 --- a/src/py-opentimelineio/opentimelineio/console/autogen_plugin_documentation.py +++ b/src/py-opentimelineio/opentimelineio/console/autogen_plugin_documentation.py @@ -72,11 +72,9 @@ Adapter plugins convert to and from OpenTimelineIO. - Adapters documentation page for more -information +[Adapters documentation page for more information](./adapters). -Tutorial on how to write an -adapter. +[Tutorial on how to write an adapter](write-an-adapter). {adapters} @@ -85,8 +83,7 @@ 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 +[Tutorial on how to write a Media Linker](write-a-media-linker). {media_linkers} @@ -94,8 +91,7 @@ SchemaDef plugins define new external schema. - Tutorial on how to write a -schemadef +[Tutorial on how to write a schemadef](write-a-schemadef). {schemadefs} @@ -103,8 +99,7 @@ HookScripts are extra plugins that run on _hooks_. -Tutorial on how to write a -hookscript. +[Tutorial on how to write a hookscript](write-a-hookscript). {hook_scripts} diff --git a/src/py-opentimelineio/opentimelineio/core/__init__.py b/src/py-opentimelineio/opentimelineio/core/__init__.py index 01509ac639..4aa455bbaf 100644 --- a/src/py-opentimelineio/opentimelineio/core/__init__.py +++ b/src/py-opentimelineio/opentimelineio/core/__init__.py @@ -41,6 +41,31 @@ item, ) +__all__ = [ + 'Composable', + 'Composition', + 'Item', + 'MediaReference', + 'SerializableObject', + 'SerializableObjectWithMetadata', + 'Track', + 'deserialize_json_from_file', + 'deserialize_json_from_string', + 'flatten_stack', + 'install_external_keepalive_monitor', + 'instance_from_schema', + 'register_serializable_object_type', + 'register_upgrade_function', + 'set_type_record', + 'add_method', + 'upgrade_function_for', + 'serializable_field', + 'deprecated_field', + 'serialize_json_to_string', + 'serialize_json_to_file', + 'register_type' +] + def serialize_json_to_string(root, indent=4): return _serialize_json_to_string(_value_to_any(root), indent) @@ -70,15 +95,19 @@ def __init__(self, *args, **kwargs): def upgrade_function_for(cls, version_to_upgrade_to): - """Decorator for identifying schema class upgrade functions. + """ + Decorator for identifying schema class upgrade functions. + + Example: + + .. code-block:: python - Example - >>> @upgrade_function_for(MyClass, 5) - ... def upgrade_to_version_five(data): - ... pass + @upgrade_function_for(MyClass, 5) + def upgrade_to_version_five(data): + pass - This will get called to upgrade a schema of MyClass to version 5. My class - must be a class deriving from otio.core.SerializableObject. + This will get called to upgrade a schema of MyClass to version 5. MyClass + must be a class deriving from :class:`~SerializableObject`. The upgrade function should take a single argument - the dictionary to upgrade, and return a dictionary with the fields upgraded. @@ -86,6 +115,9 @@ def upgrade_function_for(cls, version_to_upgrade_to): Remember that you don't need to provide an upgrade function for upgrades that add or remove fields, only for schema versions that change the field names. + + :param type cls: class to upgrade + :param int version_to_upgrade_to: the version to upgrade to """ def decorator_func(func): @@ -103,15 +135,13 @@ def wrapped_update(data): def serializable_field(name, required_type=None, doc=None): - """Create a serializable_field for child classes of SerializableObject. - + """ Convienence function for adding attributes to child classes of - SerializableObject in such a way that they will be serialized/deserialized + :class:`~SerializableObject` in such a way that they will be serialized/deserialized automatically. Use it like this: - .. highlight:: python .. code-block:: python @core.register_type @@ -120,7 +150,6 @@ class Foo(SerializableObject): This would indicate that class "foo" has a serializable field "bar". So: - .. highlight:: python .. code-block:: python f = foo() @@ -135,6 +164,13 @@ class Foo(SerializableObject): Additionally, the "doc" field will become the documentation for the property. + + :param str name: name of the field to add + :param type required_type: type required for the field + :param str doc: field documentation + + :return: property object + :rtype: :py:class:`property` """ def getter(self): @@ -158,7 +194,7 @@ def setter(self, val): def deprecated_field(): - """ For marking attributes on a SerializableObject deprecated. """ + """For marking attributes on a SerializableObject deprecated.""" def getter(self): raise DeprecationWarning diff --git a/src/py-opentimelineio/opentimelineio/core/_core_utils.py b/src/py-opentimelineio/opentimelineio/core/_core_utils.py index 8f80dbdad9..0ea6b07b35 100644 --- a/src/py-opentimelineio/opentimelineio/core/_core_utils.py +++ b/src/py-opentimelineio/opentimelineio/core/_core_utils.py @@ -231,6 +231,12 @@ def __deepcopy__(self, memo): and name not in klass.__abstractmethods__ ): setattr(mapClass, name, _im_func(func)) + if name.startswith('__') or name.endswith('__') or sys.version_info[0] < 3: # noqa + continue + + # Hide the method frm Sphinx doc. + # See https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists # noqa + getattr(mapClass, name).__doc__ += '\n\n:meta private:' mapClass.setdefault = setdefault mapClass.pop = pop @@ -376,6 +382,12 @@ def insert(self, index, item): and name not in klass.__abstractmethods__ ): setattr(sequenceClass, name, _im_func(func)) + if name.startswith('__') or name.endswith('__') or sys.version_info[0] < 3: # noqa + continue + + # Hide the method frm Sphinx doc. + # See https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists # noqa + getattr(sequenceClass, name).__doc__ += '\n\n:meta private:' if not issubclass(sequenceClass, SerializableObject): def __copy__(self): diff --git a/src/py-opentimelineio/opentimelineio/core/composition.py b/src/py-opentimelineio/opentimelineio/core/composition.py index 8cfb625417..87e3ddaf1f 100644 --- a/src/py-opentimelineio/opentimelineio/core/composition.py +++ b/src/py-opentimelineio/opentimelineio/core/composition.py @@ -42,19 +42,19 @@ def each_child( descended_from_type=_otio.Composable, shallow_search=False, ): - """ Generator that returns each child contained in the composition in + """ + Generator that returns each child contained in the composition in the order in which it is found. - Note that this function is now deprecated, please consider using - children_if() instead. + .. deprecated:: 0.14.0 + Use :meth:`children_if` instead. - Arguments: - search_range: if specified, only children whose range overlaps with - the search range will be yielded. - descended_from_type: if specified, only children who are a - descendent of the descended_from_type will be yielded. - shallow_search: if True, will only search children of self, not - and not recurse into children of children. + :param TimeRange search_range: if specified, only children whose range overlaps with + the search range will be yielded. + :param type descended_from_type: if specified, only children who are a descendent + of the descended_from_type will be yielded. + :param bool shallow_search: if True, will only search children of self, not + and not recurse into children of children. """ for child in self.children_if(descended_from_type, search_range, shallow_search): yield child diff --git a/src/py-opentimelineio/opentimelineio/exceptions.py b/src/py-opentimelineio/opentimelineio/exceptions.py index 35646b49b8..32cb25cf59 100644 --- a/src/py-opentimelineio/opentimelineio/exceptions.py +++ b/src/py-opentimelineio/opentimelineio/exceptions.py @@ -9,6 +9,25 @@ CannotComputeAvailableRangeError ) +__all__ = [ + 'OTIOError', + 'NotAChildError', + 'CannotComputeAvailableRangeError', + 'UnsupportedSchemaError', + 'CouldNotReadFileError', + 'NoKnownAdapterForExtensionError', + 'ReadingNotSupportedError', + 'WritingNotSupportedError', + 'NotSupportedError', + 'InvalidSerializableLabelError', + 'AdapterDoesntSupportFunctionError', + 'InstancingNotAllowedError', + 'TransitionFollowingATransitionError', + 'MisconfiguredPluginError', + 'CannotTrimTransitionsError', + 'NoDefaultMediaLinkerError' +] + class CouldNotReadFileError(OTIOError): pass diff --git a/src/py-opentimelineio/opentimelineio/hooks.py b/src/py-opentimelineio/opentimelineio/hooks.py index 1bb62625b7..eef65d65d5 100644 --- a/src/py-opentimelineio/opentimelineio/hooks.py +++ b/src/py-opentimelineio/opentimelineio/hooks.py @@ -9,58 +9,71 @@ __doc__ = """ HookScripts are plugins that run at defined points ("Hooks"). -They expose a hook_function with signature: -hook_function :: otio.schema.Timeline, Dict -> otio.schema.Timeline +They expose a ``hook_function`` with signature: + +.. py:function:: hook_function(timeline: opentimelineio.schema.Timeline, optional_argument_dict: dict[str, Any]) -> opentimelineio.schema.Timeline # noqa + :noindex: + + Hook function signature Both hook scripts and the hooks they attach to are defined in the plugin manifest. -You can attach multiple hook scripts to a hook. They will be executed in list +Multiple scripts can be attached to a hook. They will be executed in list order, first to last. -They are defined by the manifests HookScripts and hooks areas. - ->>> -{ - "OTIO_SCHEMA" : "PluginManifest.1", - "hook_scripts" : [ - { - "OTIO_SCHEMA" : "HookScript.1", - "name" : "example hook", - "execution_scope" : "in process", - "filepath" : "example.py" - } - ], - "hooks" : { - "pre_adapter_write" : ["example hook"], - "post_adapter_read" : [] - } -} - -The 'hook_scripts' area loads the python modules with the 'hook_function's to -call in them. The 'hooks' area defines the hooks (and any associated -scripts). You can further query and modify these from python. - ->>> import opentimelineio as otio -... hook_list = otio.hooks.scripts_attached_to("some_hook") # -> ['a','b','c'] -... -... # to run the hook scripts: -... otio.hooks.run("some_hook", some_timeline, optional_argument_dict) - -This will pass (some_timeline, optional_argument_dict) to 'a', which will -a new timeline that will get passed into 'b' with optional_argument_dict, +They are defined by the manifests :class:`HookScript`\\s and hooks areas. + +.. code-block:: json + + { + "OTIO_SCHEMA" : "PluginManifest.1", + "hook_scripts" : [ + { + "OTIO_SCHEMA" : "HookScript.1", + "name" : "example hook", + "execution_scope" : "in process", + "filepath" : "example.py" + } + ], + "hooks" : { + "pre_adapter_write" : ["example hook"], + "post_adapter_read" : [] + } + } + +The ``hook_scripts`` area loads the python modules with the ``hook_function``\\s to +call in them. The ``hooks`` area defines the hooks (and any associated +scripts). You can further query and modify these from python. + +.. code-block:: python + + import opentimelineio as otio + hook_list = otio.hooks.scripts_attached_to("some_hook") # -> ['a','b','c'] + + # to run the hook scripts: + otio.hooks.run("some_hook", some_timeline, optional_argument_dict) + +This will pass (some_timeline, optional_argument_dict) to ``a``, which will +a new timeline that will get passed into ``b`` with ``optional_argument_dict``, etc. -To Edit the order, change the order in the list: +To edit the order, change the order in the list: ->>> hook_list[0], hook_list[2] = hook_list[2], hook_list[0] -... print hook_list # ['c','b','a'] +.. code-block:: python -Now c will run, then b, then a. + 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 the list: ->>> del hook_list[1] +.. code-block:: python + + del hook_list[1] + +---- """ diff --git a/src/py-opentimelineio/opentimelineio/media_linker.py b/src/py-opentimelineio/opentimelineio/media_linker.py index b475332add..57eb709b03 100644 --- a/src/py-opentimelineio/opentimelineio/media_linker.py +++ b/src/py-opentimelineio/opentimelineio/media_linker.py @@ -1,33 +1,20 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project -""" MediaLinker plugins fire after an adapter has read a file in order to -produce MediaReferences that point at valid, site specific media. +""" +MediaLinker plugins fire after an adapter has read a file in order to +produce :class:`.MediaReference`\s that point at valid, site specific media. -They expose a "link_media_reference" function with the signature: -link_media_reference :: otio.schema.Clip -> otio.core.MediaReference +They expose a ``link_media_reference`` function with the signature: -or: - def linked_media_reference(from_clip): - result = otio.core.MediaReference() # whichever subclass - # do stuff - return result +.. py:function:: link_media_reference(in_clip: opentimelineio.schema.Clip) -> opentimelineio.core.MediaReference # noqa + :noindex: + + Example link_media_reference function. To get context information, they can inspect the metadata on the clip and on -the media reference. The .parent() method can be used to find the containing +the media reference. The :meth:`.Composable.parent` method can be used to find the containing track if metadata is stored there. - -Please raise an instance (or child instance) of -otio.exceptions.CannotLinkMediaError() if there is a problem linking the media. - -For example: - for clip in timeline.each_clip(): - try: - new_mr = otio.media_linker.linked_media_reference(clip) - clip.media_reference = new_mr - except otio.exceptions.CannotLinkMediaError: - # or report the error - pass """ import os @@ -40,8 +27,8 @@ def linked_media_reference(from_clip): ) -# Enum describing different media linker policies class MediaLinkingPolicy: + """Enum describing different media linker policies""" DoNotLinkMedia = "__do_not_link_media" ForceDefaultLinker = "__default" diff --git a/src/py-opentimelineio/opentimelineio/opentime.py b/src/py-opentimelineio/opentimelineio/opentime.py index 2f8f9ed0cc..f164935b79 100644 --- a/src/py-opentimelineio/opentimelineio/opentime.py +++ b/src/py-opentimelineio/opentimelineio/opentime.py @@ -7,6 +7,24 @@ TimeTransform, ) +__all__ = [ + 'RationalTime', + 'TimeRange', + 'TimeTransform', + 'from_frames', + 'from_timecode', + 'from_time_string', + 'from_seconds', + 'to_timecode', + 'to_frames', + 'to_seconds', + 'to_time_string', + 'range_from_start_end_time', + 'range_from_start_end_time_inclusive', + 'duration_from_start_end_time', + 'duration_from_start_end_time_inclusive', +] + from_frames = RationalTime.from_frames from_timecode = RationalTime.from_timecode from_time_string = RationalTime.from_time_string @@ -21,6 +39,7 @@ def to_timecode(rt, rate=None, drop_frame=None): + """Convert a :class:`~RationalTime` into a timecode string.""" return ( rt.to_timecode() if rate is None and drop_frame is None @@ -29,12 +48,18 @@ def to_timecode(rt, rate=None, drop_frame=None): def to_frames(rt, rate=None): + """Turn a :class:`~RationalTime` into a frame number.""" return rt.to_frames() if rate is None else rt.to_frames(rate) def to_seconds(rt): + """Convert a :class:`~RationalTime` into float seconds""" return rt.to_seconds() def to_time_string(rt): + """ + Convert this timecode to time as used by ffmpeg, formatted as + ``hh:mm:ss`` where ss is an integer or decimal number. + """ return rt.to_time_string() diff --git a/src/py-opentimelineio/opentimelineio/plugins/manifest.py b/src/py-opentimelineio/opentimelineio/plugins/manifest.py index b060fd630b..6ef6e9bd1b 100644 --- a/src/py-opentimelineio/opentimelineio/plugins/manifest.py +++ b/src/py-opentimelineio/opentimelineio/plugins/manifest.py @@ -36,7 +36,7 @@ def manifest_from_file(filepath): - """Read the .json file at filepath into a Manifest object.""" + """Read the .json file at filepath into a :py:class:`Manifest` object.""" result = core.deserialize_json_from_file(filepath) absfilepath = os.path.abspath(filepath) @@ -201,10 +201,11 @@ def load_manifest(): """ Walk the plugin manifest discovery systems and accumulate manifests. The order of loading (and precedence) is: - 1. manifests specfied via the OTIO_PLUGIN_MANIFEST_PATH variable - 2. builtin plugin manifest - 3. contrib plugin manifest - 4. setuptools.pkg_resources based plugin manifests + + 1. manifests specfied via the ``OTIO_PLUGIN_MANIFEST_PATH`` variable + 2. builtin plugin manifest + 3. contrib plugin manifest + 4. ``setuptools.pkg_resources`` based plugin manifests """ result = Manifest() diff --git a/src/py-opentimelineio/opentimelineio/schema/__init__.py b/src/py-opentimelineio/opentimelineio/schema/__init__.py index b1242bd250..47a4f5b595 100644 --- a/src/py-opentimelineio/opentimelineio/schema/__init__.py +++ b/src/py-opentimelineio/opentimelineio/schema/__init__.py @@ -58,3 +58,25 @@ def timeline_from_clips(clips): trck = Track(children=clips) return Timeline(tracks=[trck]) + +__all__ = [ + 'Box2d', + 'Clip', + 'Effect', + 'TimeEffect', + 'LinearTimeWarp', + 'ExternalReference', + 'FreezeFrame', + 'Gap', + 'GeneratorReference', + 'ImageSequenceReference', + 'Marker', + 'MissingReference', + 'SerializableCollection', + 'Stack', + 'Timeline', + 'Transition', + 'SchemaDef', + 'timeline_from_clips', + 'V2d' +] diff --git a/src/py-opentimelineio/opentimelineio/schema/image_sequence_reference.py b/src/py-opentimelineio/opentimelineio/schema/image_sequence_reference.py index d1c01d0c6d..f9e14bd126 100644 --- a/src/py-opentimelineio/opentimelineio/schema/image_sequence_reference.py +++ b/src/py-opentimelineio/opentimelineio/schema/image_sequence_reference.py @@ -58,12 +58,11 @@ def __repr__(self): @add_method(_otio.ImageSequenceReference) def frame_range_for_time_range(self, time_range): - """ - Returns a :class:`tuple` containing the first and last frame numbers for + """Returns first and last frame numbers for the given time range in the reference. - Raises ValueError if the provided time range is outside the available - range. + :rtype: tuple[int] + :raises ValueError: if the provided time range is outside the available range. """ return ( self.frame_for_time(time_range.start_time), @@ -74,7 +73,7 @@ def frame_range_for_time_range(self, time_range): @add_method(_otio.ImageSequenceReference) def abstract_target_url(self, symbol): """ - Generates a target url for a frame where :param:``symbol`` is used in place + Generates a target url for a frame where ``symbol`` is used in place of the frame number. This is often used to generate wildcard target urls. """ if not self.target_url_base.endswith("/"): diff --git a/src/py-opentimelineio/opentimelineio/schema/schemadef.py b/src/py-opentimelineio/opentimelineio/schema/schemadef.py index 7481075e67..e39f5647b4 100644 --- a/src/py-opentimelineio/opentimelineio/schema/schemadef.py +++ b/src/py-opentimelineio/opentimelineio/schema/schemadef.py @@ -29,7 +29,8 @@ def module(self): Return the module object for this schemadef plugin. If the module hasn't already been imported, it is imported and injected into the otio.schemadefs namespace as a side-effect. - (redefines PythonPlugin.module()) + + Redefines :py:meth:`.PythonPlugin.module`. """ if not self._module: diff --git a/src/py-opentimelineio/opentimelineio/schema/serializable_collection.py b/src/py-opentimelineio/opentimelineio/schema/serializable_collection.py index c1c9f668aa..9029484dda 100644 --- a/src/py-opentimelineio/opentimelineio/schema/serializable_collection.py +++ b/src/py-opentimelineio/opentimelineio/schema/serializable_collection.py @@ -35,14 +35,13 @@ def each_child(self, search_range=None, descended_from_type=_otio.Composable): """ Generator that returns each child contained in the serializable collection in the order in which it is found. - Note that this function is now deprecated, please consider using - children_if() instead. + .. deprecated:: 0.14.0 + Use :meth:`children_if` instead. - Arguments: - search_range: if specified, only children whose range overlaps with - the search range will be yielded. - descended_from_type: if specified, only children who are a - descendent of the descended_from_type will be yielded. + :param TimeRange search_range: if specified, only children whose range overlaps + with the search range will be yielded. + :param type descended_from_type: if specified, only children who are a descendent + of the descended_from_type will be yielded. """ for child in self.children_if(descended_from_type, search_range): yield child @@ -53,12 +52,11 @@ def each_clip(self, search_range=None): """ Generator that returns each clip contained in the serializable collection in the order in which it is found. - Note that this function is now deprecated, please consider using - clip_if() instead. + .. deprecated:: 0.14.0 + Use :meth:`each_clip` instead. - Arguments: - search_range: if specified, only children whose range overlaps with - the search range will be yielded. + :param TimeRange search_range: if specified, only children whose range overlaps + with the search range will be yielded. """ for child in self.clip_if(search_range): yield child diff --git a/src/py-opentimelineio/opentimelineio/schema/stack.py b/src/py-opentimelineio/opentimelineio/schema/stack.py index 8b7f24cf9f..34f56c6e22 100644 --- a/src/py-opentimelineio/opentimelineio/schema/stack.py +++ b/src/py-opentimelineio/opentimelineio/schema/stack.py @@ -7,15 +7,14 @@ @add_method(_otio.Stack) def each_clip(self, search_range=None): - """ Generator that returns each clip contained in the stack + """Generator that returns each clip contained in the stack in the order in which it is found. - Note that this function is now deprecated, please consider using - clip_if() instead. + .. deprecated:: 0.14.0 + Use :meth:`clip_if` instead. - Arguments: - search_range: if specified, only children whose range overlaps with - the search range will be yielded. + :param TimeRange search_range: if specified, only children whose range overlaps + with the search range will be yielded. """ for child in self.clip_if(search_range): yield child diff --git a/src/py-opentimelineio/opentimelineio/schema/timeline.py b/src/py-opentimelineio/opentimelineio/schema/timeline.py index 85dcd07d3b..ea7682d9ea 100644 --- a/src/py-opentimelineio/opentimelineio/schema/timeline.py +++ b/src/py-opentimelineio/opentimelineio/schema/timeline.py @@ -22,17 +22,16 @@ def __repr__(self): @add_method(_otio.Timeline) def each_child(self, search_range=None, descended_from_type=_otio.Composable): - """ Generator that returns each child contained in the timeline + """Generator that returns each child contained in the timeline in the order in which it is found. - Note that this function is now deprecated, please consider using - children_if() instead. + .. deprecated:: 0.14.0 + Use :meth:`children_if` instead. - Arguments: - search_range: if specified, only children whose range overlaps with - the search range will be yielded. - descended_from_type: if specified, only children who are a - descendent of the descended_from_type will be yielded. + :param TimeRange search_range: if specified, only children whose range overlaps + with the search range will be yielded. + :param type descended_from_type: if specified, only children who are a descendent + of the descended_from_type will be yielded. """ for child in self.children_if(descended_from_type, search_range): yield child @@ -40,15 +39,14 @@ def each_child(self, search_range=None, descended_from_type=_otio.Composable): @add_method(_otio.Timeline) def each_clip(self, search_range=None): - """ Generator that returns each clip contained in the timeline + """Generator that returns each clip contained in the timeline in the order in which it is found. - Note that this function is now deprecated, please consider using - clip_if() instead. + .. deprecated:: 0.14.0 + Use :meth:`clip_if` instead. - Arguments: - search_range: if specified, only children whose range overlaps with - the search range will be yielded. + :param TimeRange search_range: if specified, only children whose range overlaps + with the search range will be yielded. """ for child in self.clip_if(search_range): yield child diff --git a/src/py-opentimelineio/opentimelineio/schema/track.py b/src/py-opentimelineio/opentimelineio/schema/track.py index 4bac8120f8..cd851e4950 100644 --- a/src/py-opentimelineio/opentimelineio/schema/track.py +++ b/src/py-opentimelineio/opentimelineio/schema/track.py @@ -10,13 +10,12 @@ def each_clip(self, search_range=None, shallow_search=False): """ Generator that returns each clip contained in the track in the order in which it is found. - Note that this function is now deprecated, please consider using - clip_if() instead. + .. deprecated:: 0.14.0 + Use :meth:`clip_if` instead. - Arguments: - search_range: if specified, only children whose range overlaps with + :param TimeRange search_range: if specified, only children whose range overlaps with the search range will be yielded. - shallow_search: if True, will only search children of self, not + :param bool shallow_search: if True, will only search children of self, not and not recurse into children of children. """ for child in self.clip_if(search_range):