All posts by Igor Ridanovic

Embracing the AI Wave: DaVinci Resolve Chatbot

In 2023, AI has made leaps and bounds, becoming an integral part of our lives.

The AI and machine learning developments affected the Media & Entertainment industry and I have done a lot of experimentation, some of which has been documented in my series of DaVinci Resolve and AI integration videos.

I’ve taken these tests a step further and released several chatbots. They rely on the latest product from OpenAI called GPT which allows developers to easily deploy chatbots and augment their knowledge with custom datasets.

DaVinci Resolve™ and Fusion™ AI Assistant

Expert AI chatbot support for DaVinci Resolve capable of generating  API code, DCTLs, analyzing uploaded  images and much more!

Adobe After Effects AI Assistant

Expert AI support for Adobe After Effects motion graphics, VFX, and paint.

Adobe Photoshop AI Assistant

Expert AI support for Adobe Photoshop.

Qt PySide AI Assistant

Code review, code generation, tips and tricks for popular PySide2 and PySide6 UI toolkits.

Titobot

A lighthearted AI impersonator of Josip Broz Tito.

It’s all about amplifying creativity!

DaVinci Resolve Joke Message

This Python script for Blackmagic Resolve and Resolve Studio will create a custom popup message you can use to play a joke on your unsuspecting coworkers.

To install copy and paste this script into a plain text editor and save as April Fools Day.py to one of these locations:

Linux:
/opt/resolve/Fusion/Scripts/Edit

macOS:
/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Edit

Windows:
%PROGRAMDATA%\Blackmagic Design\DaVinci Resolve\Support\Fusion\Scripts\Edit

The script will be accessible from the Workspace -> Scripts -> Edit menu.

Windows requires Python 2.7 or 3.6.

See the video tutorial for more details.

Visit www.metafide.com for DaVinci Resolve productivity apps and custom Resolve coding.

Thanks Adrew Hazelden for demystifying the Blackmagic API built-in Qt bindings.

                    
#! /usr/bin/env python
# -*- coding: utf-8 -*-

# Joke pop-up messsage for DaVinci Resolve and resolve Studio. Use at your own risk.
# I'm not responsible for any damages or loss of work arising from the use of this script.
# The script itself can't cause any damage, but a colorist freaking out may!

# Igor Ridanovic, Meta Fide, www.metafide.com
# Thanks Adrew Hazelden for demystifying the BMD API built-in Qt bindings.

# Save this script as April Fools Day.py to:
# Windows: %PROGRAMDATA%\Blackmagic Design\DaVinci Resolve\Support\Fusion\Scripts\Edit
# macOS: /Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Edit
# Linux: /opt/resolve/Fusion/Scripts/Edit

# The script will be accessible from the Workspace -> Scripts -> Edit menu.

import sys
import time

buttonCSS = '''
    QPushButton {
    background-color: #2f2f2f;
    color: #929292;
    width: 98px;
    height: 13px;
    font-size: 12px;
    font-weight: normal;
    border-color: #e64b3d;
    border-style: solid;
    border-width: 1px;
    border-radius: 12px;
    padding: 5px;
    margin-left: 8px;
    margin-top: 20px;
    margin-bottom: 6px;
    outline: 0;
    }
    QPushButton:hover {
    color: #ffffff;
    }
    QPushButton:pressed {
    background-color: #171717;
    }
    }
'''

labelCSS = '''
    QLabel {
    color: #fafafa;
    font-size: 16px;
    font-weight: bold;
    margin-left: 8px;
    margin-right: 8px;
    }
'''

jokeMessageCSS = '''
    QLabel {
    margin-left: 8px;
    margin-right: 8px;
    }
'''


