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
Example #2
0
    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()
Example #3
0
    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