def buildScript(self): """ Override the default buildScript functionality to also output a temp movie file if needed for uploading to Shotgun """ # This is a bit of a hack to account for some changes to the # transcode exporter that ships with Nuke/Hiero 9.0 compared # to earlier versions of Hiero. file_type = self._preset.properties()["file_type"] self.app.log_debug("Transcode export file_type: %s" % file_type) if file_type in ["mov", "ffmpeg"]: if not self._preset.properties()[file_type].get("encoder"): encoder_name = self.app.get_default_encoder_name() self._preset.properties()[file_type]["encoder"] = encoder_name # Build the usual script using the base code FnTranscodeExporter.TranscodeExporter.buildScript(self) self.app.log_debug("Transcode base script built") # If we are not creating a version then we do not need the extra node if not self._preset.properties()["create_version"]: return if file_type in ["mov", "ffmpeg"]: # already outputting a mov file, use that for upload self._quicktime_path = self.resolvedExportPath() self._temp_quicktime = False return self._quicktime_path = os.path.join(tempfile.mkdtemp(), "preview.mov") self._temp_quicktime = True nodeName = "SG Screening Room Media" framerate = None if self._sequence: framerate = self._sequence.framerate() if self._clip.framerate().isValid(): framerate = self._clip.framerate() preset = FnTranscodeExporter.TranscodePreset( "Qt Write", self._preset.properties() ) # insert the write node to generate the quicktime file_type, properties = self.app.execute_hook( "hook_get_quicktime_settings", for_shotgun=True, base_class=HieroGetQuicktimeSettings, ) self.app.log_info("Transcode quicktime settings: %s" % (properties,)) preset.properties().update( {"file_type": file_type, file_type: properties,} ) # Sadly Foundry has a habit of changing the interfaces of # their Python classes out from under us, so now we're going # to have to handle this the ugly way, via introspecting the # arguments expected by the createWriteNode method. arg_spec = inspect.getargspec(FnExternalRender.createWriteNode) if "projectsettings" in arg_spec.args: kwargs = dict( path=self._quicktime_path, preset=preset, nodeName=nodeName, framerate=framerate, projectsettings=self._projectSettings, ) elif "ctx" in arg_spec.args: kwargs = dict( ctx=self, path=self._quicktime_path, preset=preset, nodeName=nodeName, framerate=framerate, project=self._project, ) else: kwargs = dict( path=self._quicktime_path, preset=preset, nodeName=nodeName, framerate=framerate, project=self._project, ) mov_write_node = FnExternalRender.createWriteNode(**kwargs) # We create a push node and connect it to the set node we created just before the base write node was created # This means that our write node will parent to the same node the base write node gets parented to. push_command = nuke.PushNode(self._write_set_node_label) self._script.addNode(push_command) self._script.addNode(mov_write_node)
def _taskStep(self): hiero.core.TaskBase.taskStep(self) if self._nothingToDo: return False script = nuke.ScriptWriter() start, end = self.outputRange(ignoreRetimes=True, clampToSource=False) unclampedStart = start hiero.core.log.debug("rootNode range is %s %s %s", start, end, self._startFrame) firstFrame = start if self._startFrame is not None: firstFrame = self._startFrame # if startFrame is negative we can only assume this is intentional if start < 0 and (self._startFrame is None or self._startFrame >= 0): # We dont want to export an image sequence with negative frame numbers self.setWarning( "%i Frames of handles will result in a negative frame index.\nFirst frame clamped to 0." % self._cutHandles) start = 0 firstFrame = 0 framerate = self._sequence.framerate() dropFrames = self._sequence.dropFrame() if self._clip and self._clip.framerate().isValid(): framerate = self._clip.framerate() dropFrames = self._clip.dropFrame() fps = framerate.toFloat() showAnnotations = False # Create the root node, this specifies the global frame range and frame rate rootNode = nuke.RootNode(start, end, fps, showAnnotations) rootNode.addProjectSettings(self._projectSettings) dailiesScriptCheck = 'writeNodeManager.checkDailiesTab()' rootNode.setKnob("onScriptLoad", dailiesScriptCheck) script.addNode(rootNode) if isinstance(self._item, hiero.core.TrackItem): rootNode.addInputTextKnob( "shot_guid", value=hiero.core.FnNukeHelpers._guidFromCopyTag(self._item), tooltip= "This is used to identify the master track item within the script", visible=False) inHandle, outHandle = self.outputHandles(self._retime != True) rootNode.addInputTextKnob("in_handle", value=int(inHandle), visible=False) rootNode.addInputTextKnob("out_handle", value=int(outHandle), visible=False) # Set the format knob of the root node rootNode.setKnob("format", str(self.rootFormat())) # BUG 40367 - proxy_type should be set to 'scale' by default to reflect # the custom default set in Nuke. Sadly this value can't be queried, # as it's set by nuke.knobDefault, hence the hard coding. rootNode.setKnob("proxy_type", "scale") # Project setting for using OCIO nodes for colourspace transform useOCIONodes = self._project.lutUseOCIOForExport() # A dict of arguments which are used when calling addToNukeScript on any clip/sequence/trackitem addToScriptCommonArgs = {'useOCIO': useOCIONodes} writeNodes = self._createWriteNodes(firstFrame, start, end, framerate, rootNode) if not writeNodes: # Blank preset is valid, if preset has been set and doesn't exist, report as error self.setWarning( str("NukeShotExporter: No write node destination selected")) if self.writingSequence(): self.writeSequence(script, addToScriptCommonArgs) # Write out the single track item else: self.writeTrackItem(script, addToScriptCommonArgs, firstFrame) script.pushLayoutContext("write", "%s_Render" % self._item.name()) metadataNode = nuke.MetadataNode(metadatavalues=[( "hiero/project", self._projectName), ( "hiero/project_guid", self._project.guid()), ("hiero/shot_tag_guid", self._tag_guid)]) # Add sequence Tags to metadata metadataNode.addMetadataFromTags(self._sequence.tags()) metadataNode.addMetadata([("hiero/sequence", self._sequence.name())]) metadataNode.addMetadata([("hiero/shot", self._clip.name())]) # Apply timeline offset to nuke output if isinstance(self._item, hiero.core.TrackItem): if self._cutHandles is None: # Whole clip, so timecode start frame is first frame of clip timeCodeNodeStartFrame = unclampedStart else: startHandle, endHandle = self.outputHandles() timeCodeNodeStartFrame = trackItemTimeCodeNodeStartFrame( unclampedStart, self._item, startHandle, endHandle) timecodeStart = self._clip.timecodeStart() else: # Exporting whole sequence/clip timeCodeNodeStartFrame = unclampedStart timecodeStart = self._item.timecodeStart() script.addNode( nuke.AddTimeCodeNode(timecodeStart=timecodeStart, fps=framerate, dropFrames=dropFrames, frame=timeCodeNodeStartFrame)) # The AddTimeCode field will insert an integer framerate into the metadata, if the framerate is floating point, we need to correct this metadataNode.addMetadata([("input/frame_rate", framerate.toFloat())]) script.addNode(metadataNode) # Add the delivery gizmo deliveryNode = nuke.Node('delivery') writeNodes.append(nuke.PushNode("ScriptEnd")) writeNodes.append(deliveryNode) # Generate Write nodes for nuke renders. # Bug 45843 - Branch the viewer when using OCIO nodes so # the OCIOColorSpace node created for the writer doesn't # get used when rendering the viewer. branchViewer = useOCIONodes if branchViewer: branchStackId = "OCIOWriterBranch" script.addNode(nuke.SetNode(branchStackId, 0)) assert (isinstance(writeNodes[-1], nuke.WriteNode)) ocioNodes = [ node for node in writeNodes[-1].getNodes() if node.type() == "OCIOColorSpace" ] assert (len(ocioNodes)) ocioNode = ocioNodes[0] ocioNode.setAlignToNode(metadataNode) scriptFilename = self.resolvedExportPath() for node in writeNodes: if node.knobs().has_key('file_type'): if node.knob('file_type') == 'mov': slateNode = nuke.Node('slate') script.addNode(slateNode) node.setInputNode(0, metadataNode) script.addNode(node) if branchViewer: script.addNode(nuke.PushNode(branchStackId)) # add a viewer viewerNode = nuke.Node("Viewer") # Bug 45914: If the user has for some reason selected a custom OCIO config, but then set the 'Use OCIO nodes when export' option to False, # don't set the 'viewerProcess' knob, it's referencing a LUT in the OCIO config which Nuke doesn't know about setViewerProcess = True if not self._projectSettings[ 'lutUseOCIOForExport'] and self._projectSettings[ 'ocioConfigPath']: setViewerProcess = False if setViewerProcess: # Bug 45845 - default viewer lut should be set in the comp viewerLut = _toNukeViewerLutFormat( self._projectSettings['lutSettingViewer']) viewerNode.setKnob("viewerProcess", viewerLut) script.addNode(viewerNode) hiero.core.log.debug("Writing Script to: %s", scriptFilename) script.popLayoutContext() # Layout the script #hiero.exporters.FnScriptLayout.scriptLayout(script) script.writeToDisk(scriptFilename) # Nothing left to do, return False. return False
def _createWriteNodes(self, firstFrame, start, end, framerate, rootNode): # To add Write nodes, we get a task for the paths with the preset # (default is the "Nuke Write Node" preset) and ask it to generate the Write node for # us, since it knows all about codecs and extensions and can do the token # substitution properly for that particular item. # And doing it here rather than in taskStep out of threading paranoia. writeNodes = [] firstWriteNode = True # Create a stack to prevent multiple write nodes inputting into each other stackId = "ScriptEnd" writeNodes.append(nuke.SetNode(stackId, 0)) writePaths = self._preset.properties()["writePaths"] for (itemPath, itemPreset) in self._exportTemplate.flatten(): for writePath in writePaths: if writePath == itemPath: # Generate a task on same items as this one but swap in the shot path that goes with this preset. taskData = hiero.core.TaskData( itemPreset, self._item, self._exportRoot, itemPath, self._version, self._exportTemplate, project=self._project, cutHandles=self._cutHandles, retime=self._retime, startFrame=firstFrame, resolver=self._resolver, skipOffline=self._skipOffline, shotNameIndex=self._shotNameIndex) task = hiero.core.taskRegistry.createTaskFromPreset( itemPreset, taskData) if hasattr(task, "nukeWriteNode"): # Push to the stack before adding the write node writeNodes.append(nuke.PushNode(stackId)) try: reformatNode = reformatNodeFromPreset(itemPreset) if reformatNode: writeNodes.append(reformatNode) except Exception as e: self.setError(str(e)) # Add Burnin group (if enabled) burninGroup = task.addBurninNodes(script=None) if burninGroup is not None: writeNodes.append(burninGroup) try: writeNode = task.nukeWriteNode( framerate, project=self._project) writeNode.setKnob("first", start) writeNode.setKnob("last", end) if writeNode.knob('file_type') == 'mov': writeNode.setKnob( 'file', "[python writeNodeManager.setOutputPath('mov')]" ) writeNode.setKnob( 'afterRender', 'ftrackUpload.uploadToFtrack()') else: writeNode.setKnob( 'file', "[python writeNodeManager.setOutputPath('img')]" ) writeNodes.append(writeNode) # Set the first write node in the list as the one to be shown/rendered in the timeline if firstWriteNode: firstWriteNode = False rootNode.setKnob( nuke.RootNode.kTimelineWriteNodeKnobName, writeNode.knob("name")) except RuntimeError as e: # Failed to generate write node, set task error in export queue # Most likely because could not map default colourspace for format settings. self.setError(str(e)) return writeNodes
def taskStep(self): FnShotExporter.ShotTask.taskStep(self) if self._nothingToDo: return False eventManager = FnAssetAPI.Events.getEventManager() script = nuke.ScriptWriter() start, end = self.outputRange(ignoreRetimes=True, clampToSource=False) unclampedStart = start hiero.core.log.debug("rootNode range is %s %s %s", start, end, self._startFrame) firstFrame = start if self._startFrame is not None: firstFrame = self._startFrame # if startFrame is negative we can only assume this is intentional if start < 0 and (self._startFrame is None or self._startFrame >= 0): # We dont want to export an image sequence with negative frame numbers self.setWarning( "%i Frames of handles will result in a negative frame index.\nFirst frame clamped to 0." % self._cutHandles) start = 0 # Clip framerate may be invalid, then use parent sequence framerate framerate = self._sequence.framerate() dropFrames = self._sequence.dropFrame() if self._clip and self._clip.framerate().isValid(): framerate = self._clip.framerate() dropFrames = self._clip.dropFrame() fps = framerate.toFloat() # Create the root node, this specifies the global frame range and frame rate rootNode = nuke.RootNode(start, end, fps) rootNode.addProjectSettings(self._projectSettings) #rootNode.setKnob("project_directory", os.path.split(self.resolvedExportPath())[0]) script.addNode(rootNode) reformat = None # Set the root node format to default to source format if isinstance(self._item, hiero.core.Sequence) or self._collate: reformat = self._sequence.format().addToNukeScript(None) elif isinstance(self._item, hiero.core.TrackItem): reformat = self._clip.format().addToNukeScript(None) if isinstance(self._item, hiero.core.TrackItem): rootNode.addInputTextKnob( "shot_guid", value=hiero.core.FnNukeHelpers._guidFromCloneTag(self._item), tooltip= "This is used to identify the master track item within the script" ) inHandle, outHandle = self.outputHandles(self._retime != True) rootNode.addInputTextKnob("in_handle", value=int(inHandle)) rootNode.addInputTextKnob("out_handle", value=int(outHandle)) # This sets the format knob of the root node in the Nuke Script rootReformat = None if isinstance(self._item, hiero.core.Sequence) or self._collate: rootReformat = self._sequence.format().addToNukeScript(None) elif isinstance(self._item, hiero.core.TrackItem): rootReformat = self._item.parentSequence().format( ).addToNukeScript(None) rootNode.setKnob("format", rootReformat.knob("format")) # Add Unconnected additional nodes if self._preset.properties()["additionalNodesEnabled"]: script.addNode( FnExternalRender.createAdditionalNodes( FnExternalRender.kUnconnected, self._preset.properties()["additionalNodesData"], self._item)) # Project setting for using OCIO nodes for colourspace transform useOCIONodes = self._project.lutUseOCIOForExport() # To add Write nodes, we get a task for the paths with the preset # (default is the "Nuke Write Node" preset) and ask it to generate the Write node for # us, since it knows all about codecs and extensions and can do the token # substitution properly for that particular item. # And doing it here rather than in taskStep out of threading paranoia. self._writeNodes = [] stackId = "ScriptEnd" self._writeNodes.append(nuke.SetNode(stackId, 0)) writePathExists = False writePaths = self._preset.properties()["writePaths"] for (itemPath, itemPreset) in self._exportTemplate.flatten(): for writePath in writePaths: if writePath == itemPath: # Generate a task on same items as this one but swap in the shot path that goes with this preset. taskData = hiero.core.TaskData( itemPreset, self._item, self._exportRoot, itemPath, self._version, self._exportTemplate, project=self._project, cutHandles=self._cutHandles, retime=self._retime, startFrame=firstFrame, resolver=self._resolver, skipOffline=self._skipOffline) task = hiero.core.taskRegistry.createTaskFromPreset( itemPreset, taskData) if hasattr(task, "nukeWriteNode"): self._writeNodes.append(nuke.PushNode(stackId)) rf = itemPreset.properties()["reformat"] # If the reformat field has been set, create a reformat node immediately before the Write. if str(rf["to_type"]) == nuke.ReformatNode.kToFormat: if "width" in rf and "height" in rf and "pixelAspect" in rf and "name" in rf and "resize" in rf: format = hiero.core.Format( rf["width"], rf["height"], rf["pixelAspect"], rf["name"]) resize = rf["resize"] reformat = format.addToNukeScript( None, resize=resize) self._writeNodes.append(reformat) else: self.setError( "reformat mode set to kToFormat but preset properties do not contain required settings." ) # Add Burnin group (if enabled) burninGroup = task.addBurninNodes(script=None) if burninGroup is not None: self._writeNodes.append(burninGroup) try: writeNode = task.nukeWriteNode( framerate, projectsettings=self._projectSettings) writeNode.setKnob("first", start) writeNode.setKnob("last", end) self._writeNodes.append(writeNode) except RuntimeError as e: # Failed to generate write node, set task error in export queue # Most likely because could not map default colourspace for format settings. self.setError(str(e)) writePathExists = True # MPLEC TODO should enforce in UI that you can't pick things that won't work. if not writePaths: # Blank preset is valid, if preset has been set and doesn't exist, report as error self.setWarning( str("NukeShotExporter: No write node destination selected")) # If this flag is True, a read node pointing at the original media will be added # If read nodes which point at export items are selected, this flag will be set False originalMediaReadNode = True useEntityRefs = self._preset.properties().get("useAssets", False) for item in [self._item]: originalMediaReadNode = True if not self._collate: # Build read nodes for selected entries in the shot template readPaths = self._preset.properties()["readPaths"] for (itemPath, itemPreset) in self._exportTemplate.flatten(): for readPath in readPaths: if itemPath == readPath: # Generate a task on same items as this one but swap in the shot path that goes with this preset. taskData = hiero.core.TaskData( itemPreset, item, self._exportRoot, itemPath, self._version, self._exportTemplate, project=self._project, cutHandles=self._cutHandles, retime=self._retime, startFrame=self._startFrame, resolver=self._resolver, skipOffline=self._skipOffline) task = hiero.core.taskRegistry.createTaskFromPreset( itemPreset, taskData) readNodePath = task.resolvedExportPath() itemStart, itemEnd = task.outputRange() itemFirstFrame = firstFrame if self._startFrame: itemFirstFrame = self._startFrame if hiero.core.isVideoFileExtension( os.path.splitext(readNodePath)[1].lower()): # Don't specify frame range when media is single file newSource = hiero.core.MediaSource( readNodePath) itemEnd = itemEnd - itemStart itemStart = 0 else: # File is image sequence, so specify frame range newSource = hiero.core.MediaSource( readNodePath + (" %i-%i" % task.outputRange())) newClip = hiero.core.Clip(newSource, itemStart, itemEnd) originalMediaReadNode = False if self._cutHandles is None: newClip.addToNukeScript( script, firstFrame=itemFirstFrame, trimmed=True, useOCIO=useOCIONodes, additionalNodesCallback=self. _buildAdditionalNodes, nodeLabel=item.parent().name(), useEntityRefs=useEntityRefs) else: # Clone track item and replace source with new clip (which may be offline) newTrackItem = hiero.core.TrackItem( item.name(), item.mediaType()) for tag in self._item.tags(): newTrackItem.addTag(tag) # Handles may not be exactly what the user specified. They may be clamped to media range inHandle, outHandle = 0, 0 if self._cutHandles: # Get the output range without handles inHandle, outHandle = task.outputHandles() hiero.core.log.debug( "in/outHandle %s %s", inHandle, outHandle) newTrackItem.setSource(newClip) # Trackitem in/out newTrackItem.setTimelineIn(item.timelineIn()) newTrackItem.setTimelineOut(item.timelineOut()) # Source in/out is the clip range less the handles. newTrackItem.setSourceIn(inHandle * -1) newTrackItem.setSourceOut((newClip.duration() - 1) - outHandle) #print "New trackitem (src/dst/clip) ", newTrackItem.sourceDuration(), newTrackItem.duration(), newClip.duration() # Add track item to nuke script newTrackItem.addToNukeScript( script, firstFrame=itemFirstFrame, includeRetimes=self._retime, retimeMethod=self._preset.properties() ["method"], startHandle=self._cutHandles, endHandle=self._cutHandles, additionalNodesCallback=self. _buildAdditionalNodes, useOCIO=useOCIONodes, nodeLabel=item.parent().name(), useEntityRefs=useEntityRefs) if originalMediaReadNode: if isinstance(self._item, hiero.core.Sequence) or self._collate: # When building a collated sequence, everything is offset by 1000 # This gives head room for shots which may go negative when transposed to a # custom start frame. This offset is negated during script generation. offset = -1000 if self._collate else 0 self._sequence.addToNukeScript( script, additionalNodesCallback=self._buildAdditionalNodes, includeRetimes=True, offset=offset, useOCIO=useOCIONodes, skipOffline=self._skipOffline, useEntityRefs=useEntityRefs) elif isinstance(self._item, hiero.core.TrackItem): clip = item.source() # Add a Read Node for this Clip. if self._cutHandles is None: clip.addToNukeScript( script, firstFrame=firstFrame, trimmed=True, additionalNodesCallback=self._buildAdditionalNodes, useOCIO=useOCIONodes, nodeLabel=item.parent().name(), useEntityRefs=useEntityRefs) else: item.addToNukeScript( script, firstFrame=firstFrame, includeRetimes=self._retime, retimeMethod=self._preset.properties()["method"], startHandle=self._cutHandles, endHandle=self._cutHandles, additionalNodesCallback=self._buildAdditionalNodes, useOCIO=useOCIONodes, nodeLabel=item.parent().name(), useEntityRefs=useEntityRefs) metadataNode = nuke.MetadataNode(metadatavalues=[( "hiero/project", self._projectName), ( "hiero/project_guid", self._project.guid()), ("hiero/shot_tag_guid", self._tag_guid)]) # Add sequence Tags to metadata metadataNode.addMetadata([('"hiero/tags/' + tag.name() + '"', tag.name()) for tag in self._sequence.tags()]) metadataNode.addMetadata([('"hiero/tags/' + tag.name() + '/note"', tag.note()) for tag in self._sequence.tags()]) # Apply timeline offset to nuke output if isinstance(self._item, hiero.core.TrackItem): if self._cutHandles is None: # Whole clip, so timecode start frame is first frame of clip timeCodeNodeStartFrame = unclampedStart else: # Exporting shot with handles, adjust timecode start frame by shot trim and handle count timeCodeNodeStartFrame = ( unclampedStart - self._item.sourceIn()) + self._cutHandles timecodeStart = self._clip.timecodeStart() else: # Exporting whole sequence/clip timeCodeNodeStartFrame = unclampedStart timecodeStart = self._item.timecodeStart() script.addNode( nuke.AddTimeCodeNode(timecodeStart=timecodeStart, fps=framerate, dropFrames=dropFrames, frame=timeCodeNodeStartFrame)) # The AddTimeCode field will insert an integer framerate into the metadata, if the framerate is floating point, we need to correct this metadataNode.addMetadata([("input/frame_rate", framerate.toFloat())]) script.addNode(metadataNode) # Generate Write nodes for nuke renders. for node in self._writeNodes: script.addNode(node) # We do this here, rather than when its created so the event happens at a # suitable time, otherwise, it's called before the Read node is created. if node.type() == "Write" and self._preset.properties( )["useAssets"]: ## @todo Assuming that we're always on a thread for now, this should be verified eventManager.blockingEvent(False, 'hieroToNukeScriptAddWrite', self._item, node.knob("file"), node, script) # add a viewer viewerNode = nuke.Node("Viewer") script.addNode(viewerNode) self._writeScript(script) # Nothing left to do, return False. return False