class AprilFoolsDay(object):

    def __init__(self):
        try:
            self.ui   = fusion.UIManager
            self.disp = bmd.UIDispatcher(self.ui)
        # No support for the DVR internal Qt when running as an external script.
        except AttributeError:
            sys.exit()

    def collect_input(self):

        win = self.disp.AddWindow({  'ID': 'Message1',
                                     'WindowTitle': 'DaVinci Resolve',
                                     'WindowModality': 'WindowModal'
                            },

                            [
                                self.ui.VGroup(
                                [
                                    self.ui.VGap(0, .25),
                                    self.ui.Label({  'ID': 'msgHeader',
                                                'WordWrap': True,
                                                'Text': 'Enter a joke message and time delay.',
                                                'StyleSheet': labelCSS
                                            }),

                                    self.ui.LineEdit({'ID': 'jokeHeader', 'PlaceholderText': 'Message header'}),
                                    self.ui.LineEdit({'ID': 'jokeMsg', 'PlaceholderText': 'Message'}),
                                    self.ui.HGroup(
                                    [
                                        self.ui.LineEdit({'ID': 'secondsDelay', 'PlaceholderText': 'Seconds'}),
                                        self.ui.HGap(0, 7)
                                    ]),

                                    self.ui.HGroup(
                                    [
                                        self.ui.HGap(0, 4),
                                        self.ui.Button({'ID': 'Accept',
                                                    'Text': 'Done',
                                                    'StyleSheet': buttonCSS
                                                })
                                    ]),
                                ]),
                            ])



        self.itm = win.GetItems()

        def _closewindow(ev):
            self.disp.ExitLoop()

        win.On.Accept.Clicked  = _closewindow

        win.Resize([500,230])
        win.Show()
        self.disp.RunLoop()
        win.Hide()

        # Return delay for the joke message.
        delay = self.itm['secondsDelay'].Text
        if delay == '' or not delay.isdigit():
            delay = 0

        return int(delay)



    def display_message(self):

        jokeHeader  = self.itm['jokeHeader'].Text
        jokeMessage = self.itm['jokeMsg'].Text

        if jokeHeader.rstrip() == '':
            jokeHeader = 'DaVinci Resolve Joke Message'

        if jokeMessage.rstrip() == '':
            jokeMessage = 'This joke message was brought to you by Meta Fide. When you are done joking visit www.metafide.com for more serious off-the-shelf and custom apps.'

        win = self.disp.AddWindow({  'ID': 'Message2',
                                 'WindowTitle': 'DaVinci Resolve',
                                 'WindowModality': 'WindowModal'
                            },

                            [
                                self.ui.VGroup(
                                [

                                    self.ui.Label({  'ID': 'msgHeader',
                                                'WordWrap': True,
                                                'Text': jokeHeader,
                                                'StyleSheet': labelCSS
                                            }),
                                    self.ui.Label({  'ID': 'msg-txt',
                                                'WordWrap': True,
                                                'Text': jokeMessage,
                                                'StyleSheet': jokeMessageCSS
                                            }),

                                    self.ui.HGroup(
                                    [
                                        self.ui.HGap(0, 4),
                                        self.ui.Button({'ID': 'Accept',
                                                    'Text': 'Done',
                                                    'StyleSheet': buttonCSS
                                                })
                                    ]),
                                ]),
                            ])



        itm = win.GetItems()

        def _closewindow(ev):
            self.disp.ExitLoop()

        win.On.Accept.Clicked  = _closewindow

        win.Resize([500,185])
        win.Show()
        self.disp.RunLoop()
        win.Hide()


if __name__ == '__main__':

    afd   = AprilFoolsDay()
    delay = afd.collect_input()
    time.sleep(delay)
    afd.display_message()

Resolve Render Chime

DaVinci Resolve doesn’t come with a built in way to play a chime when a render is complete, but you can make your own using this super simple Python script for macOS and Windows.

Copy and paste the script below into a plain text editor. For macOS save it as Render Chime.py to:

/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Deliver

On Windows save it as Render Chime.py to:

%PROGRAMDATA%\Blackmagic Design\DaVinci Resolve\Fusion\Scripts\Deliver

Set an audible sound at the end of DaVinci Resolve render.

When you setup a new render job in Resolve’s Deliver page check the box for “Trigger scripts at end of render job” and select “Render Chime” from the dropdown list. That’s all!

You can use your own sounds if you edit the soundFile line. You can even have Resolve trigger other things like upload files or send out an SMS or an email like I do in this older Resolve API script.

For more DaVinci Resolve API work and scripts check out my YouTube channel and GitHub.

                    
#! /usr/bin/env python
# -*- coding: utf-8 -*-

# DaVinci Resolve render chime for macOS and Windows.
# For macOS place this script in:
# /Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Deliver.
# For Windows place this script in:
# C:\ProgramData\Blackmagic Design\DaVinci Resolve\Fusion\Scripts\Deliver
# Check "Trigger script at end of render job" in Resolve's Deliver page
# and select this script from the dropdown menu.

# Igor Riđanović, www.metafide.com

from subprocess import Popen
import sys

if sys.platform.startswith('darwin'):
    soundFile = '/System/Library/Sounds/Glass.aiff'
    Popen(['afplay', soundFile])

if sys.platform.startswith('win'):
    soundFile = 'C:\\Windows\\Media\\Alarm01.wav'
    Popen(['C:\Program Files (x86)\Windows Media Player\wmplayer.exe', soundFile])

Fusion Fog and Smoke

