Draft: Top Ten Scripts

  • Blog

Version: Deadline 8.0

BACKGROUND

Draft is a lightweight compositing and video processing tool designed to automate typical post-render tasks such as the creation and processing of QuickTimes, thumbnails and other deliverables in a pipeline. It is implemented as a Python library and is designed to be tightly integrated with Deadline. Draft can also be used as a standalone tool. Read more about Draft here and here.

A good way to get introduced to Draft is through small code snippets, or “recipes”. With this approach, you can simply take the pieces you need and combine them together, tweak the code a bit and create your first Draft script!

In this blog entry, the Top Ten Draft Cookbook Recipes are presented. These recipes have been selected either because they form the basis of most scripts or because they illustrate very useful Draft functionalities. For many more, consult the complete Draft Cookbook.

1. CONVERT AN IMAGE FROM ONE FORMAT TO ANOTHER

This recipe shows how to convert an image from one format to another. This operation is as simple as reading the image in memory and saving it in a new file with the appropriate file extension.

import Draft

img = Draft.Image.ReadFromFile( 'inputFile.exr' )
img.WriteToFile( 'outputFile.png' )

2. WRITE AN IMAGE TO FILE WITH A SPECIFIC FILE CHANNEL MAP

When an image is written to file, it might be necessary to minimize the resulting file size. This recipe describes how this can be done by using an image file channel map to specify the bit depth of each image channel when the image is written to file. An image file channel map is represented using a dictionary. Each dictionary entry is made of two strings separated by a colon. The first string corresponds to the channel name, typically ‘R’, ‘G’, ‘B’ or ‘A’, and the second string corresponds to the channel bit depth.

import Draft

# Read an image from file
img = Draft.Image.ReadFromFile( 'inputFile.exr' )

# Create and set the file channel map
fileChannelMap = { 'R':'16f', 'G':'16f', 'B':'16f', 'A':'16f' }
img.SetFileChannelMap( fileChannelMap )

# Write the image back to file
img.WriteToFile( 'outputFile.exr' )

3. SPECIFY SAVING SETTINGS IN AN IMAGE FILE

One may want to control the compression, the quality and the tile size of an image when it is written to file to meet specific requirements. This recipe lays out how users can specify these properties using an ImageInfo object. An ImageInfo stores those saving settings as properties and can be passed as an additional parameter when writing an image to file.

import Draft

# Read an image from file
img = Draft.Image.ReadFromFile( 'inputFile.exr' )

# Create a Draft.ImageInfo object and set compression, quality and tileSize
imageInfo = Draft.ImageInfo()
imageInfo.compression = 'dwaa'
imageInfo.quality = 80
imageInfo.tileSize = ( 64, 64 )

# Write the image back to file
img.WriteToFile( 'outputFile.exr', imageInfo )

4. APPLY A COLOR TRANSFORM TO AN IMAGE

It is often necessary to adjust the colors in an image to obtain specific results when the image is displayed on a particular device. This recipe illustrates how this can be achieved by applying a color transform. Applying a color transform consists of two simple steps: creating a LUT that will encapsulate your color transform and applying it to your image.

import Draft
import copy

from DraftASCCDLReader import ReadASCCDL

inFile = 'manyTeapots.png'

img = Draft.Image.ReadFromFile( inFile )

# Create a Cineon LUT
cineonLut = Draft.LUT.CreateCineon()

# Apply the LUT to a copy of the original image
cineonImg = copy.deepcopy( img )
cineonLut.Apply( cineonImg )

# Write the image back to file
cineonImg.WriteToFile( 'manyTeapotsCineonLUTApplied.png' )

# Create an ASCCDL LUT from file
ASCCDLLut = ReadASCCDL( 'my_color_correction.cc' )

# Apply the LUT to a copy of the original image
ASCCDLImage = copy.deepcopy( img )
ASCCDLLut.Apply( ASCCDLImage )

# Write the image back to file
ASCCDLImage.WriteToFile( 'manyTeapotsASCCDLLUTApplied.png' )

The original image is pictured first, the resulting image when applying a cineon LUT second and the resulting image when applying the ASCCDL LUT is last.

5. ADD A SEMI-TRANSPARENT MASK TO AN IMAGE

