Merge pull request #158 from Runware/feature-aleph
Added support for Runway Aleph 2:
This commit is contained in:
+3
-1
@@ -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, audioInferenceReferenceVoiceToggleHandler, 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, audioInferenceReferenceVoiceToggleHandler, audioInferenceSpeechVoicesToggleHandler, videoInputsFrameToggleHandler, referenceVideosToggleHandler } from "./utils.js";
|
||||
import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js";
|
||||
|
||||
const nodeInitList = [];
|
||||
@@ -167,6 +167,8 @@ app.registerExtension({
|
||||
audioInferenceInputsToggleHandler(node);
|
||||
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINFERENCEINPUTSREFERENCEAUDIO) {
|
||||
audioInferenceReferenceVoiceToggleHandler(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);
|
||||
|
||||
@@ -2899,6 +2899,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)",
|
||||
@@ -3022,6 +3023,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},
|
||||
@@ -3122,6 +3124,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",
|
||||
@@ -5028,6 +5031,89 @@ 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;
|
||||
|
||||
// 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";
|
||||
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;
|
||||
|
||||
@@ -5168,6 +5254,7 @@ export {
|
||||
audioInferenceInputsToggleHandler,
|
||||
audioInferenceReferenceVoiceToggleHandler,
|
||||
audioInferenceSpeechVoicesToggleHandler,
|
||||
videoInputsFrameToggleHandler,
|
||||
referenceVideosToggleHandler,
|
||||
};
|
||||
|
||||
|
||||
+79
-32
@@ -3,61 +3,108 @@ 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).",
|
||||
"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.",
|
||||
"default": False,
|
||||
})
|
||||
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.",
|
||||
})
|
||||
|
||||
return {
|
||||
"required": {},
|
||||
"optional": optionalInputs
|
||||
"optional": optionalInputs,
|
||||
}
|
||||
|
||||
DESCRIPTION = "Convert Runware Frame Images into the expected structure for video inference inputs."
|
||||
|
||||
DESCRIPTION = (
|
||||
"Build inputs.frameImages entries for video inference: "
|
||||
"{image: base64}, {image: base64, frame: first|last}, or "
|
||||
"{image: base64, 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.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
|
||||
try:
|
||||
return max(0.0, float(stripped))
|
||||
except ValueError:
|
||||
return default
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user