This is a simple Blackmagic Fusion comp that creates a puff of particle smoke.  Copy and paste to the Fusion flow.

{
	Tools = ordered() {
		Ellipse1_2_2_1_1_1_1_1 = EllipseMask {
			CurrentSettings = 2,
			CustomData = {
				Settings = {
					[1] = {
						Tools = ordered() {
							Ellipse1 = EllipseMask {
								Inputs = {
									ClippingMode = Input { Value = FuID { "None" } },
									BorderWidth = Input { Value = 0.135 },
									SoftEdge = Input { Value = 0.2 },
									Height = Input { Value = 0.359 },
									MaskWidth = Input { Value = 1920 },
									PixelAspect = Input { Value = { 1, 1 } },
									MaskHeight = Input { Value = 1080 },
									Width = Input { Value = 0.333 }
								},
								CtrlWZoom = false,
								ViewInfo = OperatorInfo { Pos = { -104, 153 } },
								CustomData = {
								}
							}
						}
					}
				}
			},
			Inputs = {
				SoftEdge = Input { Value = 0.2, },
				BorderWidth = Input { Value = 0.0403, },
				MaskWidth = Input { Value = 1920, },
				MaskHeight = Input { Value = 1080, },
				PixelAspect = Input { Value = { 1, 1 }, },
				ClippingMode = Input { Value = FuID { "None" }, },
				Width = Input { Value = 0.193, },
				Height = Input {
					Value = 0.193,
					Expression = "Width",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 915.96, 1572.72 } },
		},
		Ellipse1_2_2_1_1_1_1_2 = EllipseMask {
			CurrentSettings = 2,
			CustomData = {
				Settings = {
					[1] = {
						Tools = ordered() {
							Ellipse1 = EllipseMask {
								Inputs = {
									ClippingMode = Input { Value = FuID { "None" } },
									BorderWidth = Input { Value = 0.135 },
									SoftEdge = Input { Value = 0.2 },
									Height = Input { Value = 0.359 },
									MaskWidth = Input { Value = 1920 },
									PixelAspect = Input { Value = { 1, 1 } },
									MaskHeight = Input { Value = 1080 },
									Width = Input { Value = 0.333 }
								},
								CtrlWZoom = false,
								ViewInfo = OperatorInfo { Pos = { -104, 153 } },
								CustomData = {
								}
							}
						}
					}
				}
			},
			Inputs = {
				SoftEdge = Input { Value = 0.2, },
				BorderWidth = Input { Value = 0.0403, },
				MaskWidth = Input { Value = 1920, },
				MaskHeight = Input { Value = 1080, },
				PixelAspect = Input { Value = { 1, 1 }, },
				ClippingMode = Input { Value = FuID { "None" }, },
				Width = Input { Value = 0.1, },
				Height = Input {
					Value = 0.1,
					Expression = "Width",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 750.96, 1605.72 } },
		},
		FastNoise3_1_1 = FastNoise {
			CurrentSettings = 3,
			CustomData = {
				Settings = {
					[1] = {
						Tools = ordered() {
							FastNoise3 = FastNoise {
								Inputs = {
									XScale = Input { Value = 2.7 },
									Contrast = Input { Value = 0.908 },
									GradientType = Input { Value = 5 },
									EffectMask = Input {
										SourceOp = "Polygon1",
										Source = "Mask"
									},
									Width = Input { Value = 300 },
									["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" } },
									Height = Input { Value = 300 },
									Gradient = Input {
										Value = Gradient {
											Colors = {
												[0] = { 0.622, 0.622, 0.632, 1 },
												[1] = { 0.811, 0.816, 0.811, 1 }
											}
										}
									},
									GlobalOut = Input { Value = 30 },
									Brightness = Input { Value = -0.277 },
									SeetheRate = Input { Value = 0.479 },
									Detail = Input { Value = 5.44 }
								},
								CtrlWZoom = false,
								ViewInfo = OperatorInfo { Pos = { 198.964, 1366.69 } },
								CustomData = {
								}
							}
						}
					},
					[2] = {
						Tools = ordered() {
							FastNoise3_1_1 = FastNoise {
								Inputs = {
									XScale = Input { Value = 11.81 },
									Brightness = Input { Value = -0.277 },
									Contrast = Input { Value = 0.908 },
									Height = Input { Value = 600 },
									GradientType = Input { Value = 5 },
									EffectMask = Input {
										SourceOp = "Ellipse1_2_2_1_1_1_1_1",
										Source = "Mask"
									},
									Width = Input { Value = 600 },
									Angle = Input { Value = 15.7 },
									["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" } },
									Discontinuous = Input { Value = 1 },
									Color1Alpha = Input { Value = 1 },
									GlobalOut = Input { Value = 30 },
									Gradient = Input {
										Value = Gradient {
											Colors = {
												[0] = { 0.622, 0.622, 0.632, 1 },
												[1] = { 0.811, 0.816, 0.811, 1 }
											}
										}
									},
									SeetheRate = Input { Value = 0.75 },
									Detail = Input { Value = 5.56 }
								},
								CtrlWZoom = false,
								ViewInfo = OperatorInfo { Pos = { 256.197, 1570.16 } },
								CustomData = {
								}
							}
						}
					}
				}
			},
			Inputs = {
				Width = Input { Value = 600, },
				Height = Input { Value = 600, },
				["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
				Discontinuous = Input { Value = 1, },
				Detail = Input { Value = 5.56, },
				Contrast = Input { Value = 0.908, },
				Brightness = Input { Value = -0.277, },
				XScale = Input { Value = 12.9, },
				Angle = Input { Value = 15.7, },
				SeetheRate = Input { Value = 0.75, },
				Color1Alpha = Input { Value = 1, },
				GradientType = Input { Value = 5, },
				Gradient = Input {
					Value = Gradient {
						Colors = {
							[0] = { 0.622, 0.622, 0.632, 1 },
							[1] = { 0.811, 0.816, 0.811, 1 }
						}
					},
				},
				EffectMask = Input {
					SourceOp = "Ellipse1_2_2_1_1_1_1_1",
					Source = "Mask",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 915.162, 1606.52 } },
		},
		FastNoise3_1 = FastNoise {
			CurrentSettings = 2,
			CustomData = {
				Settings = {
					[1] = {
						Tools = ordered() {
							FastNoise3 = FastNoise {
								Inputs = {
									XScale = Input { Value = 2.7 },
									Contrast = Input { Value = 0.908 },
									GradientType = Input { Value = 5 },
									EffectMask = Input {
										SourceOp = "Polygon1",
										Source = "Mask"
									},
									Width = Input { Value = 300 },
									["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" } },
									Height = Input { Value = 300 },
									Gradient = Input {
										Value = Gradient {
											Colors = {
												[0] = { 0.622, 0.622, 0.632, 1 },
												[1] = { 0.811, 0.816, 0.811, 1 }
											}
										}
									},
									GlobalOut = Input { Value = 30 },
									Brightness = Input { Value = -0.277 },
									SeetheRate = Input { Value = 0.479 },
									Detail = Input { Value = 5.44 }
								},
								CtrlWZoom = false,
								ViewInfo = OperatorInfo { Pos = { 198.964, 1366.69 } },
								CustomData = {
								}
							}
						}
					}
				}
			},
			Inputs = {
				Width = Input { Value = 600, },
				Height = Input { Value = 600, },
				["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
				Detail = Input { Value = 5.44, },
				Contrast = Input { Value = 2.023, },
				Brightness = Input { Value = -0.277, },
				XScale = Input { Value = 2.7, },
				SeetheRate = Input { Value = 0.425, },
				GradientType = Input { Value = 5, },
				Gradient = Input {
					Value = Gradient {
						Colors = {
							[0] = { 0.622, 0.622, 0.632, 1 },
							[1] = { 0.811, 0.816, 0.811, 1 }
						}
					},
				},
				EffectMask = Input {
					SourceOp = "Ellipse1_2_2_1_1_1_1",
					Source = "Mask",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 1081.95, 1564.46 } },
		},
		pEmitter2_1 = pEmitter {
			ID = 146,
			Inputs = {
				RandomSeed = Input { Value = 27329, },
				Number = Input {
					SourceOp = "pEmitter2_1Number",
					Source = "Value",
				},
				Lifespan = Input { Value = 24, },
				Velocity = Input { Value = 1.3, },
				AngleVariance = Input { Value = 360, },
				AngleZVariance = Input { Value = 360, },
				Rotation = Input { Value = 1, },
				RotationVariance = Input { Value = 1, },
				RotationZVariance = Input { Value = 360, },
				SpinVariance = Input { Value = 1, },
				SpinZVariance = Input { Value = 5, },
				Style = Input { Value = FuID { "ParticleStyleBitmap" }, },
				["ParticleStyle.ColorControls"] = Input { Value = 1, },
				["ParticleStyle.ColorOverLifeControls"] = Input { Value = 1, },
				["ParticleStyle.ColorOverLife"] = Input {
					Value = Gradient {
						Colors = {
							[0.00353356890459364] = { 0, 0, 0, 0 },
							[0.0989399293286219] = { 1, 1, 1, 1 }
						}
					},
				},
				["ParticleStyle.SizeControls"] = Input { Value = 1, },
				["ParticleStyle.Size"] = Input { Value = 1.2, },
				["ParticleStyle.SizeOverLife"] = Input {
					SourceOp = "pEmitter2SizeoverLife_1",
					Source = "Value",
				},
				["ParticleStyle.FadeControls"] = Input { Value = 1, },
				["ParticleStyle.FadeOut"] = Input { Value = 0.256, },
				["ParticleStyle.MergeControls"] = Input { Value = 1, },
				["ParticleStyle.SubtractiveAdditive"] = Input { Value = 0.569, },
				["ParticleStyle.BlurControls"] = Input { Value = 1, },
				["ParticleStyle.BlurOverLife"] = Input {
					SourceOp = "pEmitter2BluroverLife2D_1",
					Source = "Value",
				},
				["ParticleStyleBitmap.DropToolsHere"] = Input {
					SourceOp = "FastNoise3_1",
					Source = "Output",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 1082.94, 1595.48 } },
		},
		pEmitter2_1Number = BezierSpline {
			SplineColor = { Red = 233, Green = 206, Blue = 78 },
			NameSet = true,
			KeyFrames = {
				[0] = { 5, RH = { 1.33333333333333, 3.33333333333333 }, Flags = { Linear = true } },
				[4] = { 0, LH = { 2.66666666666667, 1.66666666666667 }, Flags = { Linear = true } }
			}
		},
		pEmitter2SizeoverLife_1 = LUTBezier {
			KeyColorSplines = {
				[0] = {
					[0] = { 0.1, RH = { 0.105106382978723, 0.682127659574468 }, Flags = { Linear = true } },
					[0.95] = { 1, LH = { 0.573052493928638, 0.968261390959546 } }
				}
			},
			SplineColor = { Red = 192, Green = 128, Blue = 64 },
		},
		pEmitter2BluroverLife2D_1 = LUTBezier {
			KeyColorSplines = {
				[0] = {
					[0] = { 0.5, RH = { 0.333333333333333, 0.5 }, Flags = { Linear = true } },
					[1] = { 0.5, LH = { 0.666666666666667, 0.5 }, Flags = { Linear = true } }
				}
			},
			SplineColor = { Red = 192, Green = 128, Blue = 64 },
		},
		Ellipse1_2_2_1_1_1_1 = EllipseMask {
			CurrentSettings = 2,
			CustomData = {
				Settings = {
					[1] = {
						Tools = ordered() {
							Ellipse1 = EllipseMask {
								Inputs = {
									ClippingMode = Input { Value = FuID { "None" } },
									BorderWidth = Input { Value = 0.135 },
									SoftEdge = Input { Value = 0.2 },
									Height = Input { Value = 0.359 },
									MaskWidth = Input { Value = 1920 },
									PixelAspect = Input { Value = { 1, 1 } },
									MaskHeight = Input { Value = 1080 },
									Width = Input { Value = 0.333 }
								},
								CtrlWZoom = false,
								ViewInfo = OperatorInfo { Pos = { -104, 153 } },
								CustomData = {
								}
							}
						}
					}
				}
			},
			Inputs = {
				SoftEdge = Input { Value = 0.2, },
				BorderWidth = Input { Value = 0.0403, },
				MaskWidth = Input { Value = 1920, },
				MaskHeight = Input { Value = 1080, },
				PixelAspect = Input { Value = { 1, 1 }, },
				ClippingMode = Input { Value = FuID { "None" }, },
				Width = Input { Value = 0.193, },
				Height = Input {
					Value = 0.193,
					Expression = "Width",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 1081.95, 1529.48 } },
		},
		pMerge4 = pMerge {
			ID = 115,
			Inputs = {
				Particles1 = Input {
					SourceOp = "pEmitter2_1_1",
					Source = "Output",
				},
				Particles2 = Input {
					SourceOp = "pEmitter2_1",
					Source = "Output",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 1245.96, 1638.72 } },
		},
		FastNoise3_1_2 = FastNoise {
			CurrentSettings = 2,
			CustomData = {
				Settings = {
					[1] = {
						Tools = ordered() {
							FastNoise3 = FastNoise {
								Inputs = {
									XScale = Input { Value = 2.7 },
									Contrast = Input { Value = 0.908 },
									GradientType = Input { Value = 5 },
									EffectMask = Input {
										SourceOp = "Polygon1",
										Source = "Mask"
									},
									Width = Input { Value = 300 },
									["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" } },
									Height = Input { Value = 300 },
									Gradient = Input {
										Value = Gradient {
											Colors = {
												[0] = { 0.622, 0.622, 0.632, 1 },
												[1] = { 0.811, 0.816, 0.811, 1 }
											}
										}
									},
									GlobalOut = Input { Value = 30 },
									Brightness = Input { Value = -0.277 },
									SeetheRate = Input { Value = 0.479 },
									Detail = Input { Value = 5.44 }
								},
								CtrlWZoom = false,
								ViewInfo = OperatorInfo { Pos = { 198.964, 1366.69 } },
								CustomData = {
								}
							}
						}
					}
				}
			},
			Inputs = {
				Width = Input { Value = 600, },
				Height = Input { Value = 600, },
				["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
				Detail = Input { Value = 5.44, },
				Contrast = Input { Value = 1.744, },
				Brightness = Input { Value = 0.085, },
				XScale = Input { Value = 2.7, },
				SeetheRate = Input { Value = 0.069, },
				GradientType = Input { Value = 5, },
				Gradient = Input {
					Value = Gradient {
						Colors = {
							[0] = { 0.622, 0.622, 0.632, 1 },
							[1] = { 0.811, 0.816, 0.811, 1 }
						}
					},
				},
				EffectMask = Input {
					SourceOp = "Ellipse1_2_2_1_1_1_1_2",
					Source = "Mask",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 750.96, 1638.72 } },
		},
		Merge2 = Merge {
			Inputs = {
				Background = Input {
					SourceOp = "FastNoise3_1_2",
					Source = "Output",
				},
				Foreground = Input {
					SourceOp = "FastNoise3_1_1",
					Source = "Output",
				},
				ApplyMode = Input { Value = FuID { "Overlay" }, },
				PerformDepthMerge = Input { Value = 0, },
			},
			ViewInfo = OperatorInfo { Pos = { 915.96, 1638.72 } },
		},
		pEmitter2_1_1 = pEmitter {
			ID = 218,
			Inputs = {
				RandomSeed = Input { Value = 7300, },
				Number = Input {
					SourceOp = "pEmitter2_1_1Number",
					Source = "Value",
				},
				Lifespan = Input { Value = 24, },
				VelocityControls = Input { Value = 1, },
				Velocity = Input { Value = 1.5, },
				VelocityVariance = Input { Value = 0.498, },
				AngleVariance = Input { Value = 360, },
				AngleZVariance = Input { Value = 360, },
				Rotation = Input { Value = 1, },
				RotationVariance = Input { Value = 1, },
				RotationZVariance = Input { Value = 360, },
				SpinVariance = Input { Value = 1, },
				SpinZVariance = Input { Value = -15, },
				Style = Input { Value = FuID { "ParticleStyleBitmap" }, },
				["ParticleStyle.ColorControls"] = Input { Value = 1, },
				["ParticleStyle.ColorOverLifeControls"] = Input { Value = 1, },
				["ParticleStyle.ColorOverLife"] = Input {
					Value = Gradient {
						Colors = {
							[0.00353356890459364] = { 0, 0, 0, 0 },
							[0.0989399293286219] = { 1, 1, 1, 1 }
						}
					},
				},
				["ParticleStyle.SizeControls"] = Input { Value = 1, },
				["ParticleStyle.Size"] = Input { Value = 1.3, },
				["ParticleStyle.SizeOverLife"] = Input {
					SourceOp = "pEmitter2_1_1SizeoverLife",
					Source = "Value",
				},
				["ParticleStyle.FadeControls"] = Input { Value = 1, },
				["ParticleStyle.FadeOut"] = Input { Value = 0.075, },
				["ParticleStyle.MergeControls"] = Input { Value = 1, },
				["ParticleStyle.SubtractiveAdditive"] = Input { Value = 0.611, },
				["ParticleStyle.BlurControls"] = Input { Value = 1, },
				["ParticleStyle.BlurOverLife"] = Input {
					SourceOp = "pEmitter2_1_1BluroverLife2D",
					Source = "Value",
				},
				["ParticleStyleBitmap.DropToolsHere"] = Input {
					SourceOp = "Merge2",
					Source = "Output",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 1082.94, 1638.72 } },
		},
		pEmitter2_1_1Number = BezierSpline {
			SplineColor = { Red = 233, Green = 206, Blue = 78 },
			NameSet = true,
			KeyFrames = {
				[0] = { 5, RH = { 1.33333333333333, 3.33333333333333 }, Flags = { Linear = true } },
				[4] = { 0, LH = { 2.66666666666667, 1.66666666666667 }, Flags = { Linear = true } }
			}
		},
		pEmitter2_1_1SizeoverLife = LUTBezier {
			KeyColorSplines = {
				[0] = {
					[0] = { 0.1, RH = { 0.105106382978723, 0.682127659574468 }, Flags = { Linear = true } },
					[0.95] = { 1, LH = { 0.573052493928638, 0.968261390959546 } }
				}
			},
			SplineColor = { Red = 192, Green = 128, Blue = 64 },
		},
		pEmitter2_1_1BluroverLife2D = LUTBezier {
			KeyColorSplines = {
				[0] = {
					[0] = { 0.5, RH = { 0.333333333333333, 0.5 }, Flags = { Linear = true } },
					[1] = { 0.5, LH = { 0.666666666666667, 0.5 }, Flags = { Linear = true } }
				}
			},
			SplineColor = { Red = 192, Green = 128, Blue = 64 },
		},
		Note5_1 = Note {
			Inputs = {
				Comments = Input { Value = "puff 2\n", }
			},
			ViewInfo = StickyNoteInfo {
				Pos = { 1309.87, 1542.32 },
				Size = { 196, 179.3 }
			},
		},
		pFriction2_1 = pFriction {
			ID = 32,
			Inputs = {
				VelocityFriction = Input {
					SourceOp = "pFriction2_1VelocityFrictionLinear",
					Source = "Value",
				},
				Input = Input {
					SourceOp = "pMerge4",
					Source = "Output",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 1410.96, 1638.72 } },
		},
		pFriction2_1VelocityFrictionLinear = BezierSpline {
			SplineColor = { Red = 246, Green = 164, Blue = 230 },
			NameSet = true,
			KeyFrames = {
				[0] = { 0, RH = { 3.5, 0.24 }, Flags = { Linear = true } },
				[40] = { 0.192, LH = { 24.2424242424242, 0.175030303030303 } }
			}
		},
		pDirectionalForce2_1 = pDirectionalForce {
			ID = 79,
			PassThrough = true,
			Inputs = {
				Strength = Input { Value = 0.695, },
				Direction = Input { Value = 90, },
				Input = Input {
					SourceOp = "pFriction2_1",
					Source = "Output",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 1520.96, 1638.72 } },
		},
		pRender3_1 = pRender {
			CtrlWZoom = false,
			Inputs = {
				_MotionBlurWarning = Input { Disabled = true, },
				Width = Input { Value = 1920, },
				Height = Input { Value = 1080, },
				["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
				OutputMode = Input { Value = FuID { "TwoD" }, },
				IntegrationMethod = Input { Value = FuID { "RK4" }, },
				["MaterialID.MaterialID"] = Input { Value = 5, },
				["ObjectID.ObjectID"] = Input { Value = 5, },
				Translation = Input { Value = 1, },
				Rotation = Input { Value = 1, },
				Perspective = Input { Value = 0.0692, },
				Input = Input {
					SourceOp = "pDirectionalForce2_1",
					Source = "Output",
				},
			},
			ViewInfo = OperatorInfo { Pos = { 1623.96, 1641.83 } },
		}
	}
}

