From 7af33a2ec3b31f88376a02ab6567284edc934d8a Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Thu, 26 Mar 2026 21:07:01 -0400 Subject: [PATCH 1/2] Added width, height and fps to videoUpscaler --- clientlibs/utils.js | 43 ++++++++++++--- modules/videoUpscaler.py | 52 +++++++++++++++++++ .../Runware_Video_Upscaler_Topazlabs.json | 1 + 3 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 workflows/Runware_Video_Upscaler_Topazlabs.json diff --git a/clientlibs/utils.js b/clientlibs/utils.js index bde3407..7d324f3 100644 --- a/clientlibs/utils.js +++ b/clientlibs/utils.js @@ -726,21 +726,50 @@ function videoUpscalerToggleHandler(videoUpscalerNode) { const useUpscaleFactorWidget = videoUpscalerNode.widgets.find(w => w && w.name === "useUpscaleFactor"); const upscaleFactorWidget = videoUpscalerNode.widgets.find(w => w && w.name === "upscaleFactor"); - - if (!useUpscaleFactorWidget || !upscaleFactorWidget) return; + const useFpsWidget = videoUpscalerNode.widgets.find(w => w && w.name === "useFps"); + const fpsWidget = videoUpscalerNode.widgets.find(w => w && w.name === "fps"); + const useDimensionWidget = videoUpscalerNode.widgets.find(w => w && w.name === "useDimension"); + const widthWidget = videoUpscalerNode.widgets.find(w => w && w.name === "width"); + const heightWidget = videoUpscalerNode.widgets.find(w => w && w.name === "height"); function applyToggleState() { - const enabled = useUpscaleFactorWidget.value === true; - toggleWidgetEnabled(upscaleFactorWidget, enabled, videoUpscalerNode); + if (useUpscaleFactorWidget && upscaleFactorWidget) { + const enabled = useUpscaleFactorWidget.value === true; + toggleWidgetEnabled(upscaleFactorWidget, enabled, videoUpscalerNode); + } + + if (useFpsWidget && fpsWidget) { + const enabled = useFpsWidget.value === true; + toggleWidgetEnabled(fpsWidget, enabled, videoUpscalerNode); + } + + if (useDimensionWidget && widthWidget && heightWidget) { + const enabled = useDimensionWidget.value === "custom"; + toggleWidgetEnabled(widthWidget, enabled, videoUpscalerNode); + toggleWidgetEnabled(heightWidget, enabled, videoUpscalerNode); + } + videoUpscalerNode.setDirtyCanvas(true); } // Initialize state once widgets are rendered setTimeout(applyToggleState, 100); - appendWidgetCB(useUpscaleFactorWidget, () => { - setTimeout(applyToggleState, 50); - }); + if (useUpscaleFactorWidget) { + appendWidgetCB(useUpscaleFactorWidget, () => { + setTimeout(applyToggleState, 50); + }); + } + if (useFpsWidget) { + appendWidgetCB(useFpsWidget, () => { + setTimeout(applyToggleState, 50); + }); + } + if (useDimensionWidget) { + appendWidgetCB(useDimensionWidget, () => { + setTimeout(applyToggleState, 50); + }); + } } function useParameterToggleHandler(node) { diff --git a/modules/videoUpscaler.py b/modules/videoUpscaler.py index 735d714..7bfefa1 100644 --- a/modules/videoUpscaler.py +++ b/modules/videoUpscaler.py @@ -5,6 +5,7 @@ class videoUpscaler: RUNWARE_VUPSCALER_MODELS = { "Bria Video Upscaler": "bria:50@1", "Bytedance Video Upscaler": "bytedance:50@1", + "Topaz Labs Starlight Precise 2.5": "topazlabs:starlight-precise@2.5", } @classmethod @@ -24,12 +25,43 @@ class videoUpscaler: "tooltip": "Enable to explicitly set the upscale factor for the request.", "default": False, }), + "useFps": ("BOOLEAN", { + "label_on": "Yes", + "label_off": "No", + "tooltip": "Enable to explicitly set FPS for the output video.", + "default": False, + }), + "useDimension": (["none", "custom"], { + "tooltip": "Choose whether to send custom width and height.", + "default": "none", + }), }, "optional": { "upscaleFactor": ([2, 4], { "tooltip": "Integer scale factor for upscaling (2x or 4x).", "default": 2, }), + "fps": ("INT", { + "default": 24, + "min": 1, + "max": 240, + "step": 1, + "tooltip": "Output frames per second (1-240). Used when useFps is enabled.", + }), + "width": ("INT", { + "default": 1920, + "min": 1, + "max": 8192, + "step": 1, + "tooltip": "Custom output width in pixels. Used when useDimension is custom.", + }), + "height": ("INT", { + "default": 1080, + "min": 1, + "max": 8192, + "step": 1, + "tooltip": "Custom output height in pixels. Used when useDimension is custom.", + }), "Output Format": (["mp4", "webm"], { "default": "mp4", "tooltip": "Choose the output video format.", @@ -47,6 +79,11 @@ class videoUpscaler: video_uuid = kwargs.get("Video") useUpscaleFactor = kwargs.get("useUpscaleFactor", False) upscaleFactor = kwargs.get("upscaleFactor") + useFps = kwargs.get("useFps", False) + fps = kwargs.get("fps", 24) + useDimension = kwargs.get("useDimension", "none") + width = kwargs.get("width", 1920) + height = kwargs.get("height", 1080) modelName = kwargs.get("Model", "Bria Video Upscaler") outputFormat = kwargs.get("Output Format", "mp4") @@ -75,6 +112,21 @@ class videoUpscaler: else: raise ValueError("upscaleFactor must be 2 or 4 when useUpscaleFactor is enabled.") + if useFps: + if isinstance(fps, int) and 1 <= fps <= 240: + task_payload["fps"] = fps + else: + raise ValueError("fps must be an integer between 1 and 240 when useFps is enabled.") + + if useDimension == "custom": + if isinstance(width, int) and isinstance(height, int) and width > 0 and height > 0: + task_payload["width"] = width + task_payload["height"] = height + else: + raise ValueError("width and height must be positive integers when useDimension is custom.") + elif useDimension != "none": + raise ValueError("useDimension must be either 'none' or 'custom'.") + genConfig = [task_payload] try: diff --git a/workflows/Runware_Video_Upscaler_Topazlabs.json b/workflows/Runware_Video_Upscaler_Topazlabs.json new file mode 100644 index 0000000..1fa53ca --- /dev/null +++ b/workflows/Runware_Video_Upscaler_Topazlabs.json @@ -0,0 +1 @@ +{"id":"bd4178de-d8b9-44bb-a22f-31329b9492d5","revision":0,"last_node_id":4,"last_link_id":3,"nodes":[{"id":2,"type":"SaveVideo","pos":[900.0263671875,767.330322265625],"size":[270,106],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"video","name":"video","type":"VIDEO","link":1},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null},{"localized_name":"format","name":"format","type":"COMBO","widget":{"name":"format"},"link":null},{"localized_name":"codec","name":"codec","type":"COMBO","widget":{"name":"codec"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.46","Node name for S&R":"SaveVideo"},"widgets_values":["video/ComfyUI","auto","auto"]},{"id":4,"type":"LoadVideo","pos":[14.491833686828613,662.3383178710938],"size":[274.080078125,348.08013916015625],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"file","name":"file","type":"COMBO","widget":{"name":"file"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"VIDEO","name":"VIDEO","type":"VIDEO","links":[2]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.46","Node name for S&R":"LoadVideo"},"widgets_values":["c3896e44-4869-4142-9f02-6d7b967d3a1e.mp4","image"]},{"id":3,"type":"Runware Media Upload","pos":[417.66265869140625,1156.2657470703125],"size":[270,58],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"media","name":"media","shape":7,"type":"VIDEO,AUDIO","link":2},{"localized_name":"mediaUUID","name":"mediaUUID","shape":7,"type":"STRING","widget":{"name":"mediaUUID"},"link":null}],"outputs":[{"localized_name":"mediaUUID","name":"mediaUUID","type":"STRING","links":[3]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"8f688fa3e58e1aaa84de508efc595c0dcdf5e190","Node name for S&R":"Runware Media Upload"},"widgets_values":[""],"bgcolor":"#5345bf"},{"id":1,"type":"Runware Video Upscaler","pos":[542.6085205078125,741.3137817382812],"size":[270,274],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"Video","name":"Video","type":"STRING","widget":{"name":"Video"},"link":3},{"localized_name":"Model","name":"Model","type":"COMBO","widget":{"name":"Model"},"link":null},{"localized_name":"useUpscaleFactor","name":"useUpscaleFactor","type":"BOOLEAN","widget":{"name":"useUpscaleFactor"},"link":null},{"localized_name":"useFps","name":"useFps","type":"BOOLEAN","widget":{"name":"useFps"},"link":null},{"localized_name":"useDimension","name":"useDimension","type":"COMBO","widget":{"name":"useDimension"},"link":null},{"localized_name":"upscaleFactor","name":"upscaleFactor","shape":7,"type":"COMBO","widget":{"name":"upscaleFactor"},"link":null},{"localized_name":"fps","name":"fps","shape":7,"type":"INT","widget":{"name":"fps"},"link":null},{"localized_name":"width","name":"width","shape":7,"type":"INT","widget":{"name":"width"},"link":null},{"localized_name":"height","name":"height","shape":7,"type":"INT","widget":{"name":"height"},"link":null},{"localized_name":"Output Format","name":"Output Format","shape":7,"type":"COMBO","widget":{"name":"Output Format"},"link":null}],"outputs":[{"localized_name":"VIDEO","name":"VIDEO","type":"VIDEO","links":[1]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"8f688fa3e58e1aaa84de508efc595c0dcdf5e190","Node name for S&R":"Runware Video Upscaler"},"widgets_values":["","Topaz Labs Starlight Precise 2.5",false,false,"custom",2,24,720,360,"mp4"],"bgcolor":"#5345bf"}],"links":[[1,1,0,2,0,"VIDEO"],[2,4,0,3,0,"VIDEO,AUDIO"],[3,3,0,1,0,"STRING"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.6830134553650705,"offset":[306.87239836992575,-512.9827815463534]}},"version":0.4} \ No newline at end of file From 6720b9076b7a315ecbf36b9d0230c4dddd0cc366 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Thu, 26 Mar 2026 21:26:31 -0400 Subject: [PATCH 2/2] Fixed timed out --- modules/videoUpscaler.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/videoUpscaler.py b/modules/videoUpscaler.py index 7bfefa1..7188d4d 100644 --- a/modules/videoUpscaler.py +++ b/modules/videoUpscaler.py @@ -147,15 +147,30 @@ class videoUpscaler: # Extract task UUID for polling taskUUID = genConfig[0]["taskUUID"] + max_poll_seconds = 2400 # 40 minutes + started_at = rwUtils.time.time() # Poll for video completion while True: + if rwUtils.time.time() - started_at > max_poll_seconds: + raise TimeoutError( + f"Video upscale timed out after {max_poll_seconds} seconds. " + f"Task UUID: {taskUUID}" + ) + # Check for interrupt before each poll comfy.model_management.throw_exception_if_processing_interrupted() # Poll for video result pollResult = rwUtils.pollVideoResult(taskUUID) print(f"[Debugging] Poll result: {pollResult}") + + if not isinstance(pollResult, dict): + # Keep polling if transport layer returned empty/invalid payload. + for _ in range(10): + comfy.model_management.throw_exception_if_processing_interrupted() + rwUtils.time.sleep(0.1) + continue # Check for errors first if pollResult and "errors" in pollResult and len(pollResult["errors"]) > 0: @@ -172,11 +187,22 @@ class videoUpscaler: if status == "success": if "mediaURL" in video_data or "videoURL" in video_data or "videoBase64Data" in video_data: - videos = rwUtils.convertVideoB64List(pollResult, 1920, 1080) + out_width = width if useDimension == "custom" else video_data.get("width", 1920) + out_height = height if useDimension == "custom" else video_data.get("height", 1080) + videos = rwUtils.convertVideoB64List(pollResult, out_width, out_height) # SaveVideo expects a single video object, not a tuple return (videos[0],) if len(videos) > 0 else (None,) + raise Exception( + "Video upscale returned success without mediaURL/videoURL/videoBase64Data. " + f"Response: {rwUtils.safe_json_dumps(video_data, indent=2)}" + ) + if status in ("failed", "error", "cancelled", "canceled"): + raise Exception( + "Video upscale failed with terminal status " + f"'{status}'. Response: {rwUtils.safe_json_dumps(video_data, indent=2)}" + ) - # If status is "processing", continue polling + # If status is "processing" (or unknown transient), continue polling # Check for interrupt before waiting comfy.model_management.throw_exception_if_processing_interrupted()