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