View difference between Paste ID: UuBcmJcF and ry7Spm5x
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
		'''