AWS Thinkbox Discussion Forums

Draft - "convert"

Hi,
Does Draft have any ImageMagick based “convert” capabilities?
Thanks,
Mike

What were you looking for specifically? IM’s “convert” tool does a lot of things :slight_smile:

Draft does have some of it, like rendering text, image conversion, simple compositing, resizing, etc.

On the other hand, we don’t have a lot of the more specific things that convert can do…

Cheers,

  • Jon

I’m after any current IM functionality to control PNG file conversion, bit depth, compression, transparency. Anything to do with manipulating PNG files.

Unfortunately we don’t have any of that kind of functionality built into Draft :frowning:

Cheers,

  • Jon

OK Jon, thanks for letting me know.
I’ll use IM directly or another tool that I’ve been playing with.
Thanks,
Mike

Does this mean that we can only create reference movies and not converted/resized files for, say, proxies?

Draft can convert and resize images, but for now you can’t control more advanced settings such as bit depth and compression.

For example, to resize an image to 160x90:

image = Draft.Image.ReadFromFile( 'c:/users/paul/image0000.exr' ) image.Resize( 160, 90 ) image.WriteToFile( 'c:/users/paul/image_proxy0000.exr' )

Or to convert from an EXR to a JPEG:

image = Draft.Image.ReadFromFile( 'c:/users/paul/image0000.exr' ) image.WriteToFile( 'c:/users/paul/image0000.jpg' )

Is there something in particular you had in mind?

No, it does not mean that.
You can resize files, convert from one format to another and so on. For example you can read 4K EXRs and output resized PNGs and JPG or whatever you want as proxies.
The question in the thread was about bit depth conversion, compression and transparency control of PNGs, and Draft does not have fine-enough controls yet to specify that a PNG or JPG should be saved in a particular way with specific settings.

So what we’re doing is working at both 4k and 2k (depending on how final an element/comp is), and different aspect ratios (sometimes elements will be 2.40, other times 1.33, 1.77, so forth), with .25 scaled proxies. Ideally we’d have a script that analyzes the input res and makes decisions accordingly (eg. to do a framing overlay, to mask for 2.40, say, the overlay needs to know the source image height and aspect ratio), but for now we’re making separate scripts depending on the output resolution and aspect ratio of the render/comp.

Right now, I’m having trouble implementing the image.WriteToFile command. It unclear to me how in the context of the example Resizable_Demo_Template.py to create the correct write path and filename. Here’s what I’ve written (it’s at the end of the attached script):


for frameNumber in range(startFrame, endFrame + 1) :

image = Image.ReadFromFile(inFile)
image.Resize( 1024, 778 )
image.WriteToFile(inFilePattern, frameNumber)

Obviously that last line is wrong, probably other things too (I’m not a coder). I’d also be curious about how to set the Resize info with variables.

I’m attaching the script for your review. Thanks.

I’ve attached the script.
4096x3112_1_movie_proxies.py.zip (1.88 KB)

one more question:
can you show me example code for where the mov file and converted files get output? looks like the default is to put a Draft folder in with the rendered frames, and put the movie in there. How can I change that to put the in a folder marked “mov” one level up from where the frames are and a resolution directory one level up for the proxies?

current:
element/4096x3112/frame.####.exr
element/4096x3112/DRAFT/frame.mov

ideal:
element/4096x3112/frame.####.exr
element/mov/frame.mov
element/1024x778/frame.####.exr

thanks!

There are a few steps involved in creating an output image path. I assume you want an image filename based on the video’s Output File?

Let’s say our Draft Output File is ‘c:/users/paul/out.mov’. We’ll need to change the extension from ‘.mov’ to ‘.jpg’ or some other image file type. We use the os.path.splitext function to split the filename into two parts, with the extension in the second part (note that this line is already in your script):

(outBase, outExt)= os.path.splitext( params['outFile'] ) In our example, this gives us:
outBase = ‘c:/users/paul/out’
outExt = ‘.mov’

Then we can make an image filename by adding ‘.jpg’ onto the end of outBase:

outFilePattern = outBase + '.jpg' This gives us outFilePattern = ‘c:/users/paul/out.jpg’.