Resolve DRP Watched Folder

This Python 2.7 or 3.x compatible script will watch a directory and move DaVinci Resolve DRP files to a backup folder one level below.

When a Resolve user repeatedly saves DRPs using a keyboard shortcut they will overwrite each other because they all use the same file name. When the script is running it will append the original name with a timestamp and move the DRP file to the backup directory.

Only tested in Linux, but it should work on all three supported OS-es. Windows does not come with Python. You need to install it yourself.

The best way to run the script is from the command line because it will report progress.  That way you can also easily stop it with CTRL+C.

Make sure to start the script before you save any DRPs. At start the script will delete any lose  files it finds in the watched folder.

You can also run this script from Resolve script dropdown menu and see the reporting in the Resolve built in console.

Download movedrp.py

FilmLight API

Baselight v5 has joined the exclusive club of motion picture finishing systems that expose the application features via an API.

Baselight’s API is called FLAPI which is pronounced as “flappy” according to FilmLight’s Martin Tlaskal. It stands for Film Light API.

Let not the acronym confuse you. This is an industrial strength tool and FilmLight is just getting started.

An API is a set of software tools that allow developers to create applications that interact with another software like Baselight. What does that mean if you’re a Baselight colorist or an assistant?

It means that many onerous tasks like logging metadata can be automated. It also means that third party developers can create symbiotic applications that provide entirely new features that don’t exist in Baselight. This opens up the door to pipeline and workflow enhancing tools similar to the Meta Fide apps for DaVinci Resolve.

