Skip to content

Instantly share code, notes, and snippets.

@tokejepsen
Last active February 9, 2024 16:43
Show Gist options
  • Save tokejepsen/bec84c9ad2d19e2e4eb0d84263554cdc to your computer and use it in GitHub Desktop.
Save tokejepsen/bec84c9ad2d19e2e4eb0d84263554cdc to your computer and use it in GitHub Desktop.
NukeStudio Frame Exporter
"""
Place in [NUKE_PATH]/Python/StartupUI
"""
import os
from PySide2 import QtCore, QtWidgets
import hiero.core.nuke as nuke
import hiero.core.nuke.Node
import hiero.core
from hiero.ui.FnTaskUIFormLayout import TaskUIFormLayout
from hiero.ui.FnUIProperty import (
UIPropertyFactory
)
class FrameExporter(hiero.exporters.FnTranscodeExporter.TranscodeExporter):
def __init__(self, initDict):
hiero.exporters.FnTranscodeExporter.TranscodeExporter.__init__(
self, initDict
)
# Store progress for later updating.
self._progress = 0.0
def taskStep(self):
# Generate a nuke script to render.
self._script = nuke.ScriptWriter()
isTrackItem = isinstance(self._item, hiero.core.TrackItem)
isClip = isinstance(self._item, hiero.core.Clip)
isSequence = isinstance(self._item, hiero.core.Sequence)
assert (isTrackItem or isClip or isSequence)
# Export an individual clip or track item
if isClip or isTrackItem:
self.writeClipOrTrackItemToScript(self._script)
# Export an entire sequence
elif isSequence:
self.writeSequenceToScript(self._script)
# Layout the script
hiero.exporters.FnScriptLayout.scriptLayout(self._script)
frames = self.get_export_frames()
for frame in frames:
self.export_frame(frame)
self._progress = 1.0
return False
def export_frame(self, frame):
for node in self._script.getNodes():
# Adjusting the root settings.
if node.__class__.__name__ == "RootNode":
node.knobs()["first_frame"] = frame
node.knobs()["last_frame"] = frame
if isinstance(self._item, hiero.core.TrackItem):
if node.__class__.__name__ == "TimeClipNode":
node.knobs()["frame"] = frame
node.knobs()["first"] = (
self._item.mapTimelineToSource(frame)
)
node.knobs()["last"] = (
self._item.mapTimelineToSource(frame)
)
self._script.writeToDisk(self._scriptfile)
log_path = self._scriptfile.replace(".nk", ".log")
log_file = open(log_path, "w")
process = nuke.executeNukeScript(self._scriptfile, log_file, True)
self.poll(process)
log_file.close()
if not self._preset.properties()["keepNukeScript"]:
os.remove(self._scriptfile)
os.remove(log_path)
def poll(self, process):
import time
returnCode = process.poll()
# if the return code hasn't been set, Nuke is still running
if returnCode is None:
time.sleep(1)
self.poll(process)
def startTask(self):
pass
def finishTask(self):
pass
def progress(self):
return self._progress
def get_export_frames(self):
item = self._item
# Can not tag TrackItems so will be searching the sequence for tagged
# frames.
if isinstance(self._item, hiero.core.TrackItem):
item = self._item.sequence()
frames_to_export = []
for tag in item.tags():
data = tag.metadata().dict()
if "tag.length" in data and data["tag.length"] == "1":
frames_to_export.append(int(data["tag.start"]))
# Can not tag TrackItems, so TrackItem will search for tagged
# frames in the sequence within its timeline range.
if isinstance(self._item, hiero.core.TrackItem):
frames_to_keep = []
for frame in frames_to_export:
if frame < int(self._item.timelineIn()):
continue
if frame > int(self._item.timelineOut()):
continue
frames_to_keep.append(frame)
frames_to_export = frames_to_keep
return frames_to_export
class FrameExportUI(hiero.exporters.FnExternalRenderUI.NukeRenderTaskUI):
def __init__(self, preset):
"""Initialize"""
hiero.exporters.FnExternalRenderUI.NukeRenderTaskUI.__init__(
self,
preset,
FrameExporter,
"Frame Exporter"
)
self._tags = []
def populateUI(self, widget, exportTemplate):
layout = widget.layout()
formLayout = TaskUIFormLayout()
self._formLayout = formLayout
layout.addLayout(formLayout)
self.buildCodecUI(formLayout, itemTaskType=self.taskItemType())
# Effects
formLayout.addDivider("Effects")
includeEffectsToolTip = (
"Enable this to include soft effects in the exported script."
)
key, value, label = "includeEffects", True, "Include Effects"
uiProperty = UIPropertyFactory.create(
type(value),
key=key,
value=value,
dictionary=self._preset.properties(),
label=label+":",
tooltip=includeEffectsToolTip
)
formLayout.addRow(label+":", uiProperty)
self._uiProperties.append(uiProperty)
# BURN-IN
formLayout.addDivider("Burn-in")
burninToolTip = (
"When enabled, a text burn-in is applied to the media using a "
"Nuke Gizmo.\n"
"Click Edit to define the information applied during burn-in. "
"Burn-in fields accept any combination of dropdown tokens and "
"custom text, for example {clip}_myEdit.\n"
"You can also include Nuke expression syntax, for example "
"[metadata input/ctime], will add the creation time metadata in "
"the Nuke stream."
)
burninLayout = QtWidgets.QHBoxLayout()
burninCheckbox = QtWidgets.QCheckBox()
burninCheckbox.setToolTip(burninToolTip)
burninCheckbox.stateChanged.connect(self._burninEnableClicked)
if self._preset.properties()["burninDataEnabled"]:
burninCheckbox.setCheckState(QtCore.Qt.Checked)
burninButton = QtWidgets.QPushButton("Edit")
burninButton.setToolTip(burninToolTip)
burninButton.clicked.connect(self._burninEditClicked)
burninLayout.addWidget(burninCheckbox)
burninLayout.addWidget(burninButton)
formLayout.addRow("Burn-in Gizmo:", burninLayout)
# NUKE SCRIPT
# create checkbox for whether the EDL task should add Absolute Paths
formLayout.addDivider("Nuke Script")
keepNukeScriptCheckbox = QtWidgets.QCheckBox()
keepNukeScriptCheckbox.setCheckState(QtCore.Qt.Unchecked)
if self._preset.properties()["keepNukeScript"]:
keepNukeScriptCheckbox.setCheckState(QtCore.Qt.Checked)
keepNukeScriptCheckbox.stateChanged.connect(
self.keepNukeScriptCheckboxChanged
)
keepNukeScriptCheckbox.setToolTip(
"A Nuke script is created for each transcode. If you'd like to "
"keep the temporary .nk file from being destroyed, enable this "
"option. The script will get generated into the same directory as "
"the transcode output"
)
formLayout.addRow("Keep Nuke Script:", keepNukeScriptCheckbox)
class FramePreset(hiero.exporters.FnTranscodeExporter.TranscodePreset):
def __init__(self, name, properties):
hiero.core.RenderTaskPreset.__init__(
self, FrameExporter, name, properties
)
# Set any preset defaults here
self.properties()["keepNukeScript"] = False
self.properties()["useSingleSocket"] = False
self.properties()["burninDataEnabled"] = False
burninPropertyData = (
hiero.exporters.FnExternalRender.NukeRenderTask.burninPropertyData
)
self.properties()["burninData"] = dict(
(datadict["knobName"], None) for datadict in burninPropertyData
)
self.properties()["additionalNodesEnabled"] = False
self.properties()["additionalNodesData"] = []
self.properties()["method"] = "Blend"
self.properties()["includeEffects"] = True
self.properties()["includeAudio"] = False
self.properties()["deleteAudio"] = True
self.properties()["readAllLinesForExport"] = False
# Give the Write node a name, so it can be referenced elsewhere
if "writeNodeName" not in self.properties():
self.properties()["writeNodeName"] = "Write_{ext}"
self.properties().update(properties)
hiero.ui.taskUIRegistry.registerTaskUI(FramePreset, FrameExportUI)
hiero.core.taskRegistry.registerTask(FramePreset, FrameExporter)
@tokejepsen
Copy link
Author

TIL from the foundry forums (https://community.foundry.com/discuss/topic/147525/nukestudio-export-tagged-frames#1188902):

Ah, I see. In that case I would rather do something like this, after calling the populateUI. The implementation is more straight-forward and probably more comprehensive to the user, who might miss sections of the UI if the wrong export content is selected accidentally.

        for widget in self._uiProperties:
            if widget._label == 'includeAudio':
                self._formLayout.setWidgetEnabled(widget, False)
                widget.setToolTip('Writing Audio is disabled for the Stillframe Export')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment