def _buildAdditionalNodes(self, item): # Callback from script generation to add additional nodes nodes = [] data = self._preset.properties()["additionalNodesData"] itemType = None if isinstance(item, hiero.core.Clip): itemType = FnExternalRender.kPerShot elif isinstance(item, hiero.core.TrackItem): itemType = FnExternalRender.kPerShot elif isinstance(item, (hiero.core.VideoTrack, hiero.core.AudioTrack)): itemType = FnExternalRender.kPerTrack elif isinstance(item, hiero.core.Sequence): itemType = FnExternalRender.kPerSequence if itemType: if self._preset.properties()["additionalNodesEnabled"]: nodes.extend( FnExternalRender.createAdditionalNodes( itemType, data, item)) if self._preset.properties()["useAssets"]: # Allow registered listeners to work with the script manager = FnAssetAPI.Events.getEventManager() ## @todo Assuming that we're always on a thread for now, this should be verified manager.blockingEvent(False, 'hieroToNukeScriptAddNodesForItem', itemType, item, nodes) return nodes
def writeSequenceToScript(self, script): # Get the range to set on the Root node. This is the range the nuke script will render by default. start, end = self.outputRange() log.debug("TranscodeExporter: rootNode range is %s %s", start, end) framerate = self._sequence.framerate() dropFrames = self._sequence.dropFrame() fps = framerate.toFloat() rootNode = self.makeRootNode(start, end, fps) script.addNode(rootNode) # Add Unconnected additional nodes if self._preset.properties()["additionalNodesEnabled"]: script.addNode( FnExternalRender.createAdditionalNodes( FnExternalRender.kUnconnected, self._preset.properties()["additionalNodesData"], self._item)) # Force track items to be reformatted to fit the sequence in this case reformatMethod = { "to_type": nuke.ReformatNode.kCompReformatToSequence, "filter": self._preset.properties()["reformat"].get("filter") } if self._preset.properties()["add_image"]: watermark_image = get_watermark_read( self._preset.properties()["watermark_path"], self._preset.properties()["watermark_premult"]) premult_node = nuke.Node('Premult') watermark_transform = nuke.Node('Transform') watermark_transform.setKnob( 'translate', self._preset.properties()["watermark_transform"]) script.addNode(watermark_image) script.addNode(premult_node) script.addNode(watermark_transform) # Build out the sequence. scriptParams = FnNukeHelpersV2.ScriptWriteParameters( includeAnnotations=False, includeEffects=self.includeEffects(), retimeMethod=self._preset.properties()["method"], reformatMethod=reformatMethod, additionalNodesCallback=self._buildAdditionalNodes, views=self.views()) script.pushLayoutContext("sequence", self._sequence.name(), disconnected=False) sequenceWriter = FnNukeHelpersV2.SequenceScriptWriter( self._sequence, scriptParams) sequenceWriter.writeToScript(script, offset=0, skipOffline=self._skipOffline, mediaToSkip=self._mediaToSkip, disconnected=False, masterTracks=None) script.popLayoutContext() script.pushLayoutContext("write", "%s_Render" % self._item.name()) # Create metadata node metadataNode = nuke.MetadataNode(metadatavalues=[( "hiero/project", self._projectName), ("hiero/project_guid", self._project.guid())]) # Add sequence Tags to metadata metadataNode.addMetadataFromTags(self._sequence.tags()) # Apply timeline offset to nuke output script.addNode( nuke.AddTimeCodeNode( timecodeStart=self._sequence.timecodeStart(), fps=framerate, dropFrames=dropFrames, frame=0 if self._startFrame is None else self._startFrame)) # 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())]) # And next the Write. script.addNode(metadataNode) # Add Burnin group (if enabled) self.addBurninNodes(script) if self._preset.properties()["add_image"]: merge_node = nuke.MergeNode() script.addNode(merge_node) # Get the output format, either from the sequence or the preset, and set it as the root format. # If a reformat is specified in the preset, add it immediately before the Write node. outputReformatNode = self._sequence.format().addToNukeScript( resize=nuke.ReformatNode.kResizeNone, black_outside=False) self._addReformatNode(script, rootNode, outputReformatNode) self.addWriteNodeToScript(script, rootNode, framerate) script.addNode(createViewerNode(self._projectSettings)) script.popLayoutContext()
def writeClipOrTrackItemToScript(self, script): isMovieContainerFormat = self._preset.properties()["file_type"] in ( "mov", "mov32", "mov64", "ffmpeg") start, end = self.outputRange(ignoreRetimes=True, clampToSource=False) unclampedStart = start log.debug("rootNode range is %s %s", start, end) firstFrame, lastFrame = start, end 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 ) and not isMovieContainerFormat: # We dont want to export an image sequence with a 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 # The clip framerate may be invalid, if so, use parent sequence framerate fps, framerate, dropFrames = None, None, False if self._sequence: framerate = self._sequence.framerate() dropFrames = self._sequence.dropFrame() if self._clip.framerate().isValid(): framerate = self._clip.framerate() dropFrames = self._clip.dropFrame() if framerate: fps = framerate.toFloat() # Create root node, this defines global frame range and framerate rootNode = self.makeRootNode(start, end, fps) script.addNode(rootNode) if self._preset.properties()["add_image"]: watermark_image = get_watermark_read( self._preset.properties()["watermark_path"], self._preset.properties()["watermark_premult"]) premult_node = nuke.Node('Premult') watermark_transform = nuke.Node('Transform') watermark_transform.setKnob( 'translate', self._preset.properties()["watermark_transform"]) script.addNode(watermark_image) script.addNode(premult_node) script.addNode(watermark_transform) # Add Unconnected additional nodes if self._preset.properties()["additionalNodesEnabled"]: script.addNode( FnExternalRender.createAdditionalNodes( FnExternalRender.kUnconnected, self._preset.properties()["additionalNodesData"], self._item)) # Now add the Read node. writingClip = isinstance(self._item, hiero.core.Clip) if writingClip: script.pushLayoutContext("clip", self._item.name()) self._clip.addToNukeScript( script, additionalNodesCallback=self._buildAdditionalNodes, firstFrame=firstFrame, trimmed=True, includeEffects=self.includeEffects(), project=self._project ) # _clip has no project set, but one is needed by addToNukeScript to do colorpsace conversions script.popLayoutContext() else: # If there are separate track items for each view, write them out (in reverse # order so the inputs are correct) then add a JoinViews items = self._multiViewTrackItems if self._multiViewTrackItems else [ self._item ] for item in reversed(items): script.pushLayoutContext("clip", item.name()) # Construct a TrackItemExportScriptWriter and write the track item trackItemWriter = TrackItemExportScriptWriter(item) trackItemWriter.setAdditionalNodesCallback( self._buildAdditionalNodes) # Find sequence level effects/annotations which apply to the track item. # Annotations are not currently included by the transcode exporter effects, annotations = FnEffectHelpers.findEffectsAnnotationsForTrackItems( [item]) trackItemWriter.setEffects(self.includeEffects(), effects) # TODO This is being done in both the NukeShotExporter and TranscodeExporter. # There should be fully shared code for doing the handles calculations. fullClipLength = (self._cutHandles is None) if fullClipLength: trackItemWriter.setOutputClipLength() else: trackItemWriter.setOutputHandles(*self.outputHandles()) trackItemWriter.setIncludeRetimes( self._retime, self._preset.properties()["method"]) trackItemWriter.setReformat( self._preset.properties()["reformat"]) trackItemWriter.setFirstFrame(firstFrame) trackItemWriter.writeToScript(script) if self._preset.properties()["add_image"]: merge_node = nuke.MergeNode() script.addNode(merge_node) script.popLayoutContext() if self._multiViewTrackItems: joinViewsNode = nuke.Node("JoinViews", inputs=len(self.views())) script.addNode(joinViewsNode) script.pushLayoutContext("write", "%s_Render" % self._item.name()) metadataNode = nuke.MetadataNode(metadatavalues=[( "hiero/project", self._projectName), ("hiero/project_guid", self._project.guid())]) # Add sequence Tags to metadata metadataNode.addMetadataFromTags(self._clip.tags()) # Need a framerate inorder to create a timecode if framerate: # Apply timeline offset to nuke output if self._cutHandles is None: timeCodeNodeStartFrame = unclampedStart else: startHandle, endHandle = self.outputHandles() timeCodeNodeStartFrame = trackItemTimeCodeNodeStartFrame( unclampedStart, self._item, startHandle, endHandle) script.addNode( nuke.AddTimeCodeNode(timecodeStart=self._clip.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 Burnin group (if enabled) self.addBurninNodes(script) # Get the output format, either from the clip or the preset, and set it as the root format. # If a reformat is specified in the preset, add it immediately before the Write node. reformatNode = self._clip.format().addToNukeScript(None) self._addReformatNode(script, rootNode, reformatNode) self.addWriteNodeToScript(script, rootNode, framerate) script.addNode(createViewerNode(self._projectSettings)) script.popLayoutContext()
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