FLAPI is actually a set of multiple APIs for several popular languages. There are Python, JavaScript, and Node.js APIs, as well as the bindings for Java.

The Film Light API is built with a modern post production facility in mind where multiple seats of Baselight use a common database to collaborate on projects.

The communications protocol uses WebSockets making it very simple to control not only a specific in-house Baselight, but also to control off-site Baselights. In fact, the very first day I had FLAPI up and running I was using my laptop to connect to a remote Baselight from 12 miles away.

The implications are many. With some content security precautions in place, it’s possible to create a system where an on-set Baselight automatically sets up a job at the home facility so everything is ready when the media arrives. It’s also possible to create an in-house system where the operations personnel can monitor job stats in real time.

FLAPI supports signals which can trigger events in the the external script or an application. For example, a change in the grade can trigger a third party app to refresh shot thumbnails.

The documented classes and methods are primarily concerned with job and scene management, LUT and CDL insertion, and media render. There are no documented API classes or methods in Baselight v5.2 that allow transport control or grade manipulation.

This Python 2.7 script demonstrates the simplest use case of fetching the Baselight application info:

#! /usr/bin/env python
import flapi
conn = flapi.Connection('localhost')
if conn.connect():   
    print conn.Application.get_application_info()

FLAPI examples on Github.

Watch a video tutorial showing how you can connect to Baselight using the API.

