"""JoyEcho_AutoFinish - fire-and-forget master-grade finishing. Wire: JoyEcho_Generate.images -> AutoFinish.images -> CreateVideo.images (pure passthrough - the graph's own preview path is unchanged). When the node executes (i.e. after Generate has written every per-shot master), it spawns autofinish_worker.py as a DETACHED process and returns immediately, so the queue item finishes normally. The worker then: snapshot shot_XXX.mp4/.wav -> RTXBatchVideoUpscale each via the ComfyUI API (jobs run right after this queue item, FIFO) -> pad-wav lossless concat -> __MASTER.mp4 next to the shot masters. Why detached instead of doing the work inline: inline VSR would hold this queue item open for ~10 extra minutes and block anything queued behind it; detached, the upscales interleave with the queue like any other job. The snapshot step makes back-to-back queued renders safe (each finisher pins its own run's frames before the next render overwrites shot_XXX). Progress/errors: /joyecho/_autofinish_.log - the node cannot report them (it has already returned by then). Requires the ComfyUI-RTX-Video-Suite pack (RTXBatchVideoUpscale) and the NVIDIA MAXINE VSR SDK on this box. On a box without them (e.g. BEAST unless MAXINE is installed) the worker logs the error and exits; the render itself is unaffected. """ from __future__ import annotations import os import subprocess import sys from pathlib import Path try: import folder_paths except ImportError: # outside ComfyUI (tests) folder_paths = None class JoyEcho_AutoFinish: @classmethod def INPUT_TYPES(cls): return { "required": { "images": ("IMAGE",), "enabled": ("BOOLEAN", { "default": True, "tooltip": "Off = pure passthrough, no finishing run."}), "master_name": ("STRING", { "default": "JOYECHO", "tooltip": "Final file: __MASTER.mp4"}), "scale_factor": ("FLOAT", { "default": 1.5, "min": 1.0, "max": 4.0, "step": 0.05, "tooltip": "1.5 on a 1280x736 base -> 1920x1104"}), "quality": (["LOW", "MEDIUM", "HIGH", "ULTRA"], {"default": "ULTRA"}), "batch_size": ("INT", {"default": 4, "min": 1, "max": 12}), }, "optional": { "shots_subdir": ("STRING", { "default": "joyecho", "tooltip": "Output subfolder holding shot_XXX.mp4 " "(matches JoyEcho_Generate output_prefix " "folder)."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("images",) FUNCTION = "run" CATEGORY = "JoyEcho" OUTPUT_NODE = True def run(self, images, enabled, master_name, scale_factor, quality, batch_size, shots_subdir="joyecho"): if not enabled: return (images,) out_root = (folder_paths.get_output_directory() if folder_paths else os.path.join(os.getcwd(), "output")) shots_dir = os.path.join(out_root, shots_subdir) upscaled_dir = os.path.join(out_root, "upscaled_videos") worker = str(Path(__file__).resolve().parent / "autofinish_worker.py") cmd = [sys.executable, worker, "--shots-dir", shots_dir, "--upscaled-dir", upscaled_dir, "--name", str(master_name).strip() or "JOYECHO", "--scale", str(float(scale_factor)), "--quality", quality, "--batch-size", str(int(batch_size))] flags = 0 if os.name == "nt": flags = (subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP) subprocess.Popen(cmd, creationflags=flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True) print(f"[JoyEcho] AutoFinish: worker spawned for '{master_name}' " f"(scale {scale_factor}); upscale jobs will queue after this " f"item. Progress: {shots_dir}\\_autofinish_.log", flush=True) return (images,) NODE_CLASS_MAPPINGS = {"JoyEcho_AutoFinish": JoyEcho_AutoFinish} NODE_DISPLAY_NAME_MAPPINGS = {"JoyEcho_AutoFinish": "JoyEcho Auto-Finish (RTX master)"}