From 870530cb08aff2f440b46d13651b25826bbebf05 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Wed, 3 Jun 2026 15:19:02 +0530 Subject: [PATCH 1/4] Added support for Runway Aleph 2: --- clientlibs/main.js | 4 +- clientlibs/utils.js | 80 +++++++++++++++++++++++++++ modules/videoInputsFrame.py | 104 +++++++++++++++++++++++++----------- modules/videoModelSearch.py | 3 ++ 4 files changed, 158 insertions(+), 33 deletions(-) diff --git a/clientlibs/main.js b/clientlibs/main.js index 39293f0..f13dd75 100644 --- a/clientlibs/main.js +++ b/clientlibs/main.js @@ -1,6 +1,6 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; -import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, saveTextHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, textModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, videoInferenceSpeechInputToggleHandler, regionalPromptingRegionsToggleHandler, upscalerToggleHandler, imageUpscalerSettingsToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, textInferenceSettingsToggleHandler, videoSettingsToggleHandler, videoInferenceSettingsTtsToggleHandler, videoInferenceSettingsActiveSpeakerDetectionToggleHandler, videoInferenceSettingsActiveSpeakerBoundingBoxesToggleHandler, videoInferenceSettingsSegmentsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, threeDInferenceSettingsMeshClusterToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, outpaintSettingsToggleHandler, safetyInputsToggleHandler, imageInferenceSettingsColorPaletteToggleHandler, imageInferenceSettingsMoodboardsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler, audioInferenceSpeechVoicesToggleHandler, referenceVideosToggleHandler } from "./utils.js"; +import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, saveTextHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, textModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, videoInferenceSpeechInputToggleHandler, regionalPromptingRegionsToggleHandler, upscalerToggleHandler, imageUpscalerSettingsToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, textInferenceSettingsToggleHandler, videoSettingsToggleHandler, videoInferenceSettingsTtsToggleHandler, videoInferenceSettingsActiveSpeakerDetectionToggleHandler, videoInferenceSettingsActiveSpeakerBoundingBoxesToggleHandler, videoInferenceSettingsSegmentsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, threeDInferenceSettingsMeshClusterToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, outpaintSettingsToggleHandler, safetyInputsToggleHandler, imageInferenceSettingsColorPaletteToggleHandler, imageInferenceSettingsMoodboardsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler, audioInferenceSpeechVoicesToggleHandler, videoInputsFrameToggleHandler, referenceVideosToggleHandler } from "./utils.js"; import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js"; const nodeInitList = []; @@ -165,6 +165,8 @@ app.registerExtension({ videoAdvancedFeatureInputsToggleHandler(node); } else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINFERENCEINPUTS) { audioInferenceInputsToggleHandler(node); + } else if(nodeClass === RUNWARE_NODE_TYPES.VIDEOINPUTSFRAMEIMAGES) { + videoInputsFrameToggleHandler(node); } else if(nodeClass === RUNWARE_NODE_TYPES.REFERENCEVIDEOS || nodeClass === RUNWARE_NODE_TYPES.REFERENCEVIDEOS_LEGACY) { referenceVideosToggleHandler(node); diff --git a/clientlibs/utils.js b/clientlibs/utils.js index 0f90b98..dd23d36 100644 --- a/clientlibs/utils.js +++ b/clientlibs/utils.js @@ -2869,6 +2869,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "runware:190@1 (Ovi)", ], "Runway": [ + "runway:aleph@2.0 (Runway Aleph 2.0)", "runway:2@1 (Runway Aleph)", "runway:1@1 (Runway Gen-4 Turbo)", "runway:1@2 (Runway Gen-4.5)", @@ -2992,6 +2993,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "lightricks:ltx@2.3": {"width": 1920, "height": 1080}, "lightricks:ltx@2.3-fast": {"width": 1920, "height": 1080}, "runware:190@1": {"width": 0, "height": 0}, + "runway:aleph@2.0": {"width": 0, "height": 0}, "runway:2@1": {"width": 1280, "height": 720}, "runway:1@1": {"width": 1280, "height": 720}, "runway:1@2": {"width": 1280, "height": 720}, @@ -3092,6 +3094,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "lightricks:ltx@2.3": "1080p", "lightricks:ltx@2.3-fast": "1080p", "runware:190@1": null, // No resolution support + "runway:aleph@2.0": null, // Output ratio matches input video "runway:2@1": "720p", "runway:1@1": "720p", "runway:1@2": "720p", @@ -4958,6 +4961,82 @@ function audioInferenceSpeechVoicesToggleHandler(voicesNode) { } } +function videoInputsFrameToggleHandler(frameImagesNode) { + if (!frameImagesNode?.widgets) return; + if (frameImagesNode._videoInputsFrameToggleHandlerRegistered) return; + frameImagesNode._videoInputsFrameToggleHandlerRegistered = true; + + function toggleWidgetState(useWidget, paramWidget, paramName) { + if (!useWidget || !paramWidget) return; + + function toggleEnabled() { + const enabled = useWidget.value === true; + + if (paramWidget.inputEl) { + paramWidget.inputEl.disabled = !enabled; + paramWidget.inputEl.style.opacity = enabled ? "1" : "0.5"; + paramWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed"; + paramWidget.inputEl.readOnly = !enabled; + } + + if (paramWidget.options && paramWidget.options.element) { + paramWidget.options.element.disabled = !enabled; + paramWidget.options.element.style.opacity = enabled ? "1" : "0.5"; + paramWidget.options.element.style.pointerEvents = enabled ? "auto" : "none"; + } + + paramWidget.disabled = !enabled; + + if (!paramWidget.inputEl && paramName) { + const nodeElement = frameImagesNode.htmlElements?.widgetsContainer || frameImagesNode.htmlElements; + if (nodeElement) { + const input = nodeElement.querySelector(`input[name="${paramName}"], textarea[name="${paramName}"], select[name="${paramName}"]`); + if (input) { + input.disabled = !enabled; + input.style.opacity = enabled ? "1" : "0.5"; + input.style.cursor = enabled ? "text" : "not-allowed"; + input.readOnly = !enabled; + if (input.tagName === "SELECT") { + input.style.pointerEvents = enabled ? "auto" : "none"; + } + } + } + } + + frameImagesNode.setDirtyCanvas(true); + } + + appendWidgetCB(useWidget, () => { + setTimeout(toggleEnabled, 50); + }); + + setTimeout(toggleEnabled, 100); + } + + function initializeHandler() { + if (!frameImagesNode.widgets || frameImagesNode.widgets.length === 0) { + setTimeout(initializeHandler, 100); + return; + } + + for (let i = 1; i <= 4; i++) { + const useFrameWidget = frameImagesNode.widgets.find((w) => w && w.name === `useFrame${i}`); + const frameWidget = frameImagesNode.widgets.find((w) => w && w.name === `frame${i} position`); + const useTimestampWidget = frameImagesNode.widgets.find((w) => w && w.name === `useTimestamp${i}`); + const timestampWidget = frameImagesNode.widgets.find((w) => w && w.name === `timestamp${i}`); + + if (useFrameWidget && frameWidget) { + toggleWidgetState(useFrameWidget, frameWidget, `frame${i} position`); + } + if (useTimestampWidget && timestampWidget) { + toggleWidgetState(useTimestampWidget, timestampWidget, `timestamp${i}`); + } + } + } + + initializeHandler(); +} + function referenceVideosToggleHandler(referenceVideosNode) { if (!referenceVideosNode?.widgets) return; @@ -5097,6 +5176,7 @@ export { videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler, audioInferenceSpeechVoicesToggleHandler, + videoInputsFrameToggleHandler, referenceVideosToggleHandler, }; diff --git a/modules/videoInputsFrame.py b/modules/videoInputsFrame.py index 45a4050..2d163b1 100644 --- a/modules/videoInputsFrame.py +++ b/modules/videoInputsFrame.py @@ -3,61 +3,101 @@ from .utils import runwareUtils as rwUtils class RunwareVideoInputsFrameImages: """Video Inputs Frame node for adapting frame images structure for video inference""" - + MAX_FRAMES = 4 - + FRAME_POSITIONS = ["first", "last"] + @classmethod def INPUT_TYPES(cls): optionalInputs = {} - + for i in range(1, cls.MAX_FRAMES + 1): optionalInputs[f"image{i}"] = ("IMAGE", { - "tooltip": "Frame image to include in video inference inputs." + "tooltip": "Frame image to include in video inference inputs.frameImages.", }) - optionalInputs[f"frame{i} position"] = ("STRING", { - "default": "", - "tooltip": "Optional frame label (e.g., 'first', 'last', specific frame number). Leave blank for provider defaults." + optionalInputs[f"useFrame{i}"] = ("BOOLEAN", { + "tooltip": "Enable to set frame position (first/last). Mutually exclusive with timestamp for this slot.", + "default": False, }) - + optionalInputs[f"frame{i} position"] = (cls.FRAME_POSITIONS, { + "default": "first", + "tooltip": "Frame position: 'first' or 'last'. Only used when 'Use Frame' is enabled.", + }) + optionalInputs[f"useTimestamp{i}"] = ("BOOLEAN", { + "tooltip": "Enable to set a timestamp (seconds) within the input video. Mutually exclusive with frame position for this slot.", + "default": False, + }) + optionalInputs[f"timestamp{i}"] = ("STRING", { + "default": "0", + "tooltip": "Timestamp in seconds (hundredths supported, e.g. 3.44). Only used when 'Use Timestamp' is enabled.", + }) + return { "required": {}, - "optional": optionalInputs + "optional": optionalInputs, } - - DESCRIPTION = "Convert Runware Frame Images into the expected structure for video inference inputs." + + DESCRIPTION = ( + "Build inputs.frameImages for video inference: image-only strings, " + "{image, frame: first|last}, or {image, timestamp: seconds}." + ) FUNCTION = "createFrameInputs" RETURN_TYPES = ("RUNWAREVIDEOINPUTSFRAMEIMAGES",) RETURN_NAMES = ("Video Inputs Frame Images",) CATEGORY = "Runware" - + def createFrameInputs(self, **kwargs): frameImages = [] - + for i in range(1, self.MAX_FRAMES + 1): - imageKey = f"image{i}" - frameKey = f"frame{i} position" - - image = kwargs.get(imageKey) - frame = kwargs.get(frameKey, "") - + image = kwargs.get(f"image{i}") if image is None: continue - - frameData = self._createFrameEntry(image, frame) - frameImages.append(frameData) - + + use_frame = kwargs.get(f"useFrame{i}", False) + frame_position = kwargs.get(f"frame{i} position", "first") + use_timestamp = kwargs.get(f"useTimestamp{i}", False) + timestamp = self._parse_timestamp(kwargs.get(f"timestamp{i}", "0")) + + frameImages.append( + self._createFrameEntry( + image, + use_frame=use_frame, + frame_position=frame_position, + use_timestamp=use_timestamp, + timestamp=timestamp, + ) + ) + return (frameImages,) - - def _createFrameEntry(self, image, frameLabel): + + @staticmethod + def _parse_timestamp(value, default=0.0): + """Coerce timestamp input; ComfyUI may send '' when the widget is disabled.""" + if value is None: + return default + if isinstance(value, (int, float)): + return max(0.0, float(value)) + if isinstance(value, str): + stripped = value.strip() + if stripped == "": + return default + return max(0.0, float(stripped)) + return default + + def _createFrameEntry(self, image, use_frame, frame_position, use_timestamp, timestamp): imageData = rwUtils.convertTensor2IMGBase64Only(image) entry = {"image": imageData} - - if isinstance(frameLabel, str) and frameLabel.strip() != "": - if frameLabel.strip().isdigit(): - entry["frame"] = int(frameLabel.strip()) - else: - entry["frame"] = frameLabel.strip() - + + if use_timestamp: + entry["timestamp"] = round(timestamp, 2) + elif use_frame: + frame_value = frame_position + if isinstance(frame_value, str): + frame_value = frame_value.strip() + if frame_value: + entry["frame"] = frame_value + return entry diff --git a/modules/videoModelSearch.py b/modules/videoModelSearch.py index d401c10..a440af5 100644 --- a/modules/videoModelSearch.py +++ b/modules/videoModelSearch.py @@ -97,6 +97,7 @@ class videoModelSearch: "runware:190@1 (Ovi)", ], "Runway": [ + "runway:aleph@2.0 (Runway Aleph 2.0)", "runway:2@1 (Runway Aleph)", "runway:1@1 (Runway Gen-4 Turbo)", "runway:1@2 (Runway Gen-4.5)", @@ -242,6 +243,7 @@ class videoModelSearch: "runware:190@1": {"width": 0, "height": 0}, # Runway Models + "runway:aleph@2.0": {"width": 0, "height": 0}, "runway:2@1": {"width": 1280, "height": 720}, "runway:1@1": {"width": 1280, "height": 720}, "runway:1@2": {"width": 1280, "height": 720}, @@ -386,6 +388,7 @@ class videoModelSearch: "runware:190@1": None, # No resolution support # Runway Models + "runway:aleph@2.0": None, # Output ratio matches input video "runway:2@1": "720p", "runway:1@1": "720p", "runway:1@2": "720p", From e56fad5584033c3e4b28145ba87917f1915bc648 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Wed, 3 Jun 2026 17:33:05 +0530 Subject: [PATCH 2/4] Added worflow --- workflows/Runware_Video_Inference_Runway_Aleph_2.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 workflows/Runware_Video_Inference_Runway_Aleph_2.json diff --git a/workflows/Runware_Video_Inference_Runway_Aleph_2.json b/workflows/Runware_Video_Inference_Runway_Aleph_2.json new file mode 100644 index 0000000..5b8a1f7 --- /dev/null +++ b/workflows/Runware_Video_Inference_Runway_Aleph_2.json @@ -0,0 +1 @@ +{"id":"bf764222-1d7d-47c9-9de0-d4ebb5e45a26","revision":0,"last_node_id":10,"last_link_id":8,"nodes":[{"id":3,"type":"SaveVideo","pos":[561.4697887933625,611.2895996849188],"size":[270,106],"flags":{},"order":8,"mode":0,"inputs":[{"localized_name":"video","name":"video","type":"VIDEO","link":4},{"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.22.0"},"widgets_values":["video/ComfyUI","auto","auto"]},{"id":7,"type":"LoadVideo","pos":[-1235.6423068040497,498.7003120329182],"size":[282.798828125,356.79882812500006],"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":[5]}],"properties":{"cnr_id":"comfy-core","ver":"0.22.0","Node name for S&R":"LoadVideo"},"widgets_values":["example-video.mp4","image"]},{"id":8,"type":"Runware Media Upload","pos":[-896.7948099933702,725.3891279700817],"size":[270,58],"flags":{},"order":4,"mode":0,"inputs":[{"localized_name":"media","name":"media","shape":7,"type":"VIDEO,AUDIO","link":5},{"localized_name":"mediaUUID","name":"mediaUUID","shape":7,"type":"STRING","widget":{"name":"mediaUUID"},"link":null}],"outputs":[{"localized_name":"mediaUUID","name":"mediaUUID","type":"STRING","links":[6]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"2d6d065e7ee3e4db538d04302be0680fbdbda1bf","Node name for S&R":"Runware Media Upload"},"widgets_values":["b457cdef-8928-4606-b1f8-85b05a2cd329"],"bgcolor":"#5345bf"},{"id":9,"type":"LoadImage","pos":[-1228.3371540784756,968.9790638019527],"size":[282.798828125,314.0000000000001],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[8]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.22.0","Node name for S&R":"LoadImage"},"widgets_values":["e9ecc70f-97ee-4158-b4a2-0625c0da9ef2.jpg","image"]},{"id":6,"type":"Runware Video Inputs Frame Images","pos":[-898.3249663729471,880.7657387290162],"size":[342.05625,478],"flags":{},"order":5,"mode":0,"inputs":[{"localized_name":"image1","name":"image1","shape":7,"type":"IMAGE","link":8},{"localized_name":"image2","name":"image2","shape":7,"type":"IMAGE","link":7},{"localized_name":"image3","name":"image3","shape":7,"type":"IMAGE","link":null},{"localized_name":"image4","name":"image4","shape":7,"type":"IMAGE","link":null},{"localized_name":"useFrame1","name":"useFrame1","shape":7,"type":"BOOLEAN","widget":{"name":"useFrame1"},"link":null},{"localized_name":"frame1 position","name":"frame1 position","shape":7,"type":"COMBO","widget":{"name":"frame1 position"},"link":null},{"localized_name":"useTimestamp1","name":"useTimestamp1","shape":7,"type":"BOOLEAN","widget":{"name":"useTimestamp1"},"link":null},{"localized_name":"timestamp1","name":"timestamp1","shape":7,"type":"FLOAT","widget":{"name":"timestamp1"},"link":null},{"localized_name":"useFrame2","name":"useFrame2","shape":7,"type":"BOOLEAN","widget":{"name":"useFrame2"},"link":null},{"localized_name":"frame2 position","name":"frame2 position","shape":7,"type":"COMBO","widget":{"name":"frame2 position"},"link":null},{"localized_name":"useTimestamp2","name":"useTimestamp2","shape":7,"type":"BOOLEAN","widget":{"name":"useTimestamp2"},"link":null},{"localized_name":"timestamp2","name":"timestamp2","shape":7,"type":"FLOAT","widget":{"name":"timestamp2"},"link":null},{"localized_name":"useFrame3","name":"useFrame3","shape":7,"type":"BOOLEAN","widget":{"name":"useFrame3"},"link":null},{"localized_name":"frame3 position","name":"frame3 position","shape":7,"type":"COMBO","widget":{"name":"frame3 position"},"link":null},{"localized_name":"useTimestamp3","name":"useTimestamp3","shape":7,"type":"BOOLEAN","widget":{"name":"useTimestamp3"},"link":null},{"localized_name":"timestamp3","name":"timestamp3","shape":7,"type":"FLOAT","widget":{"name":"timestamp3"},"link":null},{"localized_name":"useFrame4","name":"useFrame4","shape":7,"type":"BOOLEAN","widget":{"name":"useFrame4"},"link":null},{"localized_name":"frame4 position","name":"frame4 position","shape":7,"type":"COMBO","widget":{"name":"frame4 position"},"link":null},{"localized_name":"useTimestamp4","name":"useTimestamp4","shape":7,"type":"BOOLEAN","widget":{"name":"useTimestamp4"},"link":null},{"localized_name":"timestamp4","name":"timestamp4","shape":7,"type":"FLOAT","widget":{"name":"timestamp4"},"link":null}],"outputs":[{"localized_name":"Video Inputs Frame Images","name":"Video Inputs Frame Images","type":"RUNWAREVIDEOINPUTSFRAMEIMAGES","links":[1]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"2d6d065e7ee3e4db538d04302be0680fbdbda1bf","Node name for S&R":"Runware Video Inputs Frame Images"},"widgets_values":[true,"first",false,"",false,"first",true,5,false,"first",false,0,false,"first",false,0],"bgcolor":"#5345bf"},{"id":4,"type":"Runware Video Model Search","pos":[-838.0378148247337,374.21575827028437],"size":[294.16015625,250],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"Model Search","name":"Model Search","type":"STRING","widget":{"name":"Model Search"},"link":null},{"localized_name":"Model Architecture","name":"Model Architecture","type":"COMBO","widget":{"name":"Model Architecture"},"link":null},{"localized_name":"VideoList","name":"VideoList","type":"COMBO","widget":{"name":"VideoList"},"link":null},{"localized_name":"Use Search Value","name":"Use Search Value","type":"BOOLEAN","widget":{"name":"Use Search Value"},"link":null},{"localized_name":"useCustomDimensions","name":"useCustomDimensions","type":"COMBO","widget":{"name":"useCustomDimensions"},"link":null},{"localized_name":"Width","name":"Width","type":"INT","widget":{"name":"Width"},"link":null},{"localized_name":"Height","name":"Height","type":"INT","widget":{"name":"Height"},"link":null},{"localized_name":"useResolution","name":"useResolution","type":"BOOLEAN","widget":{"name":"useResolution"},"link":null},{"localized_name":"resolution","name":"resolution","type":"COMBO","widget":{"name":"resolution"},"link":null}],"outputs":[{"localized_name":"Runware Video Model","name":"Runware Video Model","type":"RUNWAREVIDEOMODEL","links":[2]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"2d6d065e7ee3e4db538d04302be0680fbdbda1bf","Node name for S&R":"Runware Video Model Search"},"widgets_values":["","Runway","runway:aleph@2.0 (Runway Aleph 2.0)",false,"Model Default",0,0,false,"1080p"],"bgcolor":"#5345bf"},{"id":2,"type":"Runware Video Inference Inputs","pos":[-498.3819310486654,650.2673832171942],"size":[341.783203125,374],"flags":{},"order":6,"mode":0,"inputs":[{"localized_name":"Image","name":"Image","shape":7,"type":"IMAGE","link":null},{"localized_name":"Frame Images","name":"Frame Images","shape":7,"type":"RUNWAREVIDEOINPUTSFRAMEIMAGES","link":1},{"localized_name":"Audio Inputs","name":"Audio Inputs","shape":7,"type":"RUNWAREAUDIOINPUT","link":null},{"localized_name":"Speech Inputs","name":"Speech Inputs","shape":7,"type":"RUNWARESPEECHINPUT","link":null},{"localized_name":"Mask","name":"Mask","shape":7,"type":"IMAGE","link":null},{"localized_name":"Frame","name":"Frame","shape":7,"type":"IMAGE","link":null},{"localized_name":"Background","name":"Background","shape":7,"type":"IMAGE","link":null},{"localized_name":"References Images","name":"References Images","shape":7,"type":"RUNWAREVIDEOINPUTSREFERENCEIMAGES","link":null},{"localized_name":"Reference Videos","name":"Reference Videos","shape":7,"type":"RUNWAREREFERENCEVIDEOS","link":null},{"localized_name":"Reference Voices","name":"Reference Voices","shape":7,"type":"RUNWAREREFERENCEVOICES","link":null},{"localized_name":"Elements","name":"Elements","shape":7,"type":"RUNWAREVIDEOINFERENCEELEMENTS","link":null},{"localized_name":"Reference Audios","name":"Reference Audios","shape":7,"type":"RUNWAREINPUTAUDIOS","link":null},{"localized_name":"Avatar","name":"Avatar","shape":7,"type":"STRING","widget":{"name":"Avatar"},"link":null},{"localized_name":"Audio","name":"Audio","shape":7,"type":"STRING","widget":{"name":"Audio"},"link":null},{"localized_name":"Video","name":"Video","shape":7,"type":"STRING","widget":{"name":"Video"},"link":6},{"localized_name":"draftId","name":"draftId","shape":7,"type":"STRING","widget":{"name":"draftId"},"link":null},{"localized_name":"videoId","name":"videoId","shape":7,"type":"STRING","widget":{"name":"videoId"},"link":null}],"outputs":[{"localized_name":"Video Inference Inputs","name":"Video Inference Inputs","type":"RUNWAREVIDEOINFERENCEINPUTS","links":[3]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"2d6d065e7ee3e4db538d04302be0680fbdbda1bf","Node name for S&R":"Runware Video Inference Inputs"},"widgets_values":["","","","",""],"bgcolor":"#5345bf"},{"id":10,"type":"LoadImage","pos":[-422.02137693301785,1195.7442402205668],"size":[282.798828125,314],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[7]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.22.0","Node name for S&R":"LoadImage"},"widgets_values":["d6e7abf8-c794-4d57-9b6f-23d48d7ec198.jpg","image"]},{"id":1,"type":"Runware Video Inference","pos":[-26.090439276473003,338.69116539922743],"size":[400,818],"flags":{},"order":7,"mode":0,"inputs":[{"localized_name":"Model","name":"Model","type":"RUNWAREVIDEOMODEL","link":2},{"localized_name":"frameImages","name":"frameImages","shape":7,"type":"RUNWAREFRAMEIMAGES","link":null},{"localized_name":"referenceImages","name":"referenceImages","shape":7,"type":"RUNWAREREFERENCEIMAGES","link":null},{"localized_name":"providerSettings","name":"providerSettings","shape":7,"type":"RUNWAREPROVIDERSETTINGS","link":null},{"localized_name":"inputAudios","name":"inputAudios","shape":7,"type":"RUNWAREINPUTAUDIOS","link":null},{"localized_name":"referenceVideos","name":"referenceVideos","shape":7,"type":"RUNWAREREFERENCEVIDEOS","link":null},{"localized_name":"speech","name":"speech","shape":7,"type":"RUNWAREVIDEOINFERENCESPEECHINPUT","link":null},{"localized_name":"inputs","name":"inputs","shape":7,"type":"RUNWAREVIDEOINFERENCEINPUTS","link":3},{"localized_name":"safetyInputs","name":"safetyInputs","shape":7,"type":"RUNWARESAFETYINPUTS","link":null},{"localized_name":"videoAdvancedFeatureInputs","name":"videoAdvancedFeatureInputs","shape":7,"type":"RUNWAREVIDEOADVANCEDFEATUREINPUTS","link":null},{"localized_name":"Accelerator","name":"Accelerator","shape":7,"type":"RUNWAREACCELERATOR","link":null},{"localized_name":"settings","name":"settings","shape":7,"type":"RUNWAREVIDEOSETTINGS","link":null},{"localized_name":"Multi Inference Mode","name":"Multi Inference Mode","type":"BOOLEAN","widget":{"name":"Multi Inference Mode"},"link":null},{"localized_name":"Prompt Weighting","name":"Prompt Weighting","type":"COMBO","widget":{"name":"Prompt Weighting"},"link":null},{"localized_name":"useDuration","name":"useDuration","type":"BOOLEAN","widget":{"name":"useDuration"},"link":null},{"localized_name":"duration","name":"duration","type":"INT","widget":{"name":"duration"},"link":null},{"localized_name":"useFps","name":"useFps","type":"BOOLEAN","widget":{"name":"useFps"},"link":null},{"localized_name":"fps","name":"fps","type":"INT","widget":{"name":"fps"},"link":null},{"localized_name":"outputFormat","name":"outputFormat","type":"COMBO","widget":{"name":"outputFormat"},"link":null},{"localized_name":"useSeed","name":"useSeed","type":"BOOLEAN","widget":{"name":"useSeed"},"link":null},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"useSteps","name":"useSteps","type":"BOOLEAN","widget":{"name":"useSteps"},"link":null},{"localized_name":"steps","name":"steps","type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"useBatchSize","name":"useBatchSize","type":"BOOLEAN","widget":{"name":"useBatchSize"},"link":null},{"localized_name":"batchSize","name":"batchSize","type":"INT","widget":{"name":"batchSize"},"link":null},{"localized_name":"useSchedulers","name":"useSchedulers","type":"BOOLEAN","widget":{"name":"useSchedulers"},"link":null},{"localized_name":"scheduler","name":"scheduler","type":"COMBO","widget":{"name":"scheduler"},"link":null},{"localized_name":"useCFGScale","name":"useCFGScale","type":"BOOLEAN","widget":{"name":"useCFGScale"},"link":null},{"localized_name":"cfgScale","name":"cfgScale","type":"FLOAT","widget":{"name":"cfgScale"},"link":null},{"localized_name":"acceleration","name":"acceleration","type":"COMBO","widget":{"name":"acceleration"},"link":null},{"localized_name":"positivePrompt","name":"positivePrompt","shape":7,"type":"STRING","widget":{"name":"positivePrompt"},"link":null},{"localized_name":"negativePrompt","name":"negativePrompt","shape":7,"type":"STRING","widget":{"name":"negativePrompt"},"link":null}],"outputs":[{"localized_name":"VIDEO","name":"VIDEO","type":"VIDEO","links":[4]},{"localized_name":"RW-Task","name":"RW-Task","type":"RUNWARETASK","links":null},{"localized_name":"OUTPUT","name":"OUTPUT","type":"RUNWARETASK","links":null}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"2d6d065e7ee3e4db538d04302be0680fbdbda1bf","Node name for S&R":"Runware Video Inference"},"widgets_values":[false,"Disabled",false,5,false,24,"mp4",false,316859533,"randomize",false,20,false,1,false,"euler",false,5,"none","a cat",""],"bgcolor":"#5345bf"}],"links":[[1,6,0,2,1,"RUNWAREVIDEOINPUTSFRAMEIMAGES"],[2,4,0,1,0,"RUNWAREVIDEOMODEL"],[3,2,0,1,7,"RUNWAREVIDEOINFERENCEINPUTS"],[4,1,0,3,0,"VIDEO"],[5,7,0,8,0,"VIDEO"],[6,8,0,2,14,"STRING"],[7,10,0,6,1,"IMAGE"],[8,9,0,6,0,"IMAGE"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.4603984853356424,"offset":[1664.2654022827712,-124.30809454398683]}},"version":0.4} \ No newline at end of file From 9ded98f377012c23d13ec9a13df0d14c810d2667 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Wed, 3 Jun 2026 18:02:59 +0530 Subject: [PATCH 3/4] Fixed comments --- modules/videoInputsFrame.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/videoInputsFrame.py b/modules/videoInputsFrame.py index 2d163b1..2c8b10e 100644 --- a/modules/videoInputsFrame.py +++ b/modules/videoInputsFrame.py @@ -16,7 +16,7 @@ class RunwareVideoInputsFrameImages: "tooltip": "Frame image to include in video inference inputs.frameImages.", }) optionalInputs[f"useFrame{i}"] = ("BOOLEAN", { - "tooltip": "Enable to set frame position (first/last). Mutually exclusive with timestamp for this slot.", + "tooltip": "Enable to set frame position (first/last).", "default": False, }) optionalInputs[f"frame{i} position"] = (cls.FRAME_POSITIONS, { @@ -24,7 +24,7 @@ class RunwareVideoInputsFrameImages: "tooltip": "Frame position: 'first' or 'last'. Only used when 'Use Frame' is enabled.", }) optionalInputs[f"useTimestamp{i}"] = ("BOOLEAN", { - "tooltip": "Enable to set a timestamp (seconds) within the input video. Mutually exclusive with frame position for this slot.", + "tooltip": "Enable to set a timestamp (seconds) within the input video.", "default": False, }) optionalInputs[f"timestamp{i}"] = ("STRING", { @@ -38,8 +38,9 @@ class RunwareVideoInputsFrameImages: } DESCRIPTION = ( - "Build inputs.frameImages for video inference: image-only strings, " - "{image, frame: first|last}, or {image, timestamp: seconds}." + "Build inputs.frameImages entries for video inference: " + "{image: base64}, {image: base64, frame: first|last|}, or " + "{image: base64, timestamp: seconds}." ) FUNCTION = "createFrameInputs" RETURN_TYPES = ("RUNWAREVIDEOINPUTSFRAMEIMAGES",) From 8adcce50e4021b56eccf3d35de1946c3c156afee Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Wed, 3 Jun 2026 18:27:21 +0530 Subject: [PATCH 4/4] Fix frame images timestamp input validation and parsing Use FLOAT for timestamp fields, handle invalid strings gracefully, update node description, and reset empty timestamp values in the UI when Use Timestamp is disabled. --- clientlibs/utils.js | 7 +++++++ modules/videoInputsFrame.py | 16 +++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/clientlibs/utils.js b/clientlibs/utils.js index e2c1406..8ebcf2a 100644 --- a/clientlibs/utils.js +++ b/clientlibs/utils.js @@ -5042,6 +5042,13 @@ function videoInputsFrameToggleHandler(frameImagesNode) { function toggleEnabled() { const enabled = useWidget.value === true; + // FLOAT timestamp widgets: avoid '' on disabled slots (ComfyUI validation error) + if (!enabled && paramName && paramName.startsWith("timestamp")) { + if (paramWidget.value === "" || paramWidget.value == null) { + paramWidget.value = 0.0; + } + } + if (paramWidget.inputEl) { paramWidget.inputEl.disabled = !enabled; paramWidget.inputEl.style.opacity = enabled ? "1" : "0.5"; diff --git a/modules/videoInputsFrame.py b/modules/videoInputsFrame.py index 2c8b10e..60fc4e8 100644 --- a/modules/videoInputsFrame.py +++ b/modules/videoInputsFrame.py @@ -27,8 +27,11 @@ class RunwareVideoInputsFrameImages: "tooltip": "Enable to set a timestamp (seconds) within the input video.", "default": False, }) - optionalInputs[f"timestamp{i}"] = ("STRING", { - "default": "0", + optionalInputs[f"timestamp{i}"] = ("FLOAT", { + "default": 0.0, + "min": 0.0, + "max": 9999.0, + "step": 0.01, "tooltip": "Timestamp in seconds (hundredths supported, e.g. 3.44). Only used when 'Use Timestamp' is enabled.", }) @@ -39,7 +42,7 @@ class RunwareVideoInputsFrameImages: DESCRIPTION = ( "Build inputs.frameImages entries for video inference: " - "{image: base64}, {image: base64, frame: first|last|}, or " + "{image: base64}, {image: base64, frame: first|last}, or " "{image: base64, timestamp: seconds}." ) FUNCTION = "createFrameInputs" @@ -58,7 +61,7 @@ class RunwareVideoInputsFrameImages: use_frame = kwargs.get(f"useFrame{i}", False) frame_position = kwargs.get(f"frame{i} position", "first") use_timestamp = kwargs.get(f"useTimestamp{i}", False) - timestamp = self._parse_timestamp(kwargs.get(f"timestamp{i}", "0")) + timestamp = self._parse_timestamp(kwargs.get(f"timestamp{i}", 0.0)) frameImages.append( self._createFrameEntry( @@ -83,7 +86,10 @@ class RunwareVideoInputsFrameImages: stripped = value.strip() if stripped == "": return default - return max(0.0, float(stripped)) + try: + return max(0.0, float(stripped)) + except ValueError: + return default return default def _createFrameEntry(self, image, use_frame, frame_position, use_timestamp, timestamp):