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,
 ...
}
'''

Macro

To create a macro node using python:

comp.DoAction('AddSetting', {'filename':'Macros:/My Macro.setting'})

You can then set keyframes (using python) on the parmeters as you would on a “standard” tool.

Dev

Run externally

To run a python script externally that interacts with Resolve here are some notes from the README found in the “Scripting folder” which is located at: C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Developer\Scripting

  • Using a script
    • DaVinci Resolve needs to be running for a script to be invoked.
    • For a Resolve script to be executed from an external folder, the script needs to know of the API location.
    • You may need to set the these environment variables to allow for your Python installation to pick up the appropriate dependencies as shown below:
    • Windows:
      • RESOLVE_SCRIPT_API=”%PROGRAMDATA%\Blackmagic Design\DaVinci Resolve\Support\Developer\Scripting”
      • RESOLVE_SCRIPT_LIB="C:\Program Files\Blackmagic Design\DaVinci Resolve\fusionscript.dll”
      • PYTHONPATH=”%PYTHONPATH%;%RESOLVE_SCRIPT_API%\Modules"
  • Run from Houdini
    • I wanted to run a scipt that interacts with Resolve from within houdini. The Environ variables were set but I needed to add the modules directory to the path like so:
    • With that done i could import the DaVinciResolveScript module
import sys
sys.path.append('C:/ProgramData/Blackmagic Design/DaVinci Resolve/Support/Developer/Scripting/Modules')

import DaVinciResolveScript as dvr_script

Run external script (from Resolve)

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

Workflow Integration

DaVinci Resolve Studio now supports Workflow Integration Plugins to be loaded and communicate with Resolve. Resolve can run one or more Workflow Integration Plugins at the same time. Users can write their own Workflow Integration Plugin (an Electron app) which could be loaded into DaVinci Resolve Studio. To interact with Resolve, Resolve’s JavaScript APIs can be used from the plugin.

Alternatively, a Python or Lua script can be invoked, with the option of a user interface built with Resolve’s built-in Qt-based UIManager, or with an external GUI manager. See the “Sample Workflow Integration Script” section below for details.

You can find information on workflow integration here:
C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Developer\Workflow Integrations

I prefer to write my scripts with Python and Qt (Pyside2 for the moment). I have not had time to try out Qt with Resolve yet but it seems like you can do it with workflow integrations.

To quickly try out the python example do this:

  • Copy the script “Sample Script Plugin.py” found in:
    • C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Developer\Workflow Integrations\Examples
  • Paste the script here:
    • C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Workflow Integration Plugins
      If the folder does not exist, create it
  • Restart Resolve
  • The script can be found in the menu: Workspace > Workflow Integrations > …

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')

Examples

Animation

Setting 1D and 2D keyframes

import pprint
import DaVinciResolveScript as dvr_script


def get_parm_dict(active_tool):
	
	if active_tool:
		return { v.Name:v for k,v in active_tool.GetInputList().items()}
	else:
		return {}

def set_keyframes(parm, data_list, current_comp):

	keyframes = parm.GetKeyFrames() 

	if keyframes:
		old_path = parm.GetConnectedOutput().GetTool()
		old_path.Delete()

	is_1D = False if len(data_list[0][1]) > 1 else True

	current_time = current_comp.CurrentTime 
	current_comp._SetCurrentTime(data_list[0][0])

	if is_1D:
		path = current_comp.BezierSpline()
	else:
		path = current_comp.PolyPath()
		# path = current_comp.Path() # The same?


	parm.ConnectTo(path)

	for i, data in enumerate(data_list):
		value = data[1][0] if is_1D else data[1]
		parm[data[0]] = value

	current_comp._SetCurrentTime(current_time)


if __name__ == '__main__':

	data_list_1D = [
		[10,[0.05]],
		[20,[0.2]],
		[30,[0.5]],
		[40,[0.2]],
		[50,[0.1]]
	]

	data_list_2D = [
		[10,[0.0, 0.2]],
		[20,[0.5, 0.5]],
		[30,[1.0, 0.8]],
		[40,[0.4, 0.0]],
		[50,[0.2, 1]]
	]

	data_list_1D_scaled = [[d[0], [d[1][0]*.1]] for d in data_list_1D]

	resolve = dvr_script.scriptapp("Resolve")
	fusion = resolve.Fusion()
	current_comp = fusion.GetCurrentComp()
	active_tool = current_comp.ActiveTool

	parm_dict = get_parm_dict(active_tool)

	if parm_dict:
		
		parm_name = 'Width'		
		parm = parm_dict[parm_name] if parm_name in parm_dict else None
		if parm:
			set_keyframes(parm, data_list_1D, current_comp)

		parm_name = 'Soft Edge'		
		parm = parm_dict[parm_name] if parm_name in parm_dict else None
		if parm:
			set_keyframes(parm, data_list_1D_scaled, current_comp)

		parm_name = 'Center'		
		parm = parm_dict[parm_name] if parm_name in parm_dict else None
		if parm:
			set_keyframes(parm, data_list_2D, current_comp)

	else:
		print('Nothing is selected!')

Data

Simple EXR splitter

import math
import pprint
import DaVinciResolveScript as dvr_script

if __name__ == '__main__':

	resolve = dvr_script.scriptapp("Resolve")
	fusion = resolve.Fusion()
	current_comp = fusion.GetCurrentComp()
	active_tool = current_comp.ActiveTool
	flow = current_comp.CurrentFrame.FlowView

	exr_file_name = active_tool.GetAttrs('TOOLST_Clip_Name')[1]
	aov_name_list = list(active_tool.Clip1.OpenEXRFormat.Part.GetAttrs('INPIDT_ComboControl_ID').values())
	x, y = flow.GetPosTable(active_tool).values()

	current_comp.Lock()

	for i, aov_name in enumerate(aov_name_list):

		active_tool.Clip1.OpenEXRFormat.Part = aov_name
		# channelList = active_tool.Clip1.OpenEXRFormat.RedName.GetAttrs('INPIDT_ComboControl_ID')

		# print(aov_name)

		# pprint.pprint(channelList)


		ty = math.floor(y)+(i*3)

		loader_node = current_comp.AddTool('Loader', x+2, ty)
		loader_node.Clip = exr_file_name
		loader_node.Clip1.OpenEXRFormat.Part = aov_name
		loader_node.SetAttrs({'TOOLS_Name':aov_name, 'TOOLB_NameSet':True})

	current_comp.Unlock()