def addWriteNodeToScript(self, script, rootNode, framerate):
        """
        Override the default addWriteNodeToScript functionality so that we can add a SetNode before
        the default writenodes get added to the script. The SetNode will allow us to later push our mov writenode
        to this point so that it get parented correctly.
        """
        self.app.log_debug("Adding SetNode before base write node gets added")
        # Add the SetNode before the write nodes are added.
        set_command = nuke.SetNode(self._write_set_node_label, 0)
        script.addNode(set_command)

        super(ShotgunTranscodeExporter, self).addWriteNodeToScript(script, rootNode, framerate)
    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):
        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 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