Resolve Project Automation

DaVinci Resolve™ Studio has recently added support for scripting.

Users normally interact with Resolve™ via the user interface. The scripting makes it possible to bypass the user interface and control the software by typing commands.

This feature may be difficult for most users, but it opens up DaVinci Resolve™ Studio to third party applications. The first application to utilize the scripting is Meta Fide Projector.

Setting up projects in Resolve™ is easy. Staying on top of your facility naming conventions can be onerous.

In environments where there is a frequent project handoff it’s necessary to use standard nomenclature and bin organizational structure. It is also very useful to have all standard media assets like logos, bars, facility slates, graphics toolkits, etc. imported into a project.

Doing all this by hand for each new project is time consuming. This is where Projector steps in. Creating a new project with Projector is as simple as giving it a name and clicking a button.

All of the project’s bin structure as well as the common assets are defined in a user template. This template ensures all new projects adhere to the facility standards. However, it is also possible to define multiple templates. The user can build a template for each client or for each type of a job.

Projector takes this a step further by allowing the command line use. This way other applications can trigger Projector automatically in the non-GUI mode.

For example, it is possible for Adobe After Effects to launch Projector after completing the render, create a new DaVinci Resolve™ project, and import all the rendered shots into a bin. All this can take place without any user input.

Projector is available from Meta Fide as a desktop application for Linux, MacOS, and Windows.

