Deadline Documentation
RSS< Twitter< etc

Event Plug-in Scripting


Overview

Event plug-ins can be created to execute specific tasks in response to specific events in Deadline (like when a job is submitted or when it finishes). For example, event plug-ins can be used to communicate with in-house pipeline tools to update the state of shots or tasks, or they can be used to submit a post-processing job when another job finishes. These event plug-ins are completely scripted, which means that it's easy to create your own event plug-ins or customize the existing ones (providing you have some experience with the Python scripting language). Because Deadline can use IronPython or Python for .NET to execute scripts,, not only can you access all of the Python modules, you can import all of the .NET libraries as well, which means the level of customization is nearly endless.

By default, all jobs will trigger these events. However, there is a job property that can be enabled to suppress events. In the Monitor, you can set the Suppress Events property under the Advanced tab in the Job Properties dialog. If you have a custom submission tool or script, you can specify the following in the job info file:

SuppressEvents=True

Note that events will be executed by different Deadline applications, depending on the context of the event. For example, the OnJobSubmitted event is processed by Deadline Command after the job has been submitted, while the OnJobFinished event is normally processed by the Slave that finishes the last task for the job. However, OnJobFinished could also be processed by the Monitor if manually marking a job as complete.

Creating Event Plug-in

To create a new event plug-in, you start by creating a folder in the Repository's events folder and give it the name of your event plug-in. For the sake of this document, we will call our new event plug-in MyEvent. All relative script and configuration files for this event plug-in are to be placed in this folder (some are required and some are optional).

The dlinit File - Required

The first file is MyEvent.dlinit, which is the main configuration file for this event plug-in. It is a plain text file that defines a few general key=value event plug-in properties, which include:

Key Name Description
Enabled If the event plug-in is enabled (True/False). Only enabled event plug-ins will respond to Deadline events.

It can also define key=value custom settings to be used by the event plug-in. For example, if you are connecting to an in-house pipeline tool, you may want the URL and credentials to be configurable, in which case our MyEvent.dlinit file might look like this:

Enabled=True
PipelineURL=http://[myserver]/pipeline
PipelineUserName=myuser
PipelinePassword=mypassword

The py File - Required

The other required file is MyEvent.py, which is the main event plug-in script file. It defines the main DeadlineEventListener class that contains the necessary tasks to perform in response to specific events. The template for this script file might look like this:

from Deadline.Events import *

######################################################################
## This is the function that Deadline calls to get an instance of the
## main DeadlineEventListener class.
######################################################################
def GetDeadlineEventListener():
    return MyEvent()

######################################################################
## This is the main DeadlineEventListener class for MyEvent.
######################################################################
class MyEvent (DeadlineEventListener):

    # TODO: Place code here

The GetDeadlineEventListener() function is important, as it allows Deadline to get an instance of our MyEvent class (which is extending the abstract DeadlineEventListener class). If this function isn't defined, Deadline will report an error when it tries to load the event plug-in. Notice that we're importing the Deadline.Events namespace so that we can access the DeadlineEventListener class. In fact, because we're using IronPython, you can import any .NET namespace, like System, System.IO, etc:

from System import *
from System.Diagnostics import *
from System.IO import *

The MyEvent class will need to override certain functions based on the types of events you want to respond to. All functions are optional, so make sure to include at least one so that your event plug-in actually does something:

