add workflows, etc
This commit is contained in:
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user