DaVinci Resolve Dynamic Text Hack

This Windows app demonstrates DaVinci Resolve external dynamic title generation.

DaVinci Resolve V15 allows Fusion macros to be used on the edit page as title templates. This app generates macros on-the-fly.

The app has two buttons. One generates a date/time stamp at the time the button is pressed. The other button retrieves Bundesliga RSS news (German football league) and formats the headlines as a bottom of the screen crawl.

It is necessary to click each button once before launching Resolve for the first time. Resolve loads the list or templates into the memory only once at start time. Once the templates are loaded we can click the buttons to refresh the templates. The text content of the templates cut into  the timeline is frozen.

Windows executable (8.1 MB)

Python script

Resolve Scripting

DaVinci Resolve V15 added a powerful new feature to its ever expanding toolset. Resolve merged with Fusion VFX software and inherited the Fusion’s scripting capability.

In addition to the existing FuScript, Resolve now has an API that opens up the the color grading application to third party software integration and pipeline automation.

The documentation lists a whole range of methods for creating and structuring projects, importing media, writing and reading metadata, applying color presets, and setting up and rendering timelines.

The API is accessible via Lua or Python. Users can use the built-in console or the native OS command line console to execute commands. But the true power comes from using external scripts or applications to automate tasks in Resolve.

This sample Python script shows the simplicity of the API. It retrieves the currently loaded project’s name once an hour and calls a user supplied presentation function to display the name.

#! /usr/bin/env python

import time

# Import the API module and
# create resolve instance

import DaVinciResolveScript as drs
resolve = drs.scriptapp('Resolve')

projectManager = resolve.GetProjectManager()
project = projectManager.GetCurrentProject()

def presentation(p)
# Supply your own function here
    print 'The current Resolve project name:', p

while True:
   projectName = project.GetName()
   presentation(projectName)
  time.sleep(3600)

Download other sample Resolve Python scripts.