Finally we’ll need to insert the frame number. Draft includes a ReplaceFilenameHashesWithNumber() function to do this. For example:

outFile = ReplaceFilenameHashesWithNumber( outFilePattern, 0 ) gives us outFile = ‘c:/users/paul/out0000.jpg’

We put this together to get:

for frameNumber in range(startFrame, endFrame + 1) : inFile = ReplaceFilenameHashesWithNumber(inFilePattern, frameNumber) image = Image.ReadFromFile(inFile) image.Resize(1024, 778) outFilePattern = outBase + '.jpg' outFile = ReplaceFilenameHashesWithNumber(outFilePattern, frameNumber) image.WriteToFile(outFile)

I’ll need to ask someone else to help with your other questions.

Sorry, I’m not quite sure what you mean here. The script below includes a variable resize based on:

proxyScale = 0.25 image.Resize( int(proxyScale * image.width), int(proxyScale * image.height) )
Please let me know if you’re looking for something else.

First we’ll change the movie path. The “DRAFT” movie folder is specified in a few different scripts:

  • \repository\events\Draft\Draft.py
  • \repository\Scripts\Submission\DraftSubmission\DraftSubmission.py
  • \repository\Scripts\Jobs\JobDraftSubmission\JobDraftSubmission.py

I assume you’re using the Draft event plugin, \repository\events\Draft\Draft.py. Please make a backup copy of that file, and then open it using a text editor. Around line 60, we find:

draftOutputFolder = Path.Combine( outputDirectory, "Draft" ) if not Directory.Exists( draftOutputFolder ): Directory.CreateDirectory( draftOutputFolder )
Instead of using a “Draft” subfolder, we want to go up one level and use a “mov” folder. We replace the code above with:

draftOutputFolder = Path.Combine( Directory.GetParent(Directory.GetParent(outputDirectory).FullName).FullName, "mov" ) if not Directory.Exists( draftOutputFolder ): Directory.CreateDirectory( draftOutputFolder )

Next we’ll change the proxy path. Open your Draft template script. First, near the top of the file, right after the "logoFile = " and "scale = " lines, add:

proxyScale = 0.25

We’ll use this to control the proxy size.

Next, after the "inFile = " and "inFrame = " lines, we add:

[code]outFile = params[‘outFile’]

Calculate the proxy size. The int() is required because the Resize() function can only accept integer numbers.

proxyWidth = int(proxyScale * inFrame.width)
proxyHeight = int(proxyScale * inFrame.height)

Make a proxy directory based on the proxy image dimensions

proxyPattern = os.path.dirname( os.path.dirname( outFile ) ) + ‘/’ + str(proxyWidth) + ‘x’ + str(proxyHeight) + ‘/’ + os.path.basename(inFilePattern)
proxyDir = os.path.dirname(proxyPattern)
if not os.path.exists(proxyDir):
os.mkdir(proxyDir)[/code]

Finally we change Image.WriteToFile() loop at the end of the script to use our proxy changes:

for frameNumber in range(startFrame, endFrame + 1) : inFile = ReplaceFilenameHashesWithNumber(inFilePattern, frameNumber) image = Image.ReadFromFile(inFile) image.Resize(proxyWidth, proxyHeight) proxyFile = ReplaceFilenameHashesWithNumber(proxyPattern, frameNumber) image.WriteToFile(proxyFile)
4096x3112_1_movie_proxies_new2.zip (2.07 KB)

This is great. I’ll start testing this and wrapping my head around it. I’m picking up how the scripts (and python) work slowly but surely, but I definitely think that having example bits of code (the cookbook) is a great idea as there are plenty of non-coding artists who would be able to hack something together using your example template scripts and code for their facilities.

Regarding my first question, I was wondering how I could send external data to the script, preferably from a Nuke script run through Deadline. Nuke has a “proxy scale” setting which, ideally, could drive the proxy variable inside this script. Similarly, the output path could ideally be set inside Nuke and have the info flow through to the Draft script. I can’t imagine it’s easy but it seems like it would be powerful.

Off to test this new script

Bill

Hi Paul

Thanks for the quick response. I was just implementing this and realized that my initial request wasn’t clear enough. I do want to put any output movie files in a directory called “mov” one level up from where the source frames live, but also wanted to put proxy files in a dynamically-named directory with the correct resolution (eg. 1024x778) in the same place. So to restate:

current:
./element/4096x3112/frame.####.exr
becomes
./element/4096x3112/DRAFT/frame.mov (movie)
./element/4096x3112/DRAFT/frame.####.exr (proxy)

ideal:
./element/4096x3112/frame.####.exr
becomes
./element/mov/frame.mov (movie)
./element/1024x778/frame.####.exr (proxy)

One note, when I test the code you provided, it puts the “mov” dir one additional directory above the files, eg:

new:
./element/4096x3112/frame.####.exr
becomes
./mov/frame.mov (movie)
./mov/frame.####.exr (proxy)

which puts all the files (movie + proxy) in one directory. I’ve changed the command to this;

draftOutputFolder = Path.Combine( Directory.GetParent(outputDirectory).FullName, “mov” )

but that still puts everything in one mov directory.

Is there a way to put an if/then statement into the Draft.py file? Or change the Draft.py code to

draftOutputFolder = Directory.GetParent(outputDirectory).FullName

and then add the “/mov” or [width]x[height] aka “1024x778” in the template file? Can the Directory.Exists function be called from the template or does it need to happen in the Draft.py event plugin?

Thanks for all the help

Bill

I think we’re on the same page about the directory structure. Here’s what I got during my testing:

Render output:
\path\to\output\640x480\frame####.png

Draft output:
\path\to\output\mov\frame.mov
\path\to\output\160x120\frame####.png

I’m not sure where things are going wrong. First, I’d like to make sure that you’re using the 4096x3112_1_movie_proxies_new2.py script I posted above?

Yes, you can perform the directory operations in the Draft template script, but the syntax is different. For example, here is where I (tried to?) make the proxy output directory:

proxyPattern = os.path.dirname( os.path.dirname( outFile ) ) + '/' + str(proxyWidth) + 'x' + str(proxyHeight) + '/' + os.path.basename(inFilePattern) proxyDir = os.path.dirname(proxyPattern) if not os.path.exists(proxyDir): os.mkdir(proxyDir)
Let’s break this down. Say we have outFile = r’\path\to\output\640x480\frame####.png’, proxyWidth = 160, and proxyHeight = 120. The first line:

proxyPattern = os.path.dirname( os.path.dirname( outFile ) ) + '/' + str(proxyWidth) + 'x' + str(proxyHeight) + '/' + os.path.basename(inFilePattern) builds a proxy filename: proxyPattern = \path\to\output/160x120/frame####.png

The second line:

proxyDir = os.path.dirname(proxyPattern) gets the directory part of the proxyPattern: proxyDir = \path\to\output/160x120

The third line checks if proxyDir already exists:

if not os.path.exists(proxyDir):

Finally, if the proxyDir directory does not already exist, the fourth line creates it:

   os.mkdir(proxyDir)

Please let me know if you have any questions about this.

I’ll get back to you about your proxy scale question soon.

Hi Paul

Thanks for the breakdown. I’m now working on the .mov and I’m trying to figure out how to parse just the base part of the movie name (ie. the base part of images/explosion.mov would be explosion) so that I can patch it all back together when feeding info to the videoEncoder function.

For the time being, I’m using the word ‘proxy’ as that movie filename base. I’ve attached the latest version of the Draft template script I’m using (it hardcodes the proxy sizes to 1024x778). It seems to be putting the files where I’d expect (thanks for that) and they look right, from what I can tell.

We’re setting up a new format for oversized renders (ie 2148x1556) for stereo work. “Oversize” means that extra pixel information is rendered on either side of the image, so that the sides don’t get clipped if the convergence changes. How do I indicate that I need an image to be center cropped? Make a 2048x1556 image and comp the larger one on top of it?

Since much of this file/directory naming stuff is about parsing and reconstructing names and paths, I’d also love to understand how these lines work, if you can point me to a good python reference for these things:

params = ParseCommandLine( expectedTypes, sys.argv )

inFilePattern = params[‘inFile’]
startFrame = int(params[‘startFrame’])
endFrame = int(params[‘endFrame’])

Thanks again for everything

Bill

Here is that script:
1K_1_movieProxy11.py.zip (2.92 KB)

[size=150]Splitting a filename[/size]

I’d do this using two functions: os.path.basename() and os.path.splitext(). os.path.basename() returns the filename part of a path: os.path.basename('c:/users/paul/explosion.mov') returns ‘explosion.mov’.
And os.path.splitext() splits a filename into its base and extension parts: os.path.splitext('explosion.mov') returns (‘explosion’, ‘.mov’)

os.path.splitext() returns something called a ‘tuple’. In this case, the tuple holds two strings. We want to get the first string in the tuple. One way to do this is: (base, ext) = os.path.splitext('explosion.mov') base is ‘explosion’, and ext is ‘.mov’

Putting all of this together in your script:

(outBase, outExt)= os.path.splitext( params['outFile'] ) outBase = outBase.rstrip('.') outBaseFilename = os.path.basename( outBase )
Now outBaseFilename should be the part you want (‘explosion’ in our example).

You can find a reference for these and other os.path functions here: http://docs.python.org/release/2.6/library/os.path.html

[size=150]Crop the center of an image[/size]

That sounds good to me:

bgFrame = Image.ReadFromFile(inFile) croppedImage = Image.CreateImage( 2048, 1556 ) croppedImage.CompositeWithPositionAndGravity( image, 0.5, 0.5, PositionalGravity.CenterGravity, CompositeOperator.CopyCompositeOp )
(Note: you could also use CompositeWithGravity() here. However, I’d suggest using CompositeWithPositionAndGravity() for now. This is because we made some compositing improvements in Draft Beta 8, but we failed to apply those improvements to CompositeWithGravity(). This will be fixed in Draft Beta 9).

[size=150]Draft command line parsing[/size]

sys.argv is used to access the script’s command line arguments. (http://docs.python.org/library/sys.html)

You can see the command-line arguments in the Draft job file in Deadline. To do this, right-click a Draft job in the Deadline Monitor, and choose “Explore Repository Directory…” You should see a file named draft_event_plugin_info0.job in the folder. Open this file using a text editor. On the “arguments=” line, you’ll see all of the command-line arguments that were passed to the Draft script. For example: arguments=username="" entity="" version="" width=640 height=480 frameList=0-50 startFrame=0 endFrame=50 inFile="\\path\to\out\640x480\frame####.png" outFile="\\path\to\out\mov\frame.mov" deadlineJobID=999_050_999_46c8bbf1

ParseCommandLine() reads the command-line arguments and builds a dictionary to hold them. In your example, the resulting dictionary is called params.

This dictionary lets you access the command-line arguments by name. For example, params[‘width’] returns ‘640’.

You can read more about Python dictionaries here: http://docs.python.org/release/2.5.2/lib/typesmapping.html
ParseCommandLine() is a part of Draft. If you are interested, you can find it in \repository\Draft\Mac\DraftParamParser.py. (Note that there is a copy for each platform.)

Thank you thank you thank you Paul, for stepping me through this. I can’t tell you how much anxiety a page like this gives me:

docs.python.org/release/2.6/library/os.path.html

Assuming I keep doing this stuff (I’m a huge n00b), I’m sure those types of function descriptions will get easier for me to parse, but until that day explanations like yours allow me to further swim out of the shallow end. As I’ve mentioned elsewhere, I think that Draft is in a unique position to be able to be used by artists with a novice understanding of programming and python, as opposed to the true coding bona fides required by something like RV or an automatic Nuke script for creating movies and proxies. I know it probably means more of this kind of hand holding, but it seems in line with Thinkbox’s approach to their other packages: make it robust, make it bulletproof and make it (relatively) easy to use.

So thanks again and here’s to taking off the floaties

Bill

ps. More questions to follow, I’m sure

Hi Jon,
Any movement on this?

I need to change the bit depth of PNG sequences via Draft.

Thanks,
Mike

Privacy | Site terms | Cookie preferences