SHOW:
|
|
- or go back to the newest paste.
1 | ''' | |
2 | FCP XML to Nuke | |
3 | - | v1.4 - |
3 | + | v1.51 - |
4 | - | Fixed a bug handling XMLs with multiple sequences. |
4 | + | Added lock_range true |
5 | v1.5 - | |
6 | Added render format selection, to allow the user to set what format the created write nodes are set to. Currently the choices are Prores4444, EXR, JPG (0.9 422), and DPX. | |
7 | Rendering from Nuke in Prores 422HQ is not reccomended due to color shifts introduced by Nuke's YUV->RGB color transform. When Prores4444 is selected, the framerate is set to the clip framerate for the current clip. | |
8 | v1.4 - | |
9 | Fixed a bug handling XMLs with multiple sequences. | |
10 | v1.3 - | |
11 | Made support for Premiere FCPXML format more robust. Premiere FCP XML stores all sequences in the project, whereas | |
12 | Final Cut Pro's XML format stores only a single sequence. I added a box that lets you choose what sequence | |
13 | of the Premiere XML file to process, and it should work more reliably now. | |
14 | Added support for transfer of linear TimeRemaps, Translates, Scales, and Rotates into Nuke. | |
15 | Also transfers framerate for each clip, and many other improvements. | |
16 | - | set to the output path. |
16 | + | |
17 | ||
18 | This script takes a Final Cut XML file with a single flattened video track, and builds Nuke scripts for each clip in the timeline. | |
19 | It is intended as a simple way to automate workflows between FCP/Premiere and Nuke. | |
20 | It creates a Nuke script with global first and last frame set, a frameRange node with the proper framerange, and a Write node | |
21 | set to the output path and the render format that you specify. | |
22 | There is an option for creating subdirectories for every Nuke script created. Handles are also an option. | |
23 | It can parse reel number and clip number from Red and Alexa footage, or can use the clip filename as the base naming for the output files. | |
24 | ||
25 | This script was somewhat inspired by compflows.blogspot.com, but has been written from scratch and is a bit more flexible (although it only goes from XML->NukeScripts and not from renders back to an XML at the moment). | |
26 | This has only been tested on OSX, but in theory should be cross-platform compatible. Comments and suggestions are welcome! | |
27 | ||
28 | # The menu.py example entry below adds this script in a folder called "Scripts" in your toolbar. | |
29 | import fcpxml_to_nuke | |
30 | nuke.toolbar('Nodes').addMenu('Scripts').addCommand('FCP XML to Nuke', 'fcpxml_to_nuke.process_xml()') | |
31 | ||
32 | ##### | |
33 | This software is provided with no guarantee of functionality, no warranty, and no support. | |
34 | I am not responsible if it breaks your computer or deletes your files. | |
35 | However, you are free to use it for anything, share it with anyone, and modify it however you wish. Have fun! | |
36 | ''' | |
37 | ||
38 | import nuke, os | |
39 | from xml.dom.minidom import parse | |
40 | ||
41 | def process_xml(): | |
42 | ''' | |
43 | Imports an FCP XML file, locates each clip in the timeline on Track 1, | |
44 | and for each clip, builds a nuke script for that file and puts it in the output directory specified | |
45 | ||
46 | New Features that would be nice to have: | |
47 | Customized naming patterns based on reel/clip number. | |
48 | Handle FCP XML from Premiere or FCP / FCPX ( Are there differences in the XML structure for these? ) | |
49 | Choose additional output naming and directory formatting patterns | |
50 | ''' | |
51 | ||
52 | # Build the Nuke Panel where locations and stuff is specified. | |
53 | p = nuke.Panel("FCP XML Import") | |
54 | xml_file = 'FCP XML To Import' | |
55 | output_dir = 'Directory to Output Nuke Scripts' | |
56 | subdirectories = 'Create subdirectories for each script?' | |
57 | render_dir = 'Render Directory for All Write Nodes' | |
58 | handle_length = "0 5 10 15 20 30 35 40" | |
59 | clip_name_format = "Bypass RED Alexa" | |
60 | render_format = 'Prores4444 exr jpg dpx' | |
61 | ||
62 | p.addFilenameSearch("FCP XML File", xml_file) | |
63 | p.addBooleanCheckBox("Create subdirectories for each script", subdirectories) | |
64 | p.addFilenameSearch("Output Directory", output_dir) | |
65 | p.addFilenameSearch("Render Directory", render_dir) | |
66 | p.addEnumerationPulldown("Handle Length", handle_length) | |
67 | p.addEnumerationPulldown("Clip Name Format", clip_name_format) | |
68 | p.addEnumerationPulldown("Render Format", render_format) | |
69 | p.setWidth(600) | |
70 | if not p.show(): | |
71 | return | |
72 | ||
73 | # Assign vars from Nuke Panel user-entered data | |
74 | xml_file = p.value("FCP XML File") | |
75 | output_dir = p.value("Output Directory") | |
76 | subdirectories = p.value("Create subdirectories for each script") | |
77 | render_dir = p.value("Render Directory") | |
78 | handle_length = int( p.value("Handle Length") ) | |
79 | clip_name_format = p.value("Clip Name Format") | |
80 | render_format = p.value('Render Format') | |
81 | ||
82 | # Create paths for render directory if it does not exist | |
83 | if not os.path.isdir(render_dir): | |
84 | os.mkdir(render_dir) | |
85 | if not os.path.isdir(output_dir): | |
86 | os.mkdir(output_dir) | |
87 | ||
88 | ||
89 | ############################ | |
90 | # Begin Parsing XML File | |
91 | ############################ | |
92 | dom = parse( xml_file ) | |
93 | ||
94 | # Prompt user to choose which sequence to process, because Premiere's XML export includes all sequences. | |
95 | # Much of this complexity is to handle sequences with spaces in their names, because the nuke.panel | |
96 | # addEnumerationPulldown is space-demarcated. The logic below handles spaces in sequence names and allows the user | |
97 | # to choose which sequence to process, and stores the sequence to process as a variable to access | |
98 | ||
99 | sequences = [] | |
100 | sequence_objects = [] | |
101 | i = 0 | |
102 | # Makes a list of all sequence names | |
103 | for seq in dom.getElementsByTagName('sequence'): | |
104 | try: | |
105 | seq.getElementsByTagName('uuid')[0].firstChild.data #Catches all sequence objects with a uuid. These are edits. | |
106 | print "sequence name is:", seq.getElementsByTagName('name')[0].childNodes[0].data | |
107 | - | # but only if there is more than one sequence in the XML file. If not, we'll just set the 'row' var to 0, and roll with that. |
107 | + | |
108 | sequences.append( [seqname] ) | |
109 | sequence_objects.append( seq ) | |
110 | # If a sequence name has space characters, replace them with underscores for the Nuke enumeration pulldown panel | |
111 | if seqname.find(' ') != -1: | |
112 | sequences[i].append( seqname.replace(' ', '_') ) | |
113 | except: | |
114 | print i, " is not a sequence!" | |
115 | continue | |
116 | i += 1 | |
117 | ||
118 | print "Sequences are: ", sequences | |
119 | # ??? Do all of the below messy work to prepare for deciding between multiple sequences, including dealing with spaces in sequence names | |
120 | # but only if there is more than one sequence in the XML file. If not, we'll just set the 'row' var to 0, and go forward with that. | |
121 | if len(sequences) > 1: | |
122 | # Makes a " " demarcated string with all sequence names for the nuke enumeration pulldown panel | |
123 | seq_enum = '' | |
124 | for i, seq in enumerate( sequences ): | |
125 | if len(sequences[i]) == 1: | |
126 | seq_enum += sequences[i][0] + ' ' | |
127 | else: | |
128 | seq_enum += sequences[i][1] + ' ' | |
129 | ||
130 | # Create a Nuke panel for the user to choose which sequence to process | |
131 | seq_panel = nuke.Panel("Choose Sequence To Process") | |
132 | seq_panel.addEnumerationPulldown("Choose Sequence", seq_enum) | |
133 | seq_panel.setWidth(400) | |
134 | if not seq_panel.show(): | |
135 | return | |
136 | chosen_sequence = seq_panel.value("Choose Sequence") | |
137 | ||
138 | # Gets the index of the chosen sequence in the list of sequences, stores the sequence XML object as a variable | |
139 | for row, i in enumerate(sequences): | |
140 | try: | |
141 | column = i.index( chosen_sequence ) | |
142 | except ValueError: | |
143 | continue | |
144 | break | |
145 | else: | |
146 | row = 0 | |
147 | #chosen_seqobj = dom.getElementsByTagName('sequence')[row] | |
148 | - | fps = 97 |
148 | + | |
149 | print "Chosen sequence is number ", row, chosen_seqobj.getElementsByTagName('name')[0].firstChild.data | |
150 | ||
151 | seq_res_x = int( chosen_seqobj.getElementsByTagName('format')[0].getElementsByTagName('width')[0].firstChild.data ) | |
152 | seq_res_y = int( chosen_seqobj.getElementsByTagName('format')[0].getElementsByTagName('height')[0].firstChild.data ) | |
153 | print "Sequence resolution is ", seq_res_x,"x",seq_res_y | |
154 | ||
155 | sequence_fps = int( chosen_seqobj.getElementsByTagName('timebase')[0].firstChild.data ) | |
156 | try: | |
157 | # This is a hack conditional: if the <ntsc> tag exists below the <timebase> tag, the framerate is set to its NTSC equivalent fractional framerate. | |
158 | # For example, 24 becomes 23.976 | |
159 | chosen_seqobj.getElementsByTagName('rate')[0].getElementsByTagName('ntsc')[0].firstChild.data | |
160 | sequence_fps = round(sequence_fps / 1.001, 3) | |
161 | except: | |
162 | pass | |
163 | print "Sequence framerate is: ", sequence_fps | |
164 | ||
165 | # Set optional effect parameters to False | |
166 | timeremap_value = False | |
167 | scale_value = False | |
168 | - | fps = float(clip.getElementsByTagName('timebase')[0].firstChild.data) |
168 | + | |
169 | y_move = False | |
170 | rotation_value = False | |
171 | clip_fps = 97 #This value gets set for each clip in the timeline | |
172 | ||
173 | seq_clip_number = 1 | |
174 | track = chosen_seqobj.getElementsByTagName('track')[0] | |
175 | - | fps = float(pathurl_clip.getElementsByTagName('timebase')[0].firstChild.data) |
175 | + | |
176 | # This loop performs the following for each clip on the first Track of the chosen Sequence. | |
177 | masterclipid = clip.getElementsByTagName('masterclipid')[0].firstChild.data | |
178 | clip_name = clip.getElementsByTagName("name")[0].firstChild.data | |
179 | - | print clip_name, in_point, out_point, clip_duration, file_path, fps |
179 | + | |
180 | out_point = int( clip.getElementsByTagName('out')[0].firstChild.data ) | |
181 | clip_duration = int( clip.getElementsByTagName("duration")[0].firstChild.data ) | |
182 | ||
183 | # Fetch the pathurl of the clip by cycling through all <pathurl> nodes and comparing the filename of the clip to the clip_name | |
184 | # This is necessary because in Premiere XMLs, the pathurl for a clip is not always stored in the clipitem node, but rather in a seperate node in the master-clip | |
185 | ||
186 | #??? Instead: Check for pathurl node in current clip node. If it doesn't exist, cycle through all | |
187 | # clipitem nodes to find another that matches the name node with the current clip_name. | |
188 | # If it finds a matching named clipitem, search for a pathurl in that node. | |
189 | try: | |
190 | file_path = clip.getElementsByTagName('pathurl')[0].firstChild.data.split("file://localhost")[1].replace("%20", " ") | |
191 | clip_fps = float(clip.getElementsByTagName('timebase')[0].firstChild.data) | |
192 | except: | |
193 | print 'Failed to get pathurl in clipitem', clip_name, '. Searching other clipitems for matching name.' | |
194 | for pathurl_clip in dom.getElementsByTagName('clipitem'): | |
195 | if pathurl_clip.getElementsByTagName('name')[0].firstChild.data == clip_name: | |
196 | try: | |
197 | file_path = pathurl_clip.getElementsByTagName('pathurl')[0].firstChild.data.split("file://localhost")[1].replace("%20", " ") | |
198 | clip_fps = float(pathurl_clip.getElementsByTagName('timebase')[0].firstChild.data) | |
199 | break | |
200 | except: | |
201 | continue | |
202 | print clip_name, in_point, out_point, clip_duration, file_path, clip_fps | |
203 | ||
204 | # Get resolution of this clip in the clipitem node, | |
205 | # Else, look for a masterclip with the same name and try to get the resolution from there, Else: fail? | |
206 | try: | |
207 | clip_width = int( clip.getElementsByTagName('width')[0].firstChild.data ) | |
208 | clip_height = int( clip.getElementsByTagName('height')[0].firstChild.data ) | |
209 | print "Clip resolution is: ", clip_width, "x", clip_height | |
210 | except: | |
211 | for masterclip in dom.getElementsByTagName('clipitem'): | |
212 | if masterclip.getElementsByTagName('name')[0].firstChild.data == clip_name: | |
213 | #!!! This is triggered if a clip is used more than once in the sequence. | |
214 | try: | |
215 | #print "found master clip: ", masterclip.getElementsByTagName('name')[0].firstChild.data, " and " | |
216 | clip_width = int( masterclip.getElementsByTagName('width')[0].firstChild.data ) | |
217 | clip_height = int( masterclip.getElementsByTagName('height')[0].firstChild.data ) | |
218 | break | |
219 | except: | |
220 | continue | |
221 | # Get all effects applied to this clip | |
222 | for effect in clip.getElementsByTagName('effect'): | |
223 | effect_name = effect.childNodes[1].firstChild.data | |
224 | if effect_name == 'Time Remap': | |
225 | # Loop through all parameters of the effect | |
226 | for param in effect.getElementsByTagName('parameter'): | |
227 | param_id = param.childNodes[1].firstChild.data | |
228 | if param_id == 'speed': | |
229 | timeremap_value = float( param.getElementsByTagName('value')[0].firstChild.data ) | |
230 | print effect_name, param_id, timeremap_value | |
231 | ||
232 | if effect_name == 'Basic Motion': | |
233 | for param in effect.getElementsByTagName('parameter'): | |
234 | param_id = param.childNodes[1].firstChild.data | |
235 | if param_id == 'scale': | |
236 | scale_value = float( param.getElementsByTagName('value')[0].firstChild.data ) | |
237 | print effect_name, param_id, scale_value | |
238 | if param_id == 'rotation': | |
239 | rotation_value = float( param.getElementsByTagName('value')[0].firstChild.data ) | |
240 | print effect_name, param_id, rotation_value | |
241 | if param_id == 'center': | |
242 | x_move = float( param.getElementsByTagName('value')[0].childNodes[1].firstChild.data ) | |
243 | y_move = float( param.getElementsByTagName('value')[0].childNodes[3].firstChild.data ) | |
244 | print effect_name, param_id, x_move, y_move | |
245 | ''' | |
246 | So.... Figuring out how Premiere handles position values: | |
247 | Prem 0-0 clip is centered upper left: value = .5,.5 | |
248 | prem 1920-1080, clip is centered lower right: value = .5 .5?? | |
249 | prem 1060-640, value: 0.052083 0.092593 | |
250 | prem center bottom: 960-1080, value: 0.0 0.5 | |
251 | prem center top: 960 0, value: 0.0 -0.5 | |
252 | prem UR 1919 0, value: 0.499479 -0.5 | |
253 | ||
254 | location x, y | |
255 | center 0, 0 | |
256 | UL -0.5, -0.5 | |
257 | UR 0.5, -0.5 | |
258 | LR 0.5, 0.5 | |
259 | LL 0, 0.5 | |
260 | y-up is negative | |
261 | x-right is positive | |
262 | The range from edge to edge of sequence space is 1. | |
263 | -.5 is left, .5 is right. | |
264 | -.5 is up, .5 is down. | |
265 | .125, 0 would be 1200x540 = (seq_res_x * x_move) = how many pixels to move from center) = 1920*.125 + 1920/2 = 1200 | |
266 | for x: seq_res_x * x_move | |
267 | for y: seq_res_y * -y_move | |
268 | ''' | |
269 | ||
270 | # Gets the shot name, which is the formatted clip_name with clip# and reel#, with the sequence clipnumber. | |
271 | # uses camera type (Red, Alexa, etc), and the clip_name string (the filename of the clip used in FCP) | |
272 | # Also takes the seq_clip_number for returning the correct shot_name (the name that will be used to name the nuke script) | |
273 | if clip_name_format == 'RED': | |
274 | # This works for Red footage of format: A###_C###_RANDOMDATA | |
275 | reel_number = clip_name.split('_')[0][1:] | |
276 | clip_number = clip_name.split('_')[1][1:] | |
277 | ||
278 | if clip_name_format == 'Alexa': | |
279 | # Alexa footage is A###C###_######_R#### | |
280 | reel_number = int(clip_name.split('C')[0][1:]) | |
281 | clip_number = int( clip_name.split('C')[1].split('_')[0] ) | |
282 | ||
283 | if clip_name_format == 'Bypass': | |
284 | shot_name = "%02d0_%s" %(seq_clip_number, os.path.splitext(clip_name)[0]) | |
285 | else: | |
286 | # shot_name is the string that defines the name that the nuke script is saved to. seq_clip_number+0_A{reelnumber}_C{clipnumber} | |
287 | shot_name = "%02d0_A%sC%s" %(seq_clip_number, reel_number, clip_number) | |
288 | ||
289 | ||
290 | ############################ | |
291 | # Build Nuke Script | |
292 | ############################ | |
293 | ||
294 | # if the subdirectories checkbox is checked, set the output_shotdir to be a subdirectory named with the shot_name | |
295 | if subdirectories: | |
296 | output_shotdir = output_dir | |
297 | output_shotdir = os.path.join(output_dir, shot_name) | |
298 | else: | |
299 | output_shotdir = output_dir | |
300 | # If the output_shotdir does not exist, create it (auto-creates subdirectories) | |
301 | if not os.path.isdir(output_shotdir): | |
302 | os.mkdir(output_shotdir) | |
303 | ||
304 | ||
305 | ########################### | |
306 | # Compute values to plug into the Nuke Script | |
307 | ||
308 | # Compute Handles and set first_frame and last_frame | |
309 | first_frame = in_point - handle_length | |
310 | last_frame = out_point-1 + handle_length | |
311 | ||
312 | if timeremap_value: | |
313 | ''' | |
314 | The XML gives us the duration of the original clip, and the in and out points of the retimed clip | |
315 | - | nuke_script.write('''Root {{\n inputs 0\n name \"{0}\"\n project_directory \"\[python \{{nuke.script_directory()\}}]\"\n first_frame {1}\n last_frame {2}\n fps {6}\n format \"{3} {4} 0 0 {3} {4} 1 {5}\"\n proxy_type scale\n}}\n'''.format(nuke_file, first_frame, last_frame, seq_res_x, seq_res_y, fcp_xml_resolution, fps)) |
315 | + | |
316 | originalOut = newOut * retime | |
317 | new duration = lastFrame - first_frame | |
318 | ''' | |
319 | timeremap_value = timeremap_value/100 | |
320 | new_clip_duration = last_frame - first_frame | |
321 | clip_duration = clip_duration * timeremap_value | |
322 | first_frame = first_frame * timeremap_value | |
323 | last_frame = first_frame + new_clip_duration | |
324 | ||
325 | ||
326 | # Set Format | |
327 | if seq_res_x == 1920 and seq_res_y == 1080: | |
328 | fcp_xml_resolution = 'HD' | |
329 | else: | |
330 | fcp_xml_resolution = 'from_xml' | |
331 | ||
332 | ||
333 | #!!! This creates a nuke script by appending text to the .nk file instead of using nuke.nodeCopy(), which is slow and messy, and so that root node settings can be added. | |
334 | # The strings that are written are triple-quoted. newlines are created with '\n'. {} chars in the string have to be doubled so as not to throw a KeyError | |
335 | nuke_file = os.path.join(output_shotdir, "%s_v001.nk"%(shot_name)) | |
336 | nuke_script = open(nuke_file, 'a+') | |
337 | # Create Root node | |
338 | nuke_script.write('''Root {{\n inputs 0\n name \"{0}\"\n project_directory \"\[python \{{nuke.script_directory()\}}]\"\n lock_range true\n first_frame {1}\n last_frame {2}\n fps {6}\n format \"{3} {4} 0 0 {3} {4} 1 {5}\"\n proxy_type scale\n}}\n'''.format(nuke_file, first_frame, last_frame, seq_res_x, seq_res_y, fcp_xml_resolution, clip_fps)) | |
339 | # Create Read node | |
340 | nuke_script.write('''Read {{\n inputs 0\n file \"{0}\"\n first 0\n last {1}\n frame_mode offset\n frame 1\n origlast {1}\n origset true\n name Read1\n selected true\n xpos -425\n ypos -40\n}}\n'''.format(file_path, clip_duration)) | |
341 | # Create TimeRemap if there is retiming on the clip | |
342 | if timeremap_value: | |
343 | nuke_script.write(''' | |
344 | Text { | |
345 | message "\[frame]" | |
346 | font /Library/Fonts/Arial.ttf | |
347 | yjustify center | |
348 | box {480 270 1440 810} | |
349 | translate {1314 -498} | |
350 | center {960 540} | |
351 | name FrameNumber | |
352 | selected true | |
353 | xpos -425 | |
354 | ypos 42 | |
355 | } | |
356 | ''') | |
357 | nuke_script.write('''Group {{ | |
358 | name RetimeFromFrame | |
359 | selected true | |
360 | addUserKnob {{20 Retime t "Retime From Frame Parameters"}} | |
361 | addUserKnob {{41 StartFrame l "SourceStart Frame" t "The source frame from which retiming starts. For example, if you have a clip that you are using a range from frames 200-300 in, and you want to retime that clip to be 50\% speed, you would set this to be 200. \\n\\nThis gizmo references the root.first_frame value to determine the \\\"in-point\\\" of the clip." T RetimeControls.StartFrame}} | |
362 | addUserKnob {{41 PlaybackSpeed l "Playback Speed" t "Retime speed as a fraction of one. That is, 0.5 = 50\% speed, 2 = 200\% speed." T RetimeControls.PlaybackSpeed}} | |
363 | }} | |
364 | Input {{ | |
365 | inputs 0 | |
366 | name Input1 | |
367 | xpos 0 | |
368 | }} | |
369 | TimeOffset {{ | |
370 | time_offset {{{{-RetimeControls.StartFrame/RetimeScreen.timingSpeed}}}} | |
371 | name Retime_TimeOffset | |
372 | tile_color 0xff0000ff | |
373 | xpos 0 | |
374 | ypos 132 | |
375 | }} | |
376 | OFXuk.co.thefoundry.time.oflow_v100 {{ | |
377 | method Motion | |
378 | timing Speed | |
379 | timingFrame 1 | |
380 | timingSpeed {{{{RetimeControls.PlaybackSpeed}}}} | |
381 | filtering Normal | |
382 | warpMode Normal | |
383 | correctLuminance false | |
384 | automaticShutterTime false | |
385 | shutterTime 0 | |
386 | shutterSamples 1 | |
387 | vectorDetail 0.2 | |
388 | smoothness 0.5 | |
389 | blockSize 6 | |
390 | Tolerances 0 | |
391 | weightRed 0.3 | |
392 | weightGreen 0.6 | |
393 | weightBlue 0.1 | |
394 | showVectors false | |
395 | cacheBreaker false | |
396 | name RetimeScreen | |
397 | tile_color 0xff0000ff | |
398 | selected true | |
399 | xpos 0 | |
400 | ypos 156 | |
401 | }} | |
402 | TimeOffset {{ | |
403 | time_offset {{{{root.first_frame}}}} | |
404 | name GlobalStart_Offset | |
405 | tile_color 0xff0000ff | |
406 | xpos 0 | |
407 | ypos 180 | |
408 | }} | |
409 | Output {{ | |
410 | name Output1 | |
411 | xpos 0 | |
412 | ypos 393 | |
413 | }} | |
414 | NoOp {{ | |
415 | inputs 0 | |
416 | name RetimeControls | |
417 | xpos -174 | |
418 | ypos 126 | |
419 | addUserKnob {{20 User}} | |
420 | addUserKnob {{7 PlaybackSpeed l "Playback Speed" R 0 100}} | |
421 | PlaybackSpeed {0} | |
422 | addUserKnob {{3 StartFrame l "Start Frame" t "Offset video start frame"}} | |
423 | StartFrame {1} | |
424 | }} | |
425 | end_group\n'''.format( timeremap_value, first_frame )) | |
426 | ||
427 | # Create FrameRange node | |
428 | nuke_script.write('''FrameRange {{\n first_frame {0}\n last_frame {1}\n name FrameRange1\n label "\\[knob first_frame]-\\[knob last_frame]"\n selected true\n}}\n'''.format(first_frame, last_frame)) | |
429 | ||
430 | # Create a Transform node with pans and scales, if they exist | |
431 | if scale_value or x_move or y_move or rotation_value: | |
432 | if not scale_value: | |
433 | scale_value = 100 | |
434 | if not x_move: | |
435 | x_move = 0 | |
436 | - | nuke_script.write('''Write {{\n file \"{0}\"\n file_type mov\n codec apch\n fps 23.976\n checkHashOnRead false\n name Write1\n selected true\n}}\n'''.format('{0}_v001.mov'.format(os.path.join(render_dir, shot_name)) ) ) |
436 | + | |
437 | y_move = 0 | |
438 | if not rotation_value: | |
439 | rotation_value = 0 | |
440 | # Create a reformat node if there are pans or scales | |
441 | nuke_script.write('''Reformat {{ | |
442 | resize none | |
443 | black_outside true | |
444 | name Reformat1 | |
445 | selected true | |
446 | }} | |
447 | '''.format()) | |
448 | nuke_script.write( '''Transform {{ | |
449 | translate {{{0} {1}}} | |
450 | rotate {2} | |
451 | scale {3} | |
452 | center {{{4} {5}}} | |
453 | name Transform1 | |
454 | selected true | |
455 | }} | |
456 | '''.format( (seq_res_x * x_move), (seq_res_y * -y_move), -rotation_value, scale_value/100, seq_res_x/2, seq_res_y/2 ) ) | |
457 | ||
458 | # Create Write node | |
459 | render_shot_dir = os.path.join(render_dir, shot_name) | |
460 | if render_format == 'Prores4444': | |
461 | nuke_script.write('''Write {{\n file \"{0}\"\n file_type mov\n codec ap4h\n fps {1}\n checkHashOnRead false\n name Write1\n selected true\n}}\n'''.format(render_shot_dir+'_v001.mov', clip_fps)) | |
462 | elif render_format == 'exr': | |
463 | nuke_script.write('''Write {{\n file \"{0}\"\n file_type exr\n "Standard layer name format" true\n name Write1\n selected true\n}}\n'''.format(render_shot_dir+'/'+shot_name+'_v001.####.exr')) | |
464 | elif render_format == 'jpg': | |
465 | nuke_script.write('''Write {{\n file \"{0}\"\n file_type jpg\n _jpeg_quality 0.9\n _jpeg_sub_sampling 4:2:2\n checkHashOnRead false\n name Write1\n selected true\n}}\n'''.format(render_shot_dir+'/'+shot_name+'_v001.####.jpg')) | |
466 | elif render_format == 'dpx': | |
467 | nuke_script.write('''Write {{\n file \"{0}\"\n file_type dpx\n checkHashOnRead false\n name Write1\n selected true\n}}\n'''.format(render_shot_dir+'/'+shot_name+'_v001.####.dpx')) | |
468 | ||
469 | # Create Viewer node | |
470 | nuke_script.write('''Viewer {\n name Viewer1\n selected true\n}\n''') | |
471 | # Create Backdrop node | |
472 | nuke_script.write('''BackdropNode {{\n inputs 0\n name BackdropNode1\n tile_color 0x26434dff\n label \"<img src=\\\"Read.png\\\"> Read Plate <br/><font size=1> {0} <br/> {1}-{2}<br/>{3} frame handles\"\n note_font_size 30\n selected true\n xpos -500\n ypos -150\n bdwidth 234\n bdheight 254\n}}\n'''.format(shot_name, in_point, out_point, handle_length)) | |
473 | ||
474 | seq_clip_number += 1 | |
475 | # Reset option effect parameters to false for next clip iteration | |
476 | timeremap_value = False | |
477 | scale_value = False | |
478 | x_move = False | |
479 | y_move = False | |
480 | rotation_value = False | |
481 | ### End of for loop which processes each clip in timeline. | |
482 | ||
483 | ||
484 | nuke.message('All clips processed successfully!') | |
485 | return | |
486 | ||
487 | ||
488 | ||
489 | ''' | |
490 | ### ??? ALTERNATIVE METHOD FOR CREATING THE SCRIPT FILES | |
491 | ### This approach uses the script that import_xml is executed from as a base for creating nodes, and then using nuke.nodeCopy() to 'paste' the data into each script file. | |
492 | ### The approach I ended up using instead just uses python to format .nk scripts as text, inputting the variables where relevant. It is much faster and more efficient. | |
493 | for node in nuke.allNodes(): | |
494 | node.setSelected(True) | |
495 | nuke.nodeDelete() | |
496 | ||
497 | # Create Read node | |
498 | read = nuke.createNode("Read") | |
499 | read.knob("file").setValue(file_path) | |
500 | read.knob("frame_mode").setValue('offset') | |
501 | read.knob("frame").setValue('1') | |
502 | read.knob("first").setValue(0) | |
503 | read.knob("last").setValue(clip_duration) | |
504 | ||
505 | # Create a NoOp node to hold shot info | |
506 | #!!! This is not needed | |
507 | #shotInfo = nuke.createNode('NoOp') | |
508 | #shotInfo.knob('name').setValue(shot_name+"_info") | |
509 | #shotInfo.addKnob( nuke.String_Knob("The original name of the clip", "clip_name", clip_name) ) | |
510 | #shotInfo.addKnob( nuke.String_Knob("Base name of the nuke script", "shot_name", shot_name) ) | |
511 | ||
512 | # Create FrameRange node | |
513 | frame_range = nuke.createNode("FrameRange") | |
514 | frame_range.knob('label').setValue('[knob first_frame]-[knob last_frame]') | |
515 | frame_range.knob('first_frame').setValue( in_point - handle_length ) | |
516 | frame_range.knob('last_frame').setValue( out_point-1 + handle_length ) | |
517 | ||
518 | # Create Write node | |
519 | write = nuke.createNode("Write") | |
520 | write.knob('file').setValue('{0}{1}_v001.mov'.format(render_dir, shot_name)) | |
521 | #write.knob("file_type").setValue("mov") | |
522 | #write.knob("mov.codec").setValue("apch") | |
523 | #write.knob("mov.fps").setValue("23.976") | |
524 | ||
525 | # Create Viewer Node | |
526 | nuke.createNode('Viewer') | |
527 | ||
528 | # Informational Backdrop Node | |
529 | bd_node = nuke.createNode("BackdropNode") | |
530 | bd_node.knob("tile_color").setValue(0x26434dff) | |
531 | bd_node.knob("note_font_size").setValue(30) | |
532 | bd_node.knob("bdwidth").setValue(234) | |
533 | bd_node.knob("bdheight").setValue(254) | |
534 | bd_node.knob("label").setValue('<img src=\"Read.png\"> Read Plate <br/><font size=1> %s <br/> %s-%s'%(shot_name,int(in_point),int(out_point))) | |
535 | ||
536 | # Set root script values | |
537 | #nuke.toNode('root').knob('project_directory').setValue('[python {nuke.script_directory()}]') | |
538 | #nuke.toNode('root').knob('first_frame').setValue( in_point-handle_length) | |
539 | #nuke.toNode('root').knob('first_frame').setValue( (out_point-1) + handle_length) | |
540 | #nuke.toNode('root').knob('format').setValue('HD') | |
541 | ||
542 | # Select all created nodes and copy them into a new script | |
543 | for node in nuke.allNodes(): | |
544 | node.setSelected(True) | |
545 | nuke.nodeCopy(nuke_file) | |
546 | ''' |