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 | ''' |