Files
twotalesanimation 1a2ece6f12 Initial Commit
2026-07-04 15:54:19 +02:00

173 lines
5.9 KiB
Python

"""
process.py — Nuke terminal-mode batch renderer.
This script runs INSIDE Nuke and must not import anything outside the
Python standard library or Nuke's own modules.
Usage (called by nuke_runner.py):
Nuke.exe -t process.py <jobs.json>
jobs.json schema
----------------
{
"template_script": "/abs/path/to/proxy_template.nk",
"jobs": [
{
"basename": "SHOW_EP_SEQ_SHOT_ELEM_TASK_v001",
"input_sequence": "/path/SHOW_..._%05d.exr",
"first_frame": 1001,
"last_frame": 1100,
"output_folder": "/output/SHOW/SHOW_EP/basename",
"output_movie": "/output/.../basename.mov",
"output_exr": "/output/.../basename.%05d.exr"
},
...
]
}
Expected template nodes
-----------------------
Read_Input — source EXR sequence
Overlay — text burn-in; knob 'bottomleft' is set to the shot basename
Write_4k — EXR sequence output (file = output_exr)
Write_h264 — MOV / H.264 output (file = output_movie)
Both Write nodes are executed in a single nuke.execute() call so the
graph is only evaluated once per frame.
Progress lines printed to stdout (parsed by nuke_runner.py):
[1/10] Rendering: <basename>
[1/10] Done: <basename>
[1/10] ERROR: <basename>: <message>
"""
from __future__ import annotations
import json
import os
import sys
def _log(msg: str) -> None:
"""Print *msg* and flush immediately so the parent process receives it."""
print(msg)
sys.stdout.flush()
def main() -> None:
# ── Locate jobs file ──────────────────────────────────────────────
# When launched as: Nuke.exe -t process.py jobs.json
# sys.argv is typically: ['process.py', 'jobs.json']
if len(sys.argv) < 2:
_log("ERROR: Usage: Nuke.exe -t process.py <jobs.json>")
sys.exit(1)
jobs_path = sys.argv[1]
if not os.path.isfile(jobs_path):
_log(f"ERROR: Jobs file not found: {jobs_path}")
sys.exit(1)
try:
with open(jobs_path, encoding="utf-8") as fh:
payload = json.load(fh)
except Exception as exc:
_log(f"ERROR: Cannot parse jobs file: {exc}")
sys.exit(1)
template_script = payload.get("template_script", "")
jobs = payload.get("jobs", [])
total = len(jobs)
if not jobs:
_log("No jobs to process.")
return
# ── Import Nuke ───────────────────────────────────────────────────
try:
import nuke # noqa: PLC0415
except ImportError:
_log("ERROR: 'nuke' module not available. This script must run inside Nuke.")
sys.exit(1)
# ── Open template ─────────────────────────────────────────────────
if not os.path.isfile(template_script):
_log(f"ERROR: Template script not found: {template_script}")
sys.exit(1)
_log(f"Loading template: {template_script}")
nuke.scriptOpen(template_script)
read_node = nuke.toNode("Read_Input")
overlay_node = nuke.toNode("Overlay")
write_4k = nuke.toNode("Write_4k")
write_h264 = nuke.toNode("Write_h264")
if read_node is None:
_log("ERROR: Node 'Read_Input' not found in template.")
sys.exit(1)
if write_4k is None:
_log("ERROR: Node 'Write_4k' not found in template.")
sys.exit(1)
if write_h264 is None:
_log("ERROR: Node 'Write_h264' not found in template.")
sys.exit(1)
if overlay_node is None:
_log("WARNING: Node 'Overlay' not found in template — bottomleft will not be set.")
# ── Process jobs ──────────────────────────────────────────────────
for idx, job in enumerate(jobs, start=1):
basename = job.get("basename", f"job_{idx}")
_log(f"[{idx}/{total}] Rendering: {basename}")
try:
first = int(job["first_frame"])
last = int(job["last_frame"])
input_seq = job["input_sequence"]
output_mov = job["output_movie"]
output_exr = job["output_exr"]
output_folder = job.get("output_folder", "")
# Ensure output directory exists
target_dir = output_folder or os.path.dirname(output_mov)
if target_dir:
os.makedirs(target_dir, exist_ok=True)
# Configure Read node
read_node["file"].setValue(input_seq)
read_node["first"].setValue(first)
read_node["last"].setValue(last)
read_node["origfirst"].setValue(first)
read_node["origlast"].setValue(last)
# Set burn-in text on the Overlay node
if overlay_node is not None:
overlay_node["bottomleft"].setValue(basename)
# Configure both Write nodes
write_4k["file"].setValue(output_exr)
write_h264["file"].setValue(output_mov)
# Render both Write nodes (sequential calls; list form not supported in all versions)
nuke.execute(write_4k, first, last, 1)
nuke.execute(write_h264, first, last, 1)
_log(f"[{idx}/{total}] Done: {basename}")
except Exception as exc: # noqa: BLE001
_log(f"[{idx}/{total}] ERROR: {basename}: {exc}")
# Free memory caches between jobs to avoid RAM build-up
for fn in ("clearRAMCache", "memory2", "memory"):
func = getattr(nuke, fn, None)
if func is not None:
try:
func("free")
except Exception:
pass
break
_log("All jobs complete.")
if __name__ == "__main__":
main()