Function Description
void OnJobArchived( Job job ) Override this function to respond to a job that has been archived. The job is passed as an argument.
void OnJobFailed( Job job ) Override this function to respond to a job that has failed. The job is passed as an argument.
void OnJobFinished( Job job ) Override this function to respond to a job being marked as complete when its last task finishes rendering. The job is passed as an argument.
void OnJobPended( Job job ) Override this function to respond to a job that has been placed in the Pending state (because it's a dependent job or it has been scheduled to run at a later time). The job is passed as an argument.
void OnJobReleased( Job job ) Override this function to respond to a job that has been released from the Pending state (because a dependent job became active or it was time for a scheduled job to start). The job is passed as an argument.
void OnJobRequeued( Job job ) Override this function to respond to a job being requeued. The job is passed as an argument.
void OnJobResumed( Job job ) Override this function to respond to a job being resumed from the suspended, failed, or pending state. The job is passed as an argument.
void OnJobStarted( Job job ) Override this function to respond to a job being picked up by a Slave for the first time. The job is passed as an argument.
void OnJobSubmitted( Job job ) Override this function to respond to a job being submitted. The job is passed as an argument.
void OnJobSuspended( Job job ) Override this function to respond to a job being suspended. The job is passed as an argument.
void OnJobUnarchived( Job job ) Override this function to respond to a job that has been unarchived. The job is passed as an argument.

After overriding a few functions, your MyEvent.py script file might look something like this:

from Deadline.Events import *

######################################################################
## This is the function that Deadline calls to get an instance of the
## main DeadlineEventListener class.
######################################################################
def GetDeadlineEventListener():
    return MyEvent()

######################################################################
## This is the main DeadlineEventListener class for MyEvent.
######################################################################
class MyEvent (DeadlineEventListener):

    def OnJobSubmitted( self, job ):
        #TODO: Connect to pipeline site to notify it that a job has been submitted for a particular shot or task.
    
    def OnJobFinished( self, job ):
        #TODO: Connect to pipeline site to notify it that the job for a particular shot or task is complete.

Remember that by default, Deadline will execute scripts using the IronPython engine. If you want to use Python for .NET, the first line of your MyEvent.py must look like the following. This tells Deadline that the event plugin is a Python.NET event plugin:

#Python.NET

In addition, the way you override functions is slightly different for Python.NET event plugins. For example, in an IronPython event plugin, you just need to define the function in the plugin class (see the code for OnJobSubmitted() and OnJobFinished() above). In Python.NET event plugins, you also need to add a corresponding event handler in the __init__ constructor for the event plugin class. For example:

######################################################################
## This is the main DeadlineEventListener class for MyEvent.
######################################################################
class MyEvent (DeadlineEventListener):
    def __init__( self ):
        # Set up the event handlers because this is a Python.NET event plugin
        self.OnJobSubmittedEvent += self.OnJobSubmitted
        self.OnJobFinishedEvent += self.OnJobFinished

    def OnJobSubmitted( self, job ):
        #TODO: Connect to pipeline site to notify it that a job has been submitted for a particular shot or task.
    
    def OnJobFinished( self, job ):
        #TODO: Connect to pipeline site to notify it that the job for a particular shot or task is complete.

The param File - Optional

The MyEvent.param is used to declare properties which are used by the Event Configuration dialog in the Monitor to dynamically generate a UI for modifying custom settings as they appear in the MyEvent.dlinit file. After you've created this file, open the Monitor and enter Super User mode. Then select Tools -> Configure Events and look for your event plug-in in the list on the left.

The file might look something like:

[Enabled]
Type=boolean
Label=Enabled
Default=True
Description=If this event plug-in should respond to events.

[PipelineURL]
Type=string
Label=Pipeline URL
Default=http://[myserver]/pipeline
Description=The URL for our pipeline website.

[PipelineUserName]
Type=string
Label=Pipeline User Name
Default=
Description=The user name for our pipeline website.

[PipelinePassword]
Type=string
Label=Pipeline Password
Default=
Description=The password for our pipeline website.

You'll notice that the property names between the square brackets matches the custom keys we defined in our MyEvent.dlinit file. This means that these control will change the corresponding settings. The available key=value pairs for the properties defined here are:

Key Name Description
Category The category the control should go under.
CategoryIndex This determines the control's order under its category. This does the same thing as Index.
CategoryOrder This determines the category's order among other categories. If more than one CategoryOrder is defined for the same category, the lowest value is used.
Default The default value to be used if this property is not defined in the dlinit file. This does the same thing as DefaultValue.
DefaultValue The default value to be used if this property is not defined in the dlinit file. This does the same thing as Default.
Description A short description of the property the control is for (displayed as a tooltip in the UI).
DisableIfBlank If True, a control will not be shown if this property is not defined in the dinit file (True/False). This does the same thing as IgnoreIfBlank.
IgnoreIfBlank If True, a control will not be shown if this property is not defined in the dinit file (True/False). This does the same thing as DisableIfBlank.
Index This determines the control's order under its category. This does the same thing as CategoryIndex.
Label The control label.
Required If True, a control will be shown for this property even if it's not defined in the dlinit file (True/False).
Type The type of control (Boolean/Enum/Filename/FilenameSave/Float/Folder/Integer/Label/MultiFilename/String).

There are also key/value pairs for specific controls:

Key Name Description
DecimalPlaces The number of decimal places for the Float controls.
Filter The filter string for the Filename, FilenameSave, or MultiFilename controls.
Increment The value to increment the Integer or Float controls by.
Items The semicolon separated list of items for the Enum control. This does the same thing as Values.
Maximum The maximum value for the Integer or Float controls.
Minimum The minimum value for the Integer or Float controls.
Values The semicolon separated list of items for the Enum control. This does the same thing as Items.

The ico File - Optional

You can add a MyEvent.ico file, which is an 16x16 icon file for identifying the event plug-in in the Event Plugin Configuration dialog. After all of your files have been created, your event plug-in folder should look something like this:

Event Plug-in Logs and Error Reports

When an event plug-in that uses the LogInfo or LogWarning functions finishes executing, its log will be stored with the job's other render logs, which you can view in the Monitor by right-clicking on the job in the Monitor and selecting Job Reports -> View Log Reports.

When an error occurs in an event-plugin, an error report will also be stored with the job's other render errors, which you can view in the Monitor by right-clicking on the job in the Monitor and selecting Job Reports -> View Error Reports.

Global Function Reference

All these functions can be called from your event plug-in python script file.

Event Plugin Configuration Functions

Function Description
bool GetBooleanConfigEntry( string Key ) Returns the value for the given Key from the event plug-in dlinit file. Throws an error if the Key does not exist.
bool GetBooleanConfigEntryWithDefault( string Key, bool Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.
string GetConfigEntry( string Key ) Returns the value for the given Key from the event plug-in dlinit file. Throws an error if the Key does not exist.
string GetConfigEntryWithDefault( string Key, string Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.
float GetFloatConfigEntry( string Key ) Returns the value for the given Key from the event plug-in dlinit file. Throws an error if the Key does not exist.
float GetFloatConfigEntryWithDefault( string Key, float Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.
int GetIntegerConfigEntry( string Key ) Returns the value for the given Key from the event plug-in dlinit file. Throws an error if the Key does not exist.
int GetIntegerConfigEntryWithDefault( string Key, int Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.
long GetLongConfigEntry( string Key ) Returns the value for the given Key from the event plug-in dlinit file. Throws an error if the Key does not exist.
long GetLongConfigEntryWithDefault( string Key, int Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.

Job Plugin Info File Functions

Function Description
bool GetBooleanPluginInfoEntry( string Key ) Returns the value for the given Key from the job's plug-in info file. Throws an error if the Key does not exist.
bool GetBooleanPluginInfoEntryWithDefault( string Key, bool Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.
float GetFloatPluginInfoEntry( string Key ) Returns the value for the given Key from the job's plug-in info file. Throws an error if the Key does not exist.
float GetFloatPluginInfoEntryWithDefault( string Key, float Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.
int GetIntegerPluginInfoEntry( string Key ) Returns the value for the given Key from the job's plug-in info file. Throws an error if the Key does not exist.
int GetIntegerPluginInfoEntryWithDefault( string Key, int Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.
long GetLongPluginInfoEntry( string Key ) Returns the value for the given Key from the job's plug-in info file. Throws an error if the Key does not exist.
long GetLongPluginInfoEntryWithDefault( string Key, int Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.
string GetPluginInfoEntry( string Key ) Returns the value for the given Key from the job's plug-in info file. Throws an error if the Key does not exist.
string GetPluginInfoEntryWithDefault( string Key, string Default ) Same as above but with a Default value to be returned if the Key does not exist. Useful for backwards-compatibility, because it will not throw an error if the Key does not exist.

Job Information Functions

Function Description
string[] GetAuxiliaryFilenames() Returns the list of files that were submitted with the job.
string GetDataFilename() Returns the path of the scene file that was submitted with the job.
string GetPluginInfoFilename() Returns the path of the plugin-info file that was submitted with the job.

Logging and Control Funtions

Function Description
void LogInfo( string Message ) Prints an info message to the log of the application that is currently executing the event plug-in.
void LogWarning( string Message ) Prints a warning message to the log of the application that is currently executing the event plug-in.

General Process Functions

Function Description
int RunProcess( string Executable, string Arguments, string StartupDir, int TimeoutMilliseconds ) Launches a program and waits for the specified TimeoutMilliseconds for it to complete (specify -1 for no timeout). The program's stdout is redirected to the log of the application that is running it. Returns the process' return code (1 is returned for a timeout).

File/Path/Directory Functions

Function Description
string CheckPathMapping( string Path ) Maps the given path to a different path if necessary. If no path mapping is required, the original path is returned. Path Mappings can be set up in the Repository Options in the Monitor.
string CheckPathMappingInFile( string InFileName, string OutFileName ) Checks each line of the given input file to see if a path mapping is required. The new file with any paths that may have been swapped is saved to the output file defined. Path Mappings can be set up in the Repository Options in the Monitor.
string CheckPathMappingInFileAndReplaceSeparator( string InFileName, string OutFileName, string SeparatorToReplace, string NewSeparator ) This is identical to CheckPathMappingInFile above, except that lines that are swapped out will replace the given SeparatorToReplace with the given NewSeparator.
bool PathMappingRequired( string Path ) Checks if the given Path needs to be mapped to a different path. Path Mappings can be set up in the Repository Options in the Monitor.

Quicktime Generation Example

An event plugin can be used to automatically submit a Quicktime job to create a movie from the rendered images of a job that just finished. An example of an event plugin like this can be downloaded from the Miscellaneous Deadline Downloads Page. To install the event plugin, just unzip the downloaded file to [Repository]\events.

Configuration Files

The QuicktimeGen.dlinit and QuicktimeGen.param files define a couple of settings that can be configured from the Deadline Monitor. Here you can specify a path to the Quicktime settings XML file you want to use. This settings file can be generated from the Submit Quicktime Job To Deadline submitter in the Monitor.

The QuicktimeGen.dlinit file:

Enabled=True
QTSettings=\\ws-wpg-026\share\quicktime_export_settings.xml

The QuicktimeGen.param file:

[Enabled]
Type=boolean
Label=Enabled
Default=True
Description=If this event plug-in should respond to events.

[QTSettings]
Type=filename
Label=QT Settings XML File
Default=
Description=The QT settings xml file.

Script File

The QuicktimeGen.py file only overrides the OnJobFinished function, since we only want to submit a Quicktime job when a job finishes.

import re

from System.IO import *
from System.Text import *

from Deadline.Events import *
from Deadline.Scripting import *

######################################################################
## This is the function that Deadline calls to get an instance of the
## main DeadlineEventListener class.
######################################################################
def GetDeadlineEventListener():
    return MyEvent()

######################################################################
## This is the main DeadlineEventListener class for MyEvent.
######################################################################
class MyEvent (DeadlineEventListener):
    
    def OnJobFinished( self, job ):
        

In this example, there are only two checks in place to see if we should submit a Quicktime job. The first is that a movie settings file has been set in the event plugin configuration, and the second is that a Nuke job has finished. The script can be edited to add additional checks as required.

        movieSettings = GetConfigEntryWithDefault( "QTSettings", "" ).strip()
        
        # Only submit a QT job for finished Nuke jobs, and only if a QT settings file has been set.
        if movieSettings == "" or job.JobPlugin != "Nuke":
            return
        

Once those checks have passed, we need to get the output path(s) from the job that just finished. We do this using the JobOutputDirectories and JobOutputFileNames properties. Then we loop through each file name because we want to submit a Quicktime job for each output sequence. Finally, we replace the padding characters in the current output file name with an actual frame number from the job.

        outputDirectories = job.JobOutputDirectories
        outputFilenames = job.JobOutputFileNames
        paddingRegex = re.compile("[^\\?#]*([\\?#]+).*")
        	
        # Submit a QT job for each output sequence.
        for i in range( 0, len(outputFilenames) ):
            	
            outputDirectory = outputDirectories[i]
            outputFilename = outputFilenames[i]
            outputPath = Path.Combine(outputDirectory,outputFilename).replace("//","/")
            
             # Swap out the padding character for an actual frame.
            m = re.match(paddingRegex,outputPath)
            if( m != None):
                padding = m.group(1)
                frame = StringUtils.ToZeroPaddedString(job.JobFramesList[0],len(padding),False)
                outputPath = outputPath.replace( padding, frame )
            

With the padding replaced, we now have an input image for the Quicktime job, which we can use this to figure out the output movie file name. Now that we have all the Quicktime properties that we need, we can create the submission files and submit the job to Deadline.

                inputFilename = outputPath
                movieFilename = Path.ChangeExtension( FrameUtils.GetFilenameWithoutPadding( inputFilename ), ".mov" )
                movieName = Path.GetFileNameWithoutExtension( movieFilename )
                movieFrames = job.JobFrames
                
                # Create job info file.
                jobInfoFilename = Path.Combine( ClientUtils.GetDeadlineTempPath(), "quicktime_job_info.job" )
                writer = StreamWriter( jobInfoFilename, False, Encoding.Unicode )
                writer.WriteLine( "Plugin=Quicktime" )
                writer.WriteLine( "Name=%s" % movieName )
                writer.WriteLine( "Comment=Auto-submitted QT" )
                writer.WriteLine( "Department=%s" % job.JobDepartment )
                writer.WriteLine( "Pool=%s" % job.JobPool )
                writer.WriteLine( "Group=%s" % job.JobGroup )
                writer.WriteLine( "Priority=%s" % job.JobPriority )
                writer.WriteLine( "MachineLimit=1" )
                writer.WriteLine( "Frames=%s" % movieFrames )
                writer.WriteLine( "ChunkSize=100000" )
                writer.WriteLine( "OutputFilename0=%s" % movieFilename )
                writer.Close()
                
                # Create plugin info file.
                pluginInfoFilename = Path.Combine( ClientUtils.GetDeadlineTempPath(), "quicktime_plugin_info.job" )
                writer = StreamWriter( pluginInfoFilename, False, Encoding.Unicode )
                writer.WriteLine( "InputImages=%s" % inputFilename )
                writer.WriteLine( "OutputFile=%s" % movieFilename )
                writer.WriteLine( "FrameRate=24" )
                writer.WriteLine( "Codec=QuickTime Movie" )
                writer.Close()
                
                # Now submit the job.
                ClientUtils.ExecuteCommand( (jobInfoFilename,pluginInfoFilename,movieSettings) )

Note that the settings file used by the Quicktime will only work on the operating system it was created on (ie: a settings file created on Mac OSX will not work on Windows, and vice versa).