Python

The libraries DJV is built from have Python bindings, and there is a version of the DJV application written in Python on top of them. This page describes what is there, how to run it, and how to modify it. It is not an API reference — the bindings follow the C++ headers closely, so the headers and the Python examples are the reference.

The modules

Four modules make up the stack:

The conventional imports:

import opentimelineio as otio
import ftkPy as ftk
import tlRenderPy as tl
import djvPy as djv

Building

One switch builds all of it. In etc/Config/local.cmake:

set(DJV_PYTHON ON CACHE BOOL "")

The feather-tk and tlRender switches follow it, the superbuild adds pybind11 and builds OpenTimelineIO's Python package, and the stack is built as shared libraries so that the modules share one copy of it. The bindings compile against the Python that CMake finds; the modules only import into that same Python, which is why the launcher below exists.

Running

The build writes a launcher that knows the interpreter and the module paths:

./build-Release/bin/djv-python render.mov

The install gets a copy at bin/djv-python that finds everything beside itself. The application takes an input file, and options in the C++ application's style — see -h for the list. The ones useful for scripting:

The application

The application lives in examples/python, one file per concern the way the C++ application is arranged: djv.py is the entry point, App.py creates the models and turns the current file into a player, MainWindow.py builds the window, and the rest are the actions, menus, tool bars, and tools.

The division of labor: the widgets are the C++ application's own, wrapped whole in djv.ui — one implementation, so the two applications cannot drift apart — and the Python code is the composition around them: the models, the actions and menus, and the tool frames. A tool like Color is a stack of bellows around wrapped widgets; a tool like Files builds its own rows from feather-tk widgets where the C++ application does the same.

The application keeps feature parity with the C++ application — playlists, dragging to reorder the file list, the tools — and the toolkit's widget events all pass through to Python: a widget written in Python can draw, take the keyboard and mouse, and be a drag and drop source or target.

The actions follow the C++ IActions pattern, in IActions.py: every operation is registered as a named command with the commands model, the menu actions run the commands, and the keyboard shortcuts come from the settings — so the shortcuts editor in the settings tool applies here, and -command can run anything a menu can.

Everything follows the same pattern: the DJV models hold the state, and the widgets observe it. A widget changes a model through its setters, and updates itself from an observer — never directly from its own callback, so that every other view of the same state stays in step:

selfWeak = weakref.ref(self)
self._volumeObserver = ftk.FloatObserver(
    audioModel.observeVolume,
    lambda value: selfWeak()._volumeUpdate(value))

Two things in that pattern matter:

Option values are copies. Reading a property, changing a field, and assigning it back is the way to change one:

options = filesModel.compareOptions
options.compare = tl.Compare.Wipe
filesModel.compareOptions = options

Modifying it

To add a tool, write a class in Tools.py deriving from IToolWidget and add it to the FACTORY dictionary; the tools model already lists the tool names, icons, and shortcuts, and the actions, menu, and tool bar offer whatever the factory implements. To add an action, follow any of the *Actions.py files: create an ftk.Action, add it to a menu in Menus.py or a tool bar in ToolBars.py. checkedCallback must be passed as a keyword argument — passed positionally it binds to the plain callback and fails when clicked.

Smaller examples are in feather-tk's and tlRender's own examples/python directories, and the Python test suites — tests/CorePyTest and tests/UIPyTest in feather-tk, tests/tlRenderPyTest in tlRender, and tests/djvPyTest here — double as working usage of most of the bound API. They run with python -m unittest discover tests, or through CTest as ftkPy-test, tlRenderPy-test, and djvPy-test.