""" 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 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: [1/10] Done: [1/10] ERROR: : """ 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 ") 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()