add workflows, etc

This commit is contained in:
Sirshendu Ganguly
2026-02-25 22:35:30 -05:00
parent dee0949f78
commit bc7b906bbd
12 changed files with 445 additions and 48 deletions
+2
View File
@@ -27,6 +27,7 @@ from .modules.referenceImages import referenceImages
from .modules.imageInferenceInputs import imageInferenceInputs
from .modules.videoInference import txt2vid
from .modules.videoModelSearch import videoModelSearch
from .modules.videoSettings import RunwareVideoSettings
from .modules.frameImages import RunwareFrameImages
from .modules.providerSettings.pixverseProviderSettings import RunwarePixverseProviderSettings
from .modules.providerSettings.openaiProviderSettings import RunwareOpenAIProviderSettings
@@ -159,6 +160,7 @@ NODE_CLASS_MAPPINGS = {
"Runware Reference Videos": referenceVideos,
"Runware Reference Voices": referenceVoices,
"Runware Video Inference Inputs": videoInferenceInputs,
"Runware Video Inference Settings": RunwareVideoSettings,
"Runware Lightricks Provider Settings": RunwareLightricksProviderSettings,
"Runware MiniMax Provider Settings": RunwareMiniMaxProviderSettings,
"Runware Luma Provider Settings": RunwareLumaProviderSettings,
+6 -1
View File
@@ -1,6 +1,6 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, useParameterToggleHandler, imageInferenceToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler } from "./utils.js";
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, videoSettingsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler } from "./utils.js";
import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js";
const nodeInitList = [];
@@ -82,6 +82,8 @@ app.registerExtension({
audioInferenceSpeechToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOSETTINGS) {
audioSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.VIDEOSETTINGS) {
videoSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.ACCELERATOROPTIONS) {
acceleratorOptionsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.BYTEDANCEPROVIDERSETTINGS) {
@@ -149,6 +151,9 @@ app.registerExtension({
videoModelSearchFilterHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOMODELSEARCH) {
audioModelSearchFilterHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.VECTORIZE) {
vectorizeModelSearchFilterHandler(node);
vectorizeToggleHandler(node);
}
for(const nodeWidget of nodeWidgets) {
+5
View File
@@ -101,6 +101,7 @@ const RUNWARE_NODE_TYPES = {
REFERENCEVIDEOS: "Runware Reference Videos",
REFERENCEVOICES: "Runware Reference Voices",
VIDEOINFERENCEINPUTS: "Runware Video Inference Inputs",
VIDEOSETTINGS: "Runware Video Inference Settings",
LIGHTRICKSPROVIDERSETTINGS: "Runware Lightricks Provider Settings",
KLINGPROVIDERSETTINGS: "Runware KlingAI Provider Settings",
KLINGMULTIPROMPTSEGMENT: "Runware Kling MultiPrompt Segment",
@@ -427,6 +428,7 @@ const RUNWARE_NODE_PROPS = {
},
[RUNWARE_NODE_TYPES.VECTORIZE]: {
bgColor: DEFAULT_BGCOLOR,
liveDimensions: true,
},
[RUNWARE_NODE_TYPES.VIDEOBGREMOVAL]: {
bgColor: DEFAULT_BGCOLOR,
@@ -452,6 +454,9 @@ const RUNWARE_NODE_PROPS = {
[RUNWARE_NODE_TYPES.AUDIOSETTINGSVOICEMODIFY]: {
bgColor: DEFAULT_BGCOLOR,
},
[RUNWARE_NODE_TYPES.VIDEOSETTINGS]: {
bgColor: DEFAULT_BGCOLOR,
},
[RUNWARE_NODE_TYPES.ACCELERATOROPTIONS]: {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
+140 -1
View File
@@ -419,8 +419,9 @@ async function syncDimensionsNodeHandler(node, dimensionsWidget) {
appendWidgetCB(dimensionsWidget, function(...args) {
const chosenDimension = args[0];
if(chosenDimension === "Custom") return;
if(chosenDimension === "Custom" || chosenDimension === "None") return;
const dimensionValue = DEFAULT_DIMENSIONS_LIST[chosenDimension];
if(!dimensionValue) return;
const [width, height] = dimensionValue.split("x");
widthWidget.callback(width, "customSetOperation");
heightWidget.callback(height, "customSetOperation");
@@ -1162,6 +1163,37 @@ function audioSettingsToggleHandler(settingsNode) {
if (useTurboWidget && turboWidget) toggleWidgetState(useTurboWidget, turboWidget, "turbo");
}
function videoSettingsToggleHandler(settingsNode) {
if (!settingsNode?.widgets) return;
const useDraftWidget = settingsNode.widgets.find(w => w && w.name === "useDraft");
const draftWidget = settingsNode.widgets.find(w => w && w.name === "draft");
const useAudioWidget = settingsNode.widgets.find(w => w && w.name === "useAudio");
const audioWidget = settingsNode.widgets.find(w => w && w.name === "audio");
const usePromptUpsamplingWidget = settingsNode.widgets.find(w => w && w.name === "usePromptUpsampling");
const promptUpsamplingWidget = settingsNode.widgets.find(w => w && w.name === "promptUpsampling");
function toggleWidgetState(useWidget, paramWidget, paramName) {
if (!useWidget || !paramWidget) return;
function applyState() {
const enabled = useWidget.value === true;
toggleWidgetEnabled(paramWidget, enabled, settingsNode);
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";
}
settingsNode.setDirtyCanvas(true);
}
setTimeout(applyState, 100);
appendWidgetCB(useWidget, () => setTimeout(applyState, 50));
}
if (useDraftWidget && draftWidget) toggleWidgetState(useDraftWidget, draftWidget, "draft");
if (useAudioWidget && audioWidget) toggleWidgetState(useAudioWidget, audioWidget, "audio");
if (usePromptUpsamplingWidget && promptUpsamplingWidget) toggleWidgetState(usePromptUpsamplingWidget, promptUpsamplingWidget, "promptUpsampling");
}
function audioInferenceSpeechToggleHandler(speechNode) {
if (!speechNode?.widgets) return;
@@ -2323,6 +2355,9 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"VEED": [
"veed:fabric@1.0 (VEED Fabric 1.0)",
],
"Pruna": [
"prunaai:p-video@0 (P-Video)",
],
};
const MODEL_DIMENSIONS = {
@@ -2402,6 +2437,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"runware:210@1": {"width": 512, "height": 512},
"xai:grok-imagine@video": {"width": 480, "height": 480},
"veed:fabric@1.0": {"width": 1280, "height": 720},
"prunaai:p-video@0": {"width": 1280, "height": 720},
};
const MODEL_RESOLUTIONS = {
@@ -2481,6 +2517,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"runware:210@1": null, // No resolution support (fixed 512x512)
"xai:grok-imagine@video": "480p",
"veed:fabric@1.0": "720p",
"prunaai:p-video@0": "720p",
};
const DEFAULT_DIMENSIONS = {"width": 1024, "height": 576};
@@ -2728,6 +2765,105 @@ function audioModelSearchFilterHandler(audioModelSearchNode) {
filterModelList();
}
function vectorizeModelSearchFilterHandler(vectorizeNode) {
const modelArchWidget = vectorizeNode.widgets.find(w => w.name === "Model Architecture");
const vectorListWidget = vectorizeNode.widgets.find(w => w.name === "Vector Model List");
if (!modelArchWidget || !vectorListWidget) return;
const VECTORIZE_MODELS = {
"Recraft": [
"recraft:1@1 (Recraft 1)",
"recraft:v4-pro@vector (Recraft V4 Pro Vector)",
"recraft:v4@vector (Recraft V4 Vector)",
],
"Picsart": [
"picsart:1@1 (Picsart 1)",
],
};
function filterModelList() {
const selectedArch = modelArchWidget.value;
let filteredModels = [];
if (selectedArch === "All") {
Object.values(VECTORIZE_MODELS).forEach(models => filteredModels.push(...models));
} else if (VECTORIZE_MODELS[selectedArch]) {
filteredModels = VECTORIZE_MODELS[selectedArch];
}
if (filteredModels.length > 0) {
const currentValue = vectorListWidget.value;
vectorListWidget.options.values = filteredModels;
if (!filteredModels.includes(currentValue)) {
vectorListWidget.value = filteredModels[0];
}
vectorizeNode.setDirtyCanvas(true);
}
}
appendWidgetCB(modelArchWidget, filterModelList);
filterModelList();
}
function vectorizeToggleHandler(vectorizeNode) {
const usePositivePromptWidget = vectorizeNode.widgets.find(w => w.name === "Use Positive Prompt");
const positivePromptWidget = vectorizeNode.widgets.find(w => w.name === "positivePrompt");
const dimensionsWidget = vectorizeNode.widgets.find(w => w.name === "dimensions");
const widthWidget = vectorizeNode.widgets.find(w => w.name === "width");
const heightWidget = vectorizeNode.widgets.find(w => w.name === "height");
function toggleWidgetState(useWidget, paramWidget, paramName) {
if (!useWidget || !paramWidget) return;
function applyState() {
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";
}
}
applyState();
appendWidgetCB(useWidget, applyState);
}
if (usePositivePromptWidget && positivePromptWidget) {
toggleWidgetState(usePositivePromptWidget, positivePromptWidget, "positivePrompt");
}
// Disable width/height when dimensions is "None" (same as image inference)
if (dimensionsWidget && widthWidget && heightWidget) {
function toggleDimensionsState() {
const dimensionsValue = dimensionsWidget.value;
const enabled = dimensionsValue !== "None";
if (widthWidget.inputEl) {
widthWidget.inputEl.disabled = !enabled;
widthWidget.inputEl.style.opacity = enabled ? "1" : "0.5";
widthWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed";
widthWidget.inputEl.readOnly = !enabled;
}
widthWidget.disabled = !enabled;
if (heightWidget.inputEl) {
heightWidget.inputEl.disabled = !enabled;
heightWidget.inputEl.style.opacity = enabled ? "1" : "0.5";
heightWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed";
heightWidget.inputEl.readOnly = !enabled;
}
heightWidget.disabled = !enabled;
vectorizeNode.setDirtyCanvas(true);
}
appendWidgetCB(dimensionsWidget, () => {
setTimeout(toggleDimensionsState, 50);
});
setTimeout(toggleDimensionsState, 100);
}
}
function klingProviderSettingsToggleHandler(klingNode) {
// Find all "use" parameter widgets for KlingAI Provider Settings (EXACT same pattern as imageInferenceToggleHandler)
const useCameraControlWidget = klingNode.widgets.find(w => w.name === "useCameraControl");
@@ -3794,6 +3930,8 @@ export {
videoInferenceDimensionsHandler,
videoModelSearchFilterHandler,
audioModelSearchFilterHandler,
vectorizeModelSearchFilterHandler,
vectorizeToggleHandler,
useParameterToggleHandler,
imageInferenceToggleHandler,
upscalerToggleHandler,
@@ -3801,6 +3939,7 @@ export {
audioInferenceToggleHandler,
audioInferenceSpeechToggleHandler,
audioSettingsToggleHandler,
videoSettingsToggleHandler,
acceleratorOptionsToggleHandler,
bytedanceProviderSettingsToggleHandler,
xaiProviderSettingsToggleHandler,
+20 -4
View File
@@ -8,13 +8,25 @@ from PIL import Image
from datetime import datetime
def _is_svg_url(url: str) -> bool:
"""True if URL points to SVG (e.g. from Runware Vectorize)."""
u = (url or "").split("?")[0].lower()
return u.endswith(".svg")
def _bytes_to_image_tensor(img_data: bytes):
"""Decode image bytes to ComfyUI IMAGE tensor [1, H, W, C] float32 0-1."""
"""Decode image bytes to ComfyUI IMAGE tensor [1, H, W, C] float32 0-1. Fails on SVG (use placeholder for SVG)."""
pil = Image.open(io.BytesIO(img_data)).convert("RGB")
arr = np.array(pil).astype(np.float32) / 255.0
return torch.from_numpy(arr).unsqueeze(0)
def _placeholder_image_tensor():
"""Return a 1x1 RGB placeholder tensor [1, 1, 1, 3] for non-raster outputs (e.g. SVG from Vectorize)."""
arr = np.ones((1, 1, 3), dtype=np.float32) * 0.5 # grey
return torch.from_numpy(arr).unsqueeze(0)
class RunwareSaveImage:
"""Runware Save Image: download from URL(s), save/preview to disk, and forward as IMAGE for chaining."""
@@ -23,7 +35,7 @@ class RunwareSaveImage:
return {
"required": {
"Images": ("STRING", {
"tooltip": "Image URL(s) from Runware Image Inference node. For multiple images, URLs should be comma-separated."
"tooltip": "Image URL(s) from Runware Image Inference or Runware Vectorize node. For multiple images, URLs should be comma-separated. SVG URLs (e.g. from Vectorize) are saved as .svg files; a placeholder tensor is passed for the IMAGE output."
}),
"filenamePrefix": ("STRING", {
"default": "ComfyUI",
@@ -69,10 +81,14 @@ class RunwareSaveImage:
response.raise_for_status()
img_data = response.content
tensors.append(_bytes_to_image_tensor(img_data))
# SVG (e.g. from Runware Vectorize) cannot be opened by PIL; save as file and use placeholder tensor
if _is_svg_url(image_url):
tensors.append(_placeholder_image_tensor())
else:
tensors.append(_bytes_to_image_tensor(img_data))
url_without_params = image_url.split("?")[0]
ext = url_without_params.split(".")[-1].lstrip(".") or "png"
ext = url_without_params.split(".")[-1].lstrip(".").lower() or "png"
extension = "." + ext
if len(image_urls) > 1:
filename = f"{filenamePrefix}_{timestamp}_{i+1:03}{extension}"
+165 -39
View File
@@ -1,24 +1,97 @@
import re
from .utils import runwareUtils as rwUtils
class vectorize:
"""Vectorize node for converting images to vector format"""
RUNWARE_VECTORIZE_MODELS = {
"recraft:1@1": "recraft:1@1",
"picsart:1@1": "picsart:1@1",
# Models grouped by architecture (provider), same pattern as video model search
VECTORIZE_MODELS = {
"Recraft": [
"recraft:1@1 (Recraft 1)",
"recraft:v4-pro@vector (Recraft V4 Pro Vector)",
"recraft:v4@vector (Recraft V4 Vector)",
],
"Picsart": [
"picsart:1@1 (Picsart 1)",
],
}
MODEL_ARCHITECTURES = [
"All",
"Recraft",
"Picsart",
]
@classmethod
def _getAllModels(cls):
"""Get all models from all architectures"""
all_models = []
for models in cls.VECTORIZE_MODELS.values():
all_models.extend(models)
return all_models
@classmethod
def INPUT_TYPES(cls):
all_models = cls._getAllModels()
default_model = all_models[0] if all_models else "recraft:1@1 (Recraft 1)"
return {
"required": {
"Image": ("IMAGE", {
"tooltip": "Input image to be vectorized."
"Model Search": ("STRING", {
"tooltip": "Search for a vectorization model by name or AIR code (e.g. recraft, picsart).",
}),
"Model": (list(cls.RUNWARE_VECTORIZE_MODELS.keys()), {
"tooltip": "Select the vectorization model to use.",
"default": "recraft:1@1",
"Model Architecture": (cls.MODEL_ARCHITECTURES, {
"tooltip": "Choose model architecture to filter the model list.",
"default": "Recraft",
}),
"Vector Model List": (all_models, {
"tooltip": "Select the vectorization model. Filter by Model Architecture above.",
"default": default_model,
}),
"Use Search Value": ("BOOLEAN", {
"tooltip": "When enabled, the value in Model Search is used as the model AIR code instead of the list selection.",
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
}),
},
"optional": {
"Image": ("IMAGE", {
"tooltip": "Optional input image to be vectorized."
}),
"dimensions": ([
"None", "Square HD (1024x1024)", "Custom"
], {
"default": "None",
"tooltip": "Output dimensions preset. Select 'None' for model default, or a preset to set width/height. Use 'Custom' with width/height inputs.",
}),
"width": ("INT", {
"tooltip": "Width in pixels. Used when dimensions is not 'None' (or with Custom).",
"default": 1024,
"min": 128,
"max": 6048,
"step": 1,
}),
"height": ("INT", {
"tooltip": "Height in pixels. Used when dimensions is not 'None' (or with Custom).",
"default": 1024,
"min": 128,
"max": 6048,
"step": 1,
}),
"Use positivePrompt": ("BOOLEAN", {
"tooltip": "When enabled, include positivePrompt in the vectorize request.",
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
}),
"positivePrompt": ("STRING", {
"multiline": True,
"tooltip": "Optional text prompt for vectorization. Only sent when Use Positive Prompt is enabled.",
}),
"providerSettings": ("RUNWAREPROVIDERSETTINGS", {
"tooltip": "Connect a Runware Provider Settings Node to configure provider-specific parameters (e.g. Recraft, Picsart).",
}),
},
}
@@ -29,51 +102,104 @@ class vectorize:
RETURN_NAMES = ("IMAGE",)
CATEGORY = "Runware"
@classmethod
def VALIDATE_INPUTS(cls, **kwargs):
return True
def vectorizeImage(self, **kwargs):
"""Vectorize image and return SVG URL"""
image = kwargs.get("Image")
modelName = kwargs.get("Model", "recraft:1@1")
model = self.RUNWARE_VECTORIZE_MODELS.get(modelName, "recraft:1@1")
imageUuid = rwUtils.convertTensor2IMG(image)
genConfig = self._buildGenConfig(model, imageUuid)
use_search_value = kwargs.get("Use Search Value", False)
search_input = kwargs.get("Model Search", "")
current_model = kwargs.get("Vector Model List", "recraft:1@1 (Recraft 1)")
if use_search_value and search_input and str(search_input).strip():
model_air_code = str(search_input).strip()
else:
# Extract AIR code from "airId (Display Name)" format
model_air_code = current_model.split(" (")[0].strip()
image_uuid = rwUtils.convertTensor2IMG(image) if image is not None else None
width = kwargs.get("width")
height = kwargs.get("height")
dimensions = kwargs.get("dimensions", "None")
use_positive_prompt = kwargs.get("Use positivePrompt", False)
positive_prompt = kwargs.get("positivePrompt")
provider_settings = kwargs.get("providerSettings")
gen_config = self._buildGenConfig(
model_air_code, image_uuid,
width=width, height=height, dimensions=dimensions,
use_positive_prompt=use_positive_prompt, positive_prompt=positive_prompt,
provider_settings=provider_settings,
)
print(f"[DEBUG] Sending Vectorize Request:")
print(f"[DEBUG] Request Payload: {rwUtils.safe_json_dumps(genConfig, indent=2)}")
genResult = rwUtils.inferenecRequest(genConfig)
print(f"[DEBUG] Request Payload: {rwUtils.safe_json_dumps(gen_config, indent=2)}")
gen_result = rwUtils.inferenecRequest(gen_config)
print(f"[DEBUG] Received Vectorize Response:")
print(f"[DEBUG] Response: {rwUtils.safe_json_dumps(genResult, indent=2)}")
self._validateResponse(genResult)
svg_url = rwUtils.extractImageURLs(genResult)
print(f"[DEBUG] Response: {rwUtils.safe_json_dumps(gen_result, indent=2)}")
self._validateResponse(gen_result)
svg_url = rwUtils.extractImageURLs(gen_result)
return (svg_url,)
def _buildGenConfig(self, model, imageUuid):
"""Build generation configuration for API request"""
return [{
@staticmethod
def _parseDimensionsPreset(dimensions):
"""Parse 'Label (WxH)' to (width, height) or None if not a preset."""
if not dimensions or dimensions in ("None", "Custom"):
return None, None
m = re.search(r"\((\d+)x(\d+)\)", dimensions)
if m:
return int(m.group(1)), int(m.group(2))
return None, None
def _buildGenConfig(self, model, image_uuid, width=None, height=None, dimensions="None",
use_positive_prompt=False, positive_prompt=None, provider_settings=None):
"""Build generation configuration for API request. image_uuid, width, height, positivePrompt, providerSettings are optional."""
inputs = {}
if image_uuid is not None:
inputs["image"] = image_uuid
payload = {
"taskType": "vectorize",
"taskUUID": rwUtils.genRandUUID(),
"model": model,
"inputs": {
"image": imageUuid
},
"inputs": inputs,
"outputType": "URL",
"outputFormat": "svg",
}]
}
if dimensions and dimensions != "None":
w, h = width, height
if w is None or h is None:
pw, ph = self._parseDimensionsPreset(dimensions)
if pw is not None and ph is not None:
w, h = pw, ph
if w is not None and h is not None:
payload["width"] = w
payload["height"] = h
if use_positive_prompt and positive_prompt is not None and str(positive_prompt).strip():
payload["positivePrompt"] = str(positive_prompt).strip()
# Handle providerSettings - extract provider name from model and merge (same as image inference)
if provider_settings is not None:
provider_name = model.split(":")[0] if ":" in model else model
if isinstance(provider_settings, dict):
if any(key in provider_settings for key in ["alibaba", "bytedance", "klingai", "openai", "pixverse",
"bria", "lightricks", "luma", "minimax", "runway", "vidu",
"elevenlabs", "bfl", "xai", "recraft", "picsart", "sourceful"]):
payload["providerSettings"] = provider_settings
else:
payload["providerSettings"] = {provider_name: provider_settings}
else:
payload["providerSettings"] = provider_settings
return [payload]
def _validateResponse(self, genResult):
def _validateResponse(self, gen_result):
"""Validate API response"""
if "errors" in genResult:
errorMessage = genResult["errors"][0]["message"]
raise Exception(f"Vectorization failed: {errorMessage}")
if "errors" in gen_result:
error_message = gen_result["errors"][0]["message"]
raise Exception(f"Vectorization failed: {error_message}")
# Node class mappings
NODE_CLASS_MAPPINGS = {
"RunwareVectorize": vectorize,
}
+8
View File
@@ -144,6 +144,9 @@ class txt2vid:
"Accelerator": ("RUNWAREACCELERATOR", {
"tooltip": "Connect a Runware Accelerator Options Node to configure caching and acceleration settings.",
}),
"settings": ("RUNWAREVIDEOSETTINGS", {
"tooltip": "Connect a Runware Video Inference Settings node to configure draft, audio, and promptUpsampling.",
}),
}
}
@@ -181,6 +184,7 @@ class txt2vid:
safetyInputs = kwargs.get("safetyInputs", None)
videoAdvancedFeatureInputs = kwargs.get("videoAdvancedFeatureInputs", None)
runwareAccelerator = kwargs.get("Accelerator", None)
settings = kwargs.get("settings", None)
useDuration = kwargs.get("useDuration", True)
duration = kwargs.get("duration", 5)
fps = kwargs.get("fps", 24)
@@ -350,6 +354,10 @@ class txt2vid:
# Add accelerator options if provided
if runwareAccelerator is not None and isinstance(runwareAccelerator, dict) and len(runwareAccelerator) > 0:
genConfig[0]["acceleratorOptions"] = runwareAccelerator
# Add settings if provided (draft, audio, promptUpsampling)
if settings is not None and isinstance(settings, dict) and len(settings) > 0:
genConfig[0]["settings"] = settings
# Add acceleration if not "none"
if acceleration and acceleration != "none":
+13 -3
View File
@@ -117,6 +117,9 @@ class videoModelSearch:
"VEED": [
"veed:fabric@1.0 (VEED Fabric 1.0)",
],
"Pruna": [
"prunaai:p-video@0 (P-Video)",
],
}
# Model dimensions mapping
@@ -234,6 +237,9 @@ class videoModelSearch:
# VEED Models (preserves input aspect ratio; resolution = shorter side)
"veed:fabric@1.0": {"width": 1280, "height": 720},
# Pruna Models (720p)
"prunaai:p-video@0": {"width": 1280, "height": 720},
}
# Model resolutions mapping
@@ -349,10 +355,13 @@ class videoModelSearch:
# xAI Models
"xai:grok-imagine@video": "480p",
# VEED Models
# VEED Models
"veed:fabric@1.0": "720p",
# Pruna Models
"prunaai:p-video@0": "720p",
}
MODEL_ARCHITECTURES = [
"All",
"KlingAI",
@@ -373,7 +382,8 @@ class videoModelSearch:
"Hunyuan",
"Kandinsky",
"xAI",
"VEED"
"VEED",
"Pruna",
]
DEFAULT_DIMENSIONS = {"width": 1024, "height": 576}
+83
View File
@@ -0,0 +1,83 @@
"""
Runware Video Inference Settings Node
Provides settings (draft, audio, promptUpsampling) for Runware Video Inference.
"""
from typing import Dict, Any
class RunwareVideoSettings:
"""Runware Video Inference Settings Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"optional": {
"useDraft": ("BOOLEAN", {
"tooltip": "Enable to include draft (lower-quality preview mode) in video inference settings.",
"default": False,
}),
"draft": ("BOOLEAN", {
"tooltip": "Draft mode for lower-quality preview. Only used when 'Use Draft' is enabled.",
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
}),
"useAudio": ("BOOLEAN", {
"tooltip": "Enable to include audio (save video with audio) in video inference settings.",
"default": True,
}),
"audio": ("BOOLEAN", {
"tooltip": "Save the video with audio. Only used when 'Use Audio' is enabled.",
"default": True,
"label_on": "Enabled",
"label_off": "Disabled",
}),
"usePromptUpsampling": ("BOOLEAN", {
"tooltip": "Enable to include promptUpsampling (enhance prompt automatically) in video inference settings.",
"default": True,
}),
"promptUpsampling": ("BOOLEAN", {
"tooltip": "Enhance prompt automatically for better results. Only used when 'Use Prompt Upsampling' is enabled.",
"default": True,
"label_on": "Enabled",
"label_off": "Disabled",
}),
}
}
RETURN_TYPES = ("RUNWAREVIDEOSETTINGS",)
RETURN_NAMES = ("settings",)
FUNCTION = "createSettings"
CATEGORY = "Runware"
DESCRIPTION = "Configure video inference settings (draft, audio, promptUpsampling) for Runware Video Inference. Connect to Runware Video Inference node."
def createSettings(self, **kwargs) -> tuple:
"""Create settings dict for Video Inference API"""
use_draft = kwargs.get("useDraft", False)
draft = kwargs.get("draft", False)
use_audio = kwargs.get("useAudio", True)
audio = kwargs.get("audio", True)
use_prompt_upsampling = kwargs.get("usePromptUpsampling", True)
prompt_upsampling = kwargs.get("promptUpsampling", True)
settings: Dict[str, Any] = {}
if use_draft:
settings["draft"] = bool(draft)
if use_audio:
settings["audio"] = bool(audio)
if use_prompt_upsampling:
settings["promptUpsampling"] = bool(prompt_upsampling)
return (settings,)
NODE_CLASS_MAPPINGS = {
"RunwareVideoSettings": RunwareVideoSettings,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareVideoSettings": "Runware Video Inference Settings",
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"id":"993f4b88-acb5-4f70-968f-e3a2c9c1c5b4","revision":0,"last_node_id":19,"last_link_id":17,"nodes":[{"id":9,"type":"Runware Save Image","pos":[541.6531372070312,656.4539184570312],"size":[270,318],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"Images","name":"Images","type":"STRING","widget":{"name":"Images"},"link":15},{"localized_name":"filenamePrefix","name":"filenamePrefix","type":"STRING","widget":{"name":"filenamePrefix"},"link":null},{"localized_name":"saveImage","name":"saveImage","type":"BOOLEAN","widget":{"name":"saveImage"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":null}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"91646ada844167e36188b535ff60063735d10ca3","Node name for S&R":"Runware Save Image"},"widgets_values":["","ComfyUI",true],"bgcolor":"#5345bf"},{"id":19,"type":"Runware Provider Settings Recraft Color","pos":[-305.0177001953125,914.2234497070312],"size":[352.7818298339844,106],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"r","name":"r","shape":7,"type":"INT","widget":{"name":"r"},"link":null},{"localized_name":"g","name":"g","shape":7,"type":"INT","widget":{"name":"g"},"link":null},{"localized_name":"b","name":"b","shape":7,"type":"INT","widget":{"name":"b"},"link":null}],"outputs":[{"localized_name":"Color","name":"Color","type":"RUNWARERECRAFTCOLOR","links":[16]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"dee0949f78f02adb54037e1df1cc2bf7df7b4bfe","Node name for S&R":"Runware Provider Settings Recraft Color"},"widgets_values":[0,0,128],"bgcolor":"#5345bf"},{"id":18,"type":"Runware Recraft Provider Settings","pos":[-282.6740417480469,646.2836303710938],"size":[315.4371032714844,182],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"color1","name":"color1","shape":7,"type":"RUNWARERECRAFTCOLOR","link":null},{"localized_name":"color2","name":"color2","shape":7,"type":"RUNWARERECRAFTCOLOR","link":null},{"localized_name":"color3","name":"color3","shape":7,"type":"RUNWARERECRAFTCOLOR","link":null},{"localized_name":"color4","name":"color4","shape":7,"type":"RUNWARERECRAFTCOLOR","link":null},{"localized_name":"color5","name":"color5","shape":7,"type":"RUNWARERECRAFTCOLOR","link":null},{"localized_name":"backgroundColor","name":"backgroundColor","shape":7,"type":"RUNWARERECRAFTCOLOR","link":16},{"localized_name":"useStyleId","name":"useStyleId","shape":7,"type":"BOOLEAN","widget":{"name":"useStyleId"},"link":null},{"localized_name":"styleId","name":"styleId","shape":7,"type":"STRING","widget":{"name":"styleId"},"link":null}],"outputs":[{"localized_name":"Provider Settings","name":"Provider Settings","type":"RUNWAREPROVIDERSETTINGS","links":[17]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"dee0949f78f02adb54037e1df1cc2bf7df7b4bfe","Node name for S&R":"Runware Recraft Provider Settings"},"widgets_values":[false,""],"bgcolor":"#5345bf"},{"id":17,"type":"Runware Vectorize","pos":[72.84228515625,650.274658203125],"size":[400,300],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"Image","name":"Image","shape":7,"type":"IMAGE","link":null},{"localized_name":"providerSettings","name":"providerSettings","shape":7,"type":"RUNWAREPROVIDERSETTINGS","link":17},{"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":"Vector Model List","name":"Vector Model List","type":"COMBO","widget":{"name":"Vector Model List"},"link":null},{"localized_name":"Use Search Value","name":"Use Search Value","type":"BOOLEAN","widget":{"name":"Use Search Value"},"link":null},{"localized_name":"dimensions","name":"dimensions","shape":7,"type":"COMBO","widget":{"name":"dimensions"},"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":"Use positivePrompt","name":"Use positivePrompt","shape":7,"type":"BOOLEAN","widget":{"name":"Use positivePrompt"},"link":null},{"localized_name":"positivePrompt","name":"positivePrompt","shape":7,"type":"STRING","widget":{"name":"positivePrompt"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"STRING","links":[15]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"dee0949f78f02adb54037e1df1cc2bf7df7b4bfe","Node name for S&R":"Runware Vectorize"},"widgets_values":["","Recraft","recraft:v4@vector (Recraft V4 Vector)",false,"Square HD (1024x1024)",1024,1024,true,"Retro 1970s style poster with the text 'Recraft V4'"],"bgcolor":"#5345bf"}],"links":[[15,17,0,9,0,"STRING"],[16,19,0,18,5,"RUNWARERECRAFTCOLOR"],[17,18,0,17,1,"RUNWAREPROVIDERSETTINGS"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.02884607845781,"offset":[484.37043720120533,-463.73196868190934]}},"version":0.4}
File diff suppressed because one or more lines are too long