When modifying an image aspect ratio, some parts of the original image need to be removed. This recipe depicts how to apply a semi-transparent mask to the original image to show exactly which parts of the image will be affected.

import Draft

# For the composite operations
compOperation = Draft.CompositeOperator.OverCompositeOp

# Read an image from file
img = Draft.Image.ReadFromFile( 'manyTeapots.png' )

# Store the original image width and height
outWidth = img.width
outHeight = img.height

# Define the desired ratio
ratio = 2.35

# Create the mask
mask = Draft.Image.CreateImage( outWidth, outHeight )
mask.SetChannel( 'A', 0 )
img.SetChannel( 'A', 1 )

maskRectHeight = int( round( ( outHeight - outWidth / ratio ) / 2 ) )
maskRect = Draft.Image.CreateImage( outWidth, maskRectHeight )
maskRect.SetChannel( 'A', 0.3 ) # The value 0.3 can be adjusted, higher the value, darker the mask

mask.CompositeWithAnchor( maskRect, Draft.Anchor.SouthEast, compOperation )
mask.CompositeWithAnchor( maskRect, Draft.Anchor.NorthEast, compOperation )

# Add the mask to the image
img.Composite( mask, 0, 0, Draft.CompositeOperator.OverCompositeOp )

# Write the image back to file
img.WriteToFile( 'manyTeapotsWithMask.png' )

The original image is pictured on the left, the resulting image is on the right.

6. CREATE A SLATE FRAME

A slate frame is used to display project metadata and can be used to pass along information to a client. Typically, it is inserted at the beginning of a video. This recipe shows how to create such a slate frame by loading a standard background and by adding text to it. The text is added in three steps: creating an AnnotationInfo object that stores properties such as font, point size and colour, creating an image that contains the text, and compositing the text image over top the slate frame background.

import Draft
import datetime

# For the composite operations
compOperation = Draft.CompositeOperator.OverCompositeOp

# Read background image from file
slateFrame = Draft.Image.ReadFromFile( 'slateBackground.png' )

# Set up the text on the slate frame
slateText = [("SHOW", "My Show"),
            ("SHOT", "119"),
            ("FRAMES", "1-300"),
            ("VERSION", "12"),
            ("ARTIST", "John Doe"),
            ("DATE", datetime.datetime.now().strftime("%m/%d/%Y") )]

# Set text's color and point size
annotationInfo = Draft.AnnotationInfo()
annotationInfo.Color = Draft.ColorRGBA( 0.0, 0.1, 0.3, 1.0 )
annotationInfo.PointSize = int( slateFrame.height * 0.045 )

# Composite annotations over top the slate frame
for i in range( 0, len( slateText ) ):
    
    txtImg = Draft.Image.CreateAnnotation( slateText[i][0] + ": ", annotationInfo )
    slateFrame.CompositeWithPositionAndAnchor( txtImg, 0.15, 0.7 - (i * 0.06), Draft.Anchor.SouthEast, compOperation )
    
    txtImg = Draft.Image.CreateAnnotation( slateText[i][1], annotationInfo )
    slateFrame.CompositeWithPositionAndAnchor( txtImg, 0.15, 0.7 - (i * 0.06), Draft.Anchor.SouthWest, compOperation )

slateFrame.WriteToFile('mySlateFrame.png')

Here’s the result:

7. ADD BURN-INS TO MOVIE FRAMES

One may also want to display basic information, or burn-ins, on each movie frame. Burn-ins can include general information about your project such as the name of the movie, the name of the studio and the current date and can also include information specific to each frame such as the frame number. This recipe describes how to add burn-ins to movie frames. It is done in a very similar way as adding text to a slate frame.

import Draft
import datetime

# For the composite operations
compOperation = Draft.CompositeOperator.OverCompositeOp

# Read an image from file
img = Draft.Image.ReadFromFile( 'awesomeTeapots.png' )

# Set text's color and point size
annotationInfo = Draft.AnnotationInfo()
annotationInfo.Color = Draft.ColorRGBA( 1.0, 1.0, 1.0, 1.0 )
annotationInfo.PointSize = int( img.height * 0.045 )

