Davinci Resolve - intro to python

An old scripting guide can be found here

Python & Lua

Historically FusionScript was Lua only, some methods that return multiple statements have a special Table() suffix variant to return the proper table for use in Python. As the Lua collection is a tuple, you will need to pass a dictionary to the API in many cases, even when it seems to be treated like a list. So each Value needs to have a key in the order of the entry.

# For example a list like:
l = ['a', 'b', 'c']

# needs to map to a dictionary
d = {1: 'a', 2: 'b', 3:'c' }

Please note that Lua uses 1 as the first index key of its tuples, not 0. Python dictionaries do not have a particular order. Only the key indicates their order in this case. Similarly, all Lua tuples result in dictionaries in Python that need to be parsed into Lists. If order does not matter, it can be simply done by:

l = d.values()

If order is important their values need to be sorted by their keys before conversion to a list. This can be achieved with a list comprehension:

l = [item[1] for item in sorted(d.items())]

Fusion Instance

The starting point for all access is a Fusion object. A Fusion object represents a running Fusion instance. It can create, open, and close compositions, stores application wide settings and preferences or persistent metadata. Fusion is able to open and manage multiple compositions from one Fusion instance. The graphical user interface represents these with a Tab-Layout.

In scripting all currently loaded compositions are accessible with fu.GetCompList()

comp_list = fu.GetCompList()
#{1.0: <PyRemoteObject object at 0x230367798>, 2.0: <PyRemoteObject object at 0x2303677b0>,... }

The currently active Composition can be accessed via fu.CurrentComp. To load a composition use fu.LoadComp(path, locked) or create an empty composition using fu.NewComp(locked, auto-close, hidden). You can also quit the Fusion instance by using fu.Quit(). If you are running the script from within Fusion it still will be executed. In reality the script is not bound to the Fusion instance. Instead a FuScript application is spawned that evaluates the scripts and communicates to the running Fusion instance. If your script exits, eventually the FuScript instance will also be stopped. This obviously also applies if running scripts from an external scripting environment as explained in the earlier chapter.

Composition Instance

A Composition may also store settings, attributes, and persistent metadata. While the Fusion instance holds Global Settings, each composition may have an individual set of settings. This behaviour is mimicked in the preferences dialog, where either global settings for each new composition, or individual settings of currently opened compositions can be changed. Most of the time the composition settings should be accessed to include the overrides for the current composition. This includes the PathMapping, which is used to identify paths from Fusion’s relative path system. The composition can be Saved and Closed, create Undos, Undo actions, and Redo them and Clear Undos altogether. Also, playback and rendering can be invoked from a composition.

Tools on the composition can be queried. A composition can get and set the currently active tool via comp.ActiveTool and comp.SetActiveTool(tool). All tools within the composition are queried with comp.GetToolList() while only the selected tools a queried with comp.GetToolList(true).

  • Composition

    • Represents an composition.
      The Composition object’s methods and members are directly available in the console and in comp scripts written in Lua. This means that you can simply type ==CurrentTime or call AddTool(“Blur”) without the need to prefix the command with comp. Python scripts have to use the full name

      print(comp.CurrentTime)
      # 42.0
    • There might be an more straightforward way of getting the wifth and height of a comp but using the comp.GetPrefs() method I can get a dict where I can find various useful info.

      frame_format = comp.GetPrefs().get('Comp').get('FrameFormat')
      height = frame_format.get('Height')
      width = frame_format.get('Width')

Flow view

  • FlowView

    • The FlowView represents the flow with all the tools.
      Positions of tools, their selection state and the views zoom level are controlled with this object.

      flow = comp.CurrentFrame.FlowView
      # get the current FlowView
      text = comp.FindTool('Text1')
      # get the tool objetc
      flow.Select(text, True)
      # Adds blur1 to the selection
      flow.Select(text, False)
      # Removes blur2 from the selection
      flow:Select()
      # Deselects all

Tools (nodes)

Tools are uniquely named operators of a particular type. Internally a tool is a subset of an Operator that is visible on the flow. It can be a Creator or Filter, 3D Tool, etc.

Add tool

  • Composition.AddTool(id[, defsettings][, xpos][, ypos])

    • Adds a tool type at a specified position.

      # add background node at x 0, y 0 
      bg = comp.AddTool('Background', 0, 0)

Add Tool Action

  • Composition.AddToolAction(id[, xpos][, ypos])

    • Adds a tool to the comp. Will be placed at the position of the last mouse click.

      bg = comp.AddToolAction('Background')

Read access to the name and its type is given with tool.Name and tool.ID

  • TOOLS_Name
    • For read and write access of the name
  • TOOLB_NameSet
    • indicates if the name was manually changed. If not, some tools will show additional information on the tile next to its name. For example, the loader will show the clip’s filename.
  • TOOLB_PassThrough
    • PassThrough-State
  • TOOLB_Locked
    • Lock-State

Main Inputs & Main Outputs

In general, tools have Inputs and Outputs. Property Inputs being represented by controls in the properties view (e.g. the Gain slider in a ColorCorrector) or the Inputs on the flow view that connect one tool to the other, so called Main Inputs. Outputs are very similar although most of the time tools only have one MainOutput on the FlowView. An exception being the Stereo Splitter (Fusion Studio) as shown in the figure.

Inputs & Outputs

Next to the MainInput and MainOutputs there are other Inputs and Outputs. If Inputs are not hidden they can be represented as an Input control in the properties view. Still the underlying DataType might be the same. For example a Number DataType might be accessible through a slider control, a Checkbox, a DropdownList, a Multibutton etc.

Querying Inputs

Connections

Animation

To animate an Input via script, the first step is to add a BezierSpline. A Bezier Spline is an animation curve that can be viewed in the spline editor. It is a storehouse for the information contained in the animated properties of a tool. To do this for a Merge’s blend property, the following code could be employed:

comp.Merge1.Blend = comp.BezierSpline()

By setting the input’s value at a specific time, keyframes will be created.

If the property is a Point DataType, use the Path function instead to add a bezier-based path. If the desire was to then animate the blend from 1 to 0 over the period of 100 frames, one could use the following code:

comp.Merge1.Blend[1] = 1
comp.Merge1.Blend[100] = 0
  • BezierSpline

    • Modifier that represents animation on a number value input. Keyframes are interpolated with a bezier spline. To animate Points use a Path instead.

      comp.Transform1.Size = comp.BezierSpline()
      comp.Transform1.Size[5] = 1
      comp.Transform1.Size[10] = 2
      comp.Transform1.Size[15] = 3
  • PolyPath

    • The PolyPath class is not documented in the (quite old) fusion scripting docs I found online. Will add info when/if I find it.

      comp.Transform1.Center = comp.PolyPath()
      tool.AddModifier('Center', 'PolyPath')
      comp.Transform1.Center[0] = {1.0: 0, 2.0: 0}
      comp.Transform1.Center[50] = {1.0: 1, 2.0: 1}
      tool = comp.ActiveTool
      tool.Center = comp.PolyPath()
      tool.Center[0] = {1.0: 0, 2.0: 0}
      tool.Center[0] = {1.0: 0, 2.0: 0}
      tool.Center[0] = {1.0: 1, 2.0: 1}

Modifier

  • AddModifier(input, modifier)

    • Creates a modifier and connects it to an input. This provides an easy way to animate the controls of a tool. input ID of the tool’s Input to be connected to. modifier ID of the modifier to be created. Returns a boolean value indicating success.

      tool.AddModifier('Size', 'BezierSpline')
      tool['Size'][1] = 1
      tool['Size'][2] = 2
      
      
      for i in range(10):
      	tool['Size'][i] = i

Attributes

Attributes store information about the capabilities of a certain type, as well as some common flags that contribute to the object’s state. For example, in the case of a Tool the attributes may include the typename of the object, its name in the composition, its abbreviation shown in the Toolbar, its PassThrough and selection state, etc.

Data type

The type character stands for:

key Type
S String
B Boolean
N Number (float)
I Integer
H Handle
NT Number Table
IT Integer Table
ST String Table
BT Boolean Table

Get attrs

In order to access the Attributes, the GetAttrs() method can be used. As it is provided by the Object superclass, pretty much all objects can have Attributes. So GetAttrs() is a good place to look for functionality or data within an object. If no argument is given, all Attributes are returned. It is also possible to supply a single tag string to narrow down the search.

tool.GetAttrs()
# {'TOOLB_CacheToDisk': False, 'TOOLB_HoldOutput': False, ...
tool.GetAttrs('TOOLB_Locked')
# False 

Set attrs

In our example, TOOLB_Locked stands for a Tool Attribute of type boolean with the name “Locked.” Attributes can be changed by using SetAttrs({}). The supplied table is required to have the Tag as key and the new value as value. Multiple attributes can be changed at a time, however not all Attributes can be changed at all.

tool.SetAttrs({'TOOLS_Name':'my_text', 'TOOLB_Locked':True})

In scripting the value on an Input can be changed directly with an assignment, by using an index that represents a specific time or by using tool:SetInput(“InputName”, value, [time]).

Merge1.Angle = 10
#Sets Angle to 10

Merge1.Angle[5] = 20
#Sets Angle to 20 on frame 5

Merge1.SetInput('Angle', 20, 5)
# Same as above

# get the selected node, note the it is not a method
tool = comp.ActiveTool
# Transform (0x0x21ec42810) [App: 'Fusion' on 127.0.0.1, UUID: 142a43b3-c7b5-46d3-8ce2-26b823c9abed]

# print the size. We need to pass the frame we wish to query
# this is done with a square bracket
print(tool.Size[0])
# 1.0

# to set a 1D attr
tool.Size = 2.0

# if we query a 2d attr like center, we get back an dict
print(tool.Center[0])
# {1.0: 0.5, 2.0: 0.5, 3.0: 0.0}
# this represent x, y & frame number

# To set a 2D attr:
tool.Center = {1.0: 0.25, 2.0: 0.25, 3.0: 0}
# if the attr has keyframes you can not set an attr like this

# get the input list, this is all the ui params and the input to the node
input_list = tool.GetInputList()
# {1.0: <PyRemoteObject object at 0x230367168>, 2.0: <PyRemoteObject object at 0x230367150>,...

print(input_list.get(1.0))
#Input (0x0x26e747210) [App: 'Fusion' on 127.0.0.1, UUID: cf0a5593-9ce9-43c4-a17e-56afe7b11b44]

print(input_list.get(1.0).Name)
# Settings

# print the index and name of the inputs
print('\n'.join(['{} - {}'.format(k, v.Name) for k, v in input_list.iteritems()]))
'''
1.0 - Settings
2.0 - Blend
...
34.0 - Size
'''

# size is index 34
size_param = input_list[34]

size_attrs = size_param.GetAttrs()
pprint.pprint(size_attrs)
# will set the size to 2

'''
{'INPB_Active': False,
 'INPB_Connected': False,
 'INPB_Disabled': False,
 ...
}
'''

Dev

Here is a quick way of running an external script from the console. Can be useful if you just want to try out someting quick. Since the module is executed within the fusion environment it has access to comp etc.

execfile(path/to/file.py)

import math
#import pprint

def get_screen_orbit_pos(num, radius, width, height):

	inc = math.pi*2/(num)

	ret_list = []
	for i in range(num+1):
		x = math.cos(inc*i)*radius*.5+width*.5
		y = math.sin(inc*i)*radius*.5+height*.5
		# print(x, y)
		ret_list.append((x, y))

	return ret_list


def convert_pos(pos_list, width, height):

	ret_list = []
	for i, pos in enumerate(pos_list):
		x = pos[0]/float(width)
		y = pos[1]/float(height)
		# print(x, y)
		ret_list.append((x, y))

	return ret_list


def animate(pos_list):

	tool = comp.ActiveTool()
	tool.AddModifier('Center', 'PolyPath')

	for i, pos in enumerate(pos_list):
		# print(pos)
		tool['Center'][i] = {1.0: pos[0], 2.0: pos[1]}


pos_list = get_screen_orbit_pos(num=36, radius=1080, width=1920, height=1080)
pos_list = convert_pos(pos_list=pos_list, width=1920, height=1080)

try:
	animate(pos_list)
except:
	pass

UI

Ask User

A simple way to build and evaluate a dialog is called: comp:AskUser(name, {table of inputs}). Each input is a table structured as follows : {Input Name, Input Type, Options …}

In Python, make sure to create a dictionary with proper indices starting with 1 as explained in the Chapter about Python. For Example:

dialog = {
	1: {1:'path', 'Name':'Select a Directory', 2:'PathBrowse'},
	2: {1:'file', 'Name':'Select A Source File', 2:'FileBrowse'},
	3: {1:'copies', 'Name':'Number of Copies', 2:'Slider', 'Default':1.0, 'Integer':True, 'Min':1, 'Max':5},
	4: {1:'cb', 'Name':'A Check Box', 2:'Checkbox', 'Default':1}
}
ret = composition.AskUser('A simple dialog', dialog)
if ret.get('cb'):
	# print '{} - {}'.format(ret.get('path'), ret.get('file'))
	print('\n'.join(['{}:{}'.format(k, v) for k, v in ret.iteritems()]))
else:
	print('No diggity')