# Add each annotation
titleAnnotation = Draft.Image.CreateAnnotation( "Awesome Teapots", annotationInfo )
img.CompositeWithAnchor( titleAnnotation, Draft.Anchor.NorthWest, compOperation )

dateAnnotation = Draft.Image.CreateAnnotation( datetime.datetime.now().strftime("%m/%d/%Y %I:%M %p"), annotationInfo )
img.CompositeWithAnchor( dateAnnotation, Draft.Anchor.NorthEast, compOperation )

studioNameAnnotation = Draft.Image.CreateAnnotation( "VFX.co", annotationInfo )
img.CompositeWithAnchor( studioNameAnnotation, Draft.Anchor.SouthEast, compOperation )

Here’s the result:

8. CREATE A QUICKTIME MOVIE

This recipe illustrates a fundamental post rendering task: creating a video. In order to do that, users first need to create a VideoEncoder object and encode each frame, one after the other. Once all frames have been encoded, the encoding process is finalized and the video is saved to file.

import Draft
from DraftParamParser import ReplaceFilenameHashesWithNumber

# Create the video encoder.
encoder = Draft.VideoEncoder( 'myMovie.mov' )

# Encode each frames
for currFrame in range( 1, 200 ):
    
    # Read current frame from file
    currFile = ReplaceFilenameHashesWithNumber( 'movieFrame####.exr', currFrame )
    frame = Draft.Image.ReadFromFile( currFile )
    
    # Add current frame to the video.
    encoder.EncodeNextFrame( frame )

# Finalize and save the resulting video.
encoder.FinalizeEncoding()

9. EMBED A TIMECODE IN A VIDEO FILE

In Draft, a Timecode object stores the hours, minutes, seconds and frame number associated to a timecode, as described in SMPTE standard. A timecode is a metadata holding a time reference and is typically used in video editing. This recipe outlines how to embed a timecode when creating a video file from an image sequence, by extracting the timecode embedded in the first frame (if there is one) and injecting it into the VideoEncoder.

import Draft
from DraftParamParser import ReplaceFilenameHashesWithNumber

# Create a Draft.ImageInfo object to extract a Draft.Timecode object
imageInfo = Draft.ImageInfo()

# Main encoding loop
for currFrame in range( 1, 200 ):
    currFile = ReplaceFilenameHashesWithNumber( 'movieFrame####.exr', currFrame )
    frame = Draft.Image.ReadFromFile( currFile, imageInfo )
    
    # If first frame and a timecode is found, specify the timecode parameter when creating the encoder
    if currFrame == 1:
        if( imageInfo.timecode ):
            encoder = Draft.VideoEncoder( 'myMovie.mov', timecode = imageInfo.timecode )
        else:
            encoder = Draft.VideoEncoder( 'myMovie.mov' )
    
    # Add current frame to the video.
    encoder.EncodeNextFrame( frame )

encoder.FinalizeEncoding()

10. CONCATENATE VIDEO FILES

This recipe shows how to concatenate movie clips to create one single video. This last recipe is very simple but incredibly powerful! Within Deadline, it is used in Quick Draft, behind the scene, to concatenate movie chunks when performing distributed encoding across a rendering farm. For more details, see the section entitled “Distributed Encoding” from this blog entry.

import Draft

# Define input and output files
inputVideoFile1 = 'movie_clip_1.mov'
inputVideoFile2 = 'movie_clip_2.mov'
inputVideoFile3 = 'movie_clip_3.mov'
outputVideoFile = 'complete_movie.mov'

# Concatenate video files
Draft.ConcatenateVideoFiles( [ inputVideo1, inputVideo2, inputVideo3 ], outputVideo )

PUTTING IT ALL TOGETHER

Now, let us put together a few of the above recipes to create a complete Draft script. A typical task would be to create a QuickTime movie, add a slate frame, add frame burn-ins, apply a color transform, and add a semi-transparent mask to each frame. A sample Draft script that would do all of the above is as follows.

import Draft
import datetime

from DraftASCCDLReader import ReadASCCDL # For creating the ASCCDL LUT
from DraftParamParser import ReplaceFilenameHashesWithNumber # For reading frames when encoding

# Final dimensions
outWidth = 1920
outHeight = 1152

# For the composite operations
compOperation = Draft.CompositeOperator.OverCompositeOp

# Set text's color and point size
annotationInfo = Draft.AnnotationInfo()
annotationInfo.Color = Draft.ColorRGBA( 0.0, 0.1, 0.3, 1.0 )
annotationInfo.PointSize = int( outHeight * 0.045 )

# Set up the text for the slate frame
slateText = [("SHOW", "My Show"), 
            ("SHOT", "119"), 
            ("FRAMES", "1-300"), 
            ("VERSION", "12"), 
            ("ARTIST", "John Doe"), 
            ("DATE", datetime.datetime.now().strftime("%m/%d/%Y") )]
 
# Create the slate frame
slateFrame = Draft.Image.ReadFromFile( 'slateBackground.png' )

for i in range( 0, len( slateText ) ):
    
    txtImg = Draft.Image.CreateAnnotation( slateText[i][0] + ": ", annotationInfo )
    slateFrame.CompositeWithPositionAndAnchor( txtImg, 0.18, 0.7 - (i * 0.06), Draft.Anchor.SouthEast, compOperation )
    
    txtImg = Draft.Image.CreateAnnotation( slateText[i][1], annotationInfo )
    slateFrame.CompositeWithPositionAndAnchor( txtImg, 0.18, 0.7 - (i * 0.06), Draft.Anchor.SouthWest, compOperation )

annotationInfo.Color = Draft.ColorRGBA( 1.0, 1.0, 1.0, 1.0 )

titleAnnotation = Draft.Image.CreateAnnotation( "Awesome Teapots", annotationInfo )
dateAnnotation = Draft.Image.CreateAnnotation( datetime.datetime.now().strftime("%m/%d/%Y %I:%M %p"), annotationInfo )
studioNameAnnotation = Draft.Image.CreateAnnotation( "VFX.co", annotationInfo )

# Create an ASCCDL LUT from file
ASCCDLLut = ReadASCCDL( 'my_color_correction.cc' )

# Create the semi-transparent mask
ratio = 2.35 # The value 2.35 can be adjusted to fit your project's needs
maskRectHeight = int( round( ( outHeight - outWidth / ratio ) / 2 ) )
maskRect = Draft.Image.CreateImage( outWidth, maskRectHeight )
maskRect.SetChannel( 'A', 0.3 ) # The value 0.3 can be adjusted, higher the value, darker the mask

mask = Draft.Image.CreateImage( outWidth, outHeight )
mask.SetChannel( 'A', 0 )

mask.CompositeWithAnchor( maskRect, Draft.Anchor.SouthEast, compOperation )
mask.CompositeWithAnchor( maskRect, Draft.Anchor.NorthEast, compOperation )

# Initialize the video encoder.
encoder = Draft.VideoEncoder( 'MyMovie.mov', width=outWidth, height=outHeight ) 

# Encode the slate frames at the start of the video
numberOfSlateFrames = 12 # Hold for half a second @ 24fps
for i in range( 0, numberOfSlateFrames ):
    encoder.EncodeNextFrame( slateFrame )

# Main encoding loop
for currFrame in range( 1, 10 ):
    currFile = ReplaceFilenameHashesWithNumber( 'movieFrame####.exr', currFrame )
    frame = Draft.Image.ReadFromFile( currFile )
    
    # Add burn-ins
    frame.CompositeWithAnchor( titleAnnotation, Draft.Anchor.NorthWest, compOperation )
    frame.CompositeWithAnchor( dateAnnotation, Draft.Anchor.NorthEast, compOperation )
    frame.CompositeWithAnchor( studioNameAnnotation, Draft.Anchor.SouthEast, compOperation )
    
    # Apply the ASCCDL LUT
    ASCCDLLut.Apply( frame )
    
    # Add the semi-transparent mask
    frame.Composite( mask, 0, 0, compOperation )
    
    # Add the frame to the video.
    encoder.EncodeNextFrame( frame )

# Finalize and save the resulting video.
encoder.FinalizeEncoding() 

WRAPUP

In this blog entry, the goal was to keep things simple. For more details on all the available options and additional features, please consult the full Draft Documentation. You can also consult this blog entry to see how you can integrate Draft in your Deadline workflow using Custom Draft.