Merge pull request #72 from Runware/feature-KlingReferenceVoices/wanAnimateAdvancedFeatureSettings

Add Wan Animate advanced feature settings, reference voices
This commit is contained in:
Sirshendu Ganguly
2025-12-30 20:27:28 -05:00
committed by GitHub
14 changed files with 453 additions and 14 deletions
+4
View File
@@ -48,6 +48,7 @@ from .modules.audioInputCombine import RunwareAudioInputCombine
from .modules.speechInput import RunwareSpeechInput
from .modules.speechInputCombine import RunwareSpeechInputCombine
from .modules.referenceVideos import referenceVideos
from .modules.referenceVoices import referenceVoices
from .modules.videoInferenceInputs import videoInferenceInputs
from .modules.providerSettings.lightricksProviderSettings import RunwareLightricksProviderSettings
from .modules.providerSettings.minimaxProviderSettings import RunwareMiniMaxProviderSettings
@@ -68,6 +69,7 @@ from .modules.videoInputsFrame import RunwareVideoInputsFrameImages
from .modules.safetyInputs import safetyInputs
from .modules.settings import RunwareSettings
from .modules.videoAdvancedFeatureInputs import videoAdvancedFeatureInputs
from .modules.advancedFeatureSettings.wanAnimateAdvancedFeatureSettings import RunwareWanAnimateAdvancedFeatureSettings
RUNWARE_COMFYUI_VERSION = "1.4.0 Beta"
@@ -130,6 +132,7 @@ NODE_CLASS_MAPPINGS = {
"Runware Video Speech Input": RunwareSpeechInput,
"Runware Speech Inputs Combine": RunwareSpeechInputCombine,
"Runware Reference Videos": referenceVideos,
"Runware Reference Voices": referenceVoices,
"Runware Video Inference Inputs": videoInferenceInputs,
"Runware Lightricks Provider Settings": RunwareLightricksProviderSettings,
"Runware MiniMax Provider Settings": RunwareMiniMaxProviderSettings,
@@ -150,6 +153,7 @@ NODE_CLASS_MAPPINGS = {
"Runware Safety Inputs": safetyInputs,
"Runware Settings": RunwareSettings,
"Runware Video Advanced Feature Inputs": videoAdvancedFeatureInputs,
"Runware Wan Animate Advanced Feature Settings": RunwareWanAnimateAdvancedFeatureSettings,
}
NODE_DISPLAY_NAME_MAPPINGS = {
+5 -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, videoTranscriptionHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, useParameterToggleHandler, imageInferenceToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler } from "./utils.js";
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, videoTranscriptionHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, useParameterToggleHandler, imageInferenceToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler } from "./utils.js";
import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js";
const nodeInitList = [];
@@ -87,6 +87,10 @@ app.registerExtension({
speechInputToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.BRIAPROVIDERMASK) {
briaProviderMaskToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.WANANIMATEADVANCEDFEATURESETTINGS) {
wanAnimateAdvancedFeatureSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.VIDEOADVANCEDFEATUREINPUTS) {
videoAdvancedFeatureInputsToggleHandler(node);
}
if(crNodeProps.colorModeOnly === true) return;
+10
View File
@@ -96,6 +96,7 @@ const RUNWARE_NODE_TYPES = {
AUDIOINPUT: "Runware Video Audio Input",
SPEECHINPUT: "Runware Video Speech Input",
REFERENCEVIDEOS: "Runware Reference Videos",
REFERENCEVOICES: "Runware Reference Voices",
VIDEOINFERENCEINPUTS: "Runware Video Inference Inputs",
LIGHTRICKSPROVIDERSETTINGS: "Runware Lightricks Provider Settings",
KLINGPROVIDERSETTINGS: "Runware KlingAI Provider Settings",
@@ -118,6 +119,7 @@ const RUNWARE_NODE_TYPES = {
SAFETYINPUTS: "Runware Safety Inputs",
SETTINGS: "Runware Settings",
VIDEOADVANCEDFEATUREINPUTS: "Runware Video Advanced Feature Inputs",
WANANIMATEADVANCEDFEATURESETTINGS: "Runware Wan Animate Advanced Feature Settings",
};
const RUNWARE_NODE_PROPS = {
@@ -316,6 +318,10 @@ const RUNWARE_NODE_PROPS = {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
[RUNWARE_NODE_TYPES.REFERENCEVOICES]: {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
[RUNWARE_NODE_TYPES.VIDEOINFERENCEINPUTS]: {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
@@ -405,6 +411,10 @@ const RUNWARE_NODE_PROPS = {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
[RUNWARE_NODE_TYPES.WANANIMATEADVANCEDFEATURESETTINGS]: {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
};
export {
+214
View File
@@ -1718,6 +1718,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"Wan": [
"runware:200@1 (Wan 2.1 1.3B)", "runware:200@2 (Wan 2.1 14B)",
"runware:200@6 (Wan 2.2)",
"runware:200@8 (Wan 2.2 A14B Animate)",
"alibaba:wan@2.6 (Wan 2.6)",
],
"OpenAI": [
@@ -1794,6 +1795,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"runware:200@1": {"width": 853, "height": 480},
"runware:200@2": {"width": 853, "height": 480},
"runware:200@6": {"width": 1280, "height": 720},
"runware:200@8": {"width": 1104, "height": 832},
"alibaba:wan@2.6": {"width": 1280, "height": 720},
"openai:3@1": {"width": 1280, "height": 720},
"openai:3@0": {"width": 1280, "height": 720},
@@ -1857,6 +1859,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"runware:200@1": "480p",
"runware:200@2": "480p",
"runware:200@6": "720p",
"runware:200@8": "720p",
"alibaba:wan@2.6": "720p",
"openai:3@1": "720p",
"openai:3@0": "720p",
@@ -2127,6 +2130,10 @@ function klingProviderSettingsToggleHandler(klingNode) {
const asmrModeWidget = klingNode.widgets.find(w => w.name === "asmrMode");
const useKeepOriginalSoundWidget = klingNode.widgets.find(w => w.name === "useKeepOriginalSound");
const keepOriginalSoundWidget = klingNode.widgets.find(w => w.name === "keepOriginalSound");
const useSoundWidget = klingNode.widgets.find(w => w.name === "useSound");
const soundWidget = klingNode.widgets.find(w => w.name === "sound");
const useCharacterOrientationWidget = klingNode.widgets.find(w => w.name === "useCharacterOrientation");
const characterOrientationWidget = klingNode.widgets.find(w => w.name === "characterOrientation");
// Helper function to toggle widget enabled state (EXACT same pattern as imageInferenceToggleHandler)
function toggleWidgetState(useWidget, paramWidget, paramName) {
@@ -2202,6 +2209,14 @@ function klingProviderSettingsToggleHandler(klingNode) {
if (useKeepOriginalSoundWidget && keepOriginalSoundWidget) {
toggleWidgetState(useKeepOriginalSoundWidget, keepOriginalSoundWidget, "keepOriginalSound");
}
if (useSoundWidget && soundWidget) {
toggleWidgetState(useSoundWidget, soundWidget, "sound");
}
if (useCharacterOrientationWidget && characterOrientationWidget) {
toggleWidgetState(useCharacterOrientationWidget, characterOrientationWidget, "characterOrientation");
}
}
function alibabaProviderSettingsToggleHandler(alibabaNode) {
@@ -2752,6 +2767,203 @@ function briaProviderMaskToggleHandler(maskNode) {
}
}
function wanAnimateAdvancedFeatureSettingsToggleHandler(wanAnimateNode) {
// Find widgets
const useModeWidget = wanAnimateNode.widgets.find(w => w.name === "useMode");
const modeWidget = wanAnimateNode.widgets.find(w => w.name === "mode");
const useRetargetPoseWidget = wanAnimateNode.widgets.find(w => w.name === "useRetargetPose");
const retargetPoseWidget = wanAnimateNode.widgets.find(w => w.name === "retargetPose");
const usePrevSegCondFramesWidget = wanAnimateNode.widgets.find(w => w.name === "usePrevSegCondFrames");
const prevSegCondFramesWidget = wanAnimateNode.widgets.find(w => w.name === "prevSegCondFrames");
// Helper function to toggle widget enabled state
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;
}
// Handle dropdown widgets (for mode field)
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;
// Fallback: try to find inputs via DOM if inputEl is not available
if (!paramWidget.inputEl && paramName) {
const nodeElement = wanAnimateNode.htmlElements?.widgetsContainer || wanAnimateNode.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";
}
}
}
}
wanAnimateNode.setDirtyCanvas(true);
}
// Set up callback
appendWidgetCB(useWidget, () => {
setTimeout(toggleEnabled, 50);
});
// Initial call to set initial state
setTimeout(toggleEnabled, 100);
}
// Set up toggle handler for mode
if (useModeWidget && modeWidget) {
toggleWidgetState(useModeWidget, modeWidget, "mode");
}
// Set up toggle handler for retargetPose (with mode dependency)
if (useRetargetPoseWidget && retargetPoseWidget) {
function toggleRetargetPoseEnabled() {
const useRetargetPoseEnabled = useRetargetPoseWidget.value === true;
const modeValue = modeWidget ? modeWidget.value : "animate";
// retargetPose is only supported for animate mode
const enabled = useRetargetPoseEnabled && modeValue === "animate";
if (retargetPoseWidget.inputEl) {
retargetPoseWidget.inputEl.disabled = !enabled;
retargetPoseWidget.inputEl.style.opacity = enabled ? "1" : "0.5";
retargetPoseWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed";
retargetPoseWidget.inputEl.readOnly = !enabled;
}
retargetPoseWidget.disabled = !enabled;
// Fallback: try to find inputs via DOM
if (!retargetPoseWidget.inputEl) {
const nodeElement = wanAnimateNode.htmlElements?.widgetsContainer || wanAnimateNode.htmlElements;
if (nodeElement) {
const input = nodeElement.querySelector(`input[name="retargetPose"], textarea[name="retargetPose"], select[name="retargetPose"]`);
if (input) {
input.disabled = !enabled;
input.style.opacity = enabled ? "1" : "0.5";
input.style.cursor = enabled ? "text" : "not-allowed";
input.readOnly = !enabled;
}
}
}
wanAnimateNode.setDirtyCanvas(true);
}
// Set up callback for useRetargetPose toggle
appendWidgetCB(useRetargetPoseWidget, () => {
setTimeout(toggleRetargetPoseEnabled, 50);
});
// Set up callback for mode change (to disable retargetPose when mode is "replace")
if (modeWidget) {
appendWidgetCB(modeWidget, () => {
setTimeout(toggleRetargetPoseEnabled, 50);
});
}
// Initial call to set initial state
setTimeout(toggleRetargetPoseEnabled, 100);
}
// Set up toggle handler for prevSegCondFrames
if (usePrevSegCondFramesWidget && prevSegCondFramesWidget) {
toggleWidgetState(usePrevSegCondFramesWidget, prevSegCondFramesWidget, "prevSegCondFrames");
}
}
function videoAdvancedFeatureInputsToggleHandler(advancedFeatureNode) {
// Helper function to toggle widget enabled state
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;
}
paramWidget.disabled = !enabled;
// Fallback: try to find inputs via DOM if inputEl is not available
if (!paramWidget.inputEl && paramName) {
const nodeElement = advancedFeatureNode.htmlElements?.widgetsContainer || advancedFeatureNode.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;
}
}
}
advancedFeatureNode.setDirtyCanvas(true);
}
// Set up callback
appendWidgetCB(useWidget, () => {
setTimeout(toggleEnabled, 50);
});
// Initial call to set initial state
setTimeout(toggleEnabled, 100);
}
// Set up toggle handlers for each parameter
const useVideoCFGScaleWidget = advancedFeatureNode.widgets.find(w => w.name === "useVideoCFGScale");
const videoCFGScaleWidget = advancedFeatureNode.widgets.find(w => w.name === "videoCFGScale");
if (useVideoCFGScaleWidget && videoCFGScaleWidget) {
toggleWidgetState(useVideoCFGScaleWidget, videoCFGScaleWidget, "videoCFGScale");
}
const useAudioCFGScaleWidget = advancedFeatureNode.widgets.find(w => w.name === "useAudioCFGScale");
const audioCFGScaleWidget = advancedFeatureNode.widgets.find(w => w.name === "audioCFGScale");
if (useAudioCFGScaleWidget && audioCFGScaleWidget) {
toggleWidgetState(useAudioCFGScaleWidget, audioCFGScaleWidget, "audioCFGScale");
}
const useVideoNegativePromptWidget = advancedFeatureNode.widgets.find(w => w.name === "useVideoNegativePrompt");
const videoNegativePromptWidget = advancedFeatureNode.widgets.find(w => w.name === "videoNegativePrompt");
if (useVideoNegativePromptWidget && videoNegativePromptWidget) {
toggleWidgetState(useVideoNegativePromptWidget, videoNegativePromptWidget, "videoNegativePrompt");
}
const useAudioNegativePromptWidget = advancedFeatureNode.widgets.find(w => w.name === "useAudioNegativePrompt");
const audioNegativePromptWidget = advancedFeatureNode.widgets.find(w => w.name === "audioNegativePrompt");
if (useAudioNegativePromptWidget && audioNegativePromptWidget) {
toggleWidgetState(useAudioNegativePromptWidget, audioNegativePromptWidget, "audioNegativePrompt");
}
const useSlgLayerWidget = advancedFeatureNode.widgets.find(w => w.name === "useSlgLayer");
const slgLayerWidget = advancedFeatureNode.widgets.find(w => w.name === "slgLayer");
if (useSlgLayerWidget && slgLayerWidget) {
toggleWidgetState(useSlgLayerWidget, slgLayerWidget, "slgLayer");
}
}
export {
notifyUser,
promptEnhanceHandler,
@@ -2786,6 +2998,8 @@ export {
audioInputToggleHandler,
speechInputToggleHandler,
briaProviderMaskToggleHandler,
wanAnimateAdvancedFeatureSettingsToggleHandler,
videoAdvancedFeatureInputsToggleHandler,
};
function googleProviderSettingsToggleHandler(googleNode) {
@@ -0,0 +1,2 @@
# Advanced Feature Settings module
@@ -0,0 +1,99 @@
"""
Runware Wan Animate Advanced Feature Settings Node
Provides Wan Animate-specific settings for video generation
"""
from typing import Optional, Dict, Any
class RunwareWanAnimateAdvancedFeatureSettings:
"""Runware Wan Animate Advanced Feature Settings Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"optional": {
"useMode": ("BOOLEAN", {
"tooltip": "Enable to include mode parameter in API request.",
"default": True,
}),
"mode": (["animate", "replace"], {
"tooltip": "Animation mode: 'animate' uses pose detector and skeleton retargeting to animate a character from a reference image. 'replace' uses pose detector and segmentation model to replace a character in existing footage. Only used when 'Use Mode' is enabled.",
"default": "animate",
}),
"useRetargetPose": ("BOOLEAN", {
"tooltip": "Enable to include retargetPose parameter in API request. Only available for animate mode.",
"default": False,
}),
"retargetPose": ("BOOLEAN", {
"tooltip": "Retarget the pose of video image to match the reference image initial pose. That is achieved moving the hands' bones according to video. Means if the character from video moves his hands it will take the initial reference image pose and change the position of bones according to video movements. Supported only for animate mode. Only used when 'Use Retarget Pose' is enabled.",
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
}),
"usePrevSegCondFrames": ("BOOLEAN", {
"tooltip": "Enable to include prevSegCondFrames parameter in API request.",
"default": False,
}),
"prevSegCondFrames": ("INT", {
"tooltip": "How many frames to take from previous segment to keep temporal consistency. Less -> less consistent between the segments. More -> more time for inference. Only used when 'Use Prev Seg Cond Frames' is enabled.",
"default": 1,
"min": 1,
"max": 5,
}),
}
}
RETURN_TYPES = ("RUNWAREWANANIMATEADVANCEDFEATURESETTINGS",)
RETURN_NAMES = ("Wan Animate Advanced Feature Settings",)
FUNCTION = "create_wan_animate_settings"
CATEGORY = "Runware/Advanced Feature Settings"
DESCRIPTION = "Configure Wan Animate advanced feature settings including mode (animate/replace), pose retargeting, and previous segment conditioning frames for character animation and replacement."
def create_wan_animate_settings(self, **kwargs) -> tuple[Dict[str, Any]]:
"""Create Wan Animate advanced feature settings"""
# Get use parameters
useMode = kwargs.get("useMode", True)
useRetargetPose = kwargs.get("useRetargetPose", False)
usePrevSegCondFrames = kwargs.get("usePrevSegCondFrames", False)
# Get actual parameters
mode = kwargs.get("mode", "animate")
retargetPose = kwargs.get("retargetPose", False)
prevSegCondFrames = kwargs.get("prevSegCondFrames", 1)
# Build settings dictionary
wanAnimateSettings = {}
# Add mode only if useMode is enabled
if useMode and mode is not None:
wanAnimateSettings["mode"] = mode
# Add retargetPose only if useRetargetPose is enabled
# Note: retargetPose is only supported for animate mode, but we'll let the API handle validation
if useRetargetPose:
wanAnimateSettings["retargetPose"] = retargetPose
# Add prevSegCondFrames only if usePrevSegCondFrames is enabled
if usePrevSegCondFrames:
wanAnimateSettings["prevSegCondFrames"] = prevSegCondFrames
# Clean up None values
wanAnimateSettings = {k: v for k, v in wanAnimateSettings.items() if v is not None}
# Return the full structure with the feature name as key (similar to providerSettings pattern)
if wanAnimateSettings:
return ({"wanAnimate": wanAnimateSettings},)
else:
return ({},)
# Node class mappings
NODE_CLASS_MAPPINGS = {
"RunwareWanAnimateAdvancedFeatureSettings": RunwareWanAnimateAdvancedFeatureSettings,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareWanAnimateAdvancedFeatureSettings": "Runware Wan Animate Advanced Feature Settings",
}
@@ -90,6 +90,14 @@ class RunwareKlingProviderSettings:
"label_on": "Enabled",
"label_off": "Disabled",
}),
"useCharacterOrientation": ("BOOLEAN", {
"tooltip": "Enable to include characterOrientation parameter in provider settings",
"default": False,
}),
"characterOrientation": (["image", "video"], {
"tooltip": "Character orientation mode. 'image': has the same orientation as the person in the picture (reference video duration should not exceed 10 seconds). 'video': consistent with the orientation of the characters in the video (reference video duration should not exceed 30 seconds). Only used when 'Use Character Orientation' is enabled.",
"default": "image",
}),
}
}
@@ -111,6 +119,7 @@ class RunwareKlingProviderSettings:
useAsmrMode = kwargs.get("useAsmrMode", False)
useKeepOriginalSound = kwargs.get("useKeepOriginalSound", False)
useSound = kwargs.get("useSound", False)
useCharacterOrientation = kwargs.get("useCharacterOrientation", False)
# Get value parameters
cameraControl = kwargs.get("cameraControl", "none")
@@ -121,6 +130,7 @@ class RunwareKlingProviderSettings:
asmrMode = kwargs.get("asmrMode", False)
keepOriginalSound = kwargs.get("keepOriginalSound", True)
sound = kwargs.get("sound", False)
characterOrientation = kwargs.get("characterOrientation", "image")
# Build settings dictionary - only include what is enabled
klingSettings: Dict[str, Any] = {}
@@ -157,6 +167,10 @@ class RunwareKlingProviderSettings:
if useSound:
klingSettings["sound"] = sound
# Add characterOrientation only if useCharacterOrientation is enabled
if useCharacterOrientation and characterOrientation:
klingSettings["characterOrientation"] = characterOrientation
# Clean up None values
klingSettings = {k: v for k, v in klingSettings.items() if v is not None}
+43
View File
@@ -0,0 +1,43 @@
from typing import List
from .utils import runwareUtils as rwUtils
class referenceVoices:
"""Reference Voices node for configuring reference voice inputs"""
MAX_VOICES = 4
@classmethod
def INPUT_TYPES(cls):
optionalInputs = {}
for i in range(1, cls.MAX_VOICES + 1):
ordinal = rwUtils.getOrdinal(i)
optionalInputs[f"Voice{i}"] = ("STRING", {
"tooltip": f"URL or mediaUUID for the {ordinal} reference voice from Runware Media Upload node or external URL.",
})
return {
"required": {},
"optional": optionalInputs
}
DESCRIPTION = "Configure multiple reference voice inputs (URLs or mediaUUIDs) for video generation."
FUNCTION = "createReferenceVoices"
RETURN_TYPES = ("RUNWAREREFERENCEVOICES",)
RETURN_NAMES = ("Reference Voices",)
CATEGORY = "Runware"
def createReferenceVoices(self, **kwargs) -> tuple[List[str]]:
"""Create list of reference voice URLs/UUIDs"""
voiceList = []
for i in range(1, self.MAX_VOICES + 1):
voiceKey = f"Voice{i}"
voiceUrl = kwargs.get(voiceKey)
if voiceUrl and voiceUrl.strip() != "":
voiceList.append(voiceUrl.strip())
return (voiceList,)
+49 -12
View File
@@ -8,36 +8,59 @@ class videoAdvancedFeatureInputs:
return {
"required": {},
"optional": {
"useVideoCFGScale": ("BOOLEAN", {
"tooltip": "Enable to include videoCFGScale parameter in API request.",
"default": False,
}),
"videoCFGScale": ("FLOAT", {
"tooltip": "CFG scale for video generation.",
"tooltip": "CFG scale for video generation. Only used when 'Use Video CFG Scale' is enabled.",
"default": None,
"min": 0.0,
"max": 30.0,
"step": 0.1,
}),
"useAudioCFGScale": ("BOOLEAN", {
"tooltip": "Enable to include audioCFGScale parameter in API request.",
"default": False,
}),
"audioCFGScale": ("FLOAT", {
"tooltip": "CFG scale for audio generation.",
"tooltip": "CFG scale for audio generation. Only used when 'Use Audio CFG Scale' is enabled.",
"default": None,
"min": 0.0,
"max": 30.0,
"step": 0.1,
}),
"useVideoNegativePrompt": ("BOOLEAN", {
"tooltip": "Enable to include videoNegativePrompt parameter in API request.",
"default": False,
}),
"videoNegativePrompt": ("STRING", {
"tooltip": "Negative prompt for video generation.",
"tooltip": "Negative prompt for video generation. Only used when 'Use Video Negative Prompt' is enabled.",
"multiline": True,
"default": "",
}),
"useAudioNegativePrompt": ("BOOLEAN", {
"tooltip": "Enable to include audioNegativePrompt parameter in API request.",
"default": False,
}),
"audioNegativePrompt": ("STRING", {
"tooltip": "Negative prompt for audio generation.",
"tooltip": "Negative prompt for audio generation. Only used when 'Use Audio Negative Prompt' is enabled.",
"multiline": True,
"default": "",
}),
"useSlgLayer": ("BOOLEAN", {
"tooltip": "Enable to include slgLayer parameter in API request.",
"default": False,
}),
"slgLayer": ("INT", {
"tooltip": "SLG layer setting.",
"tooltip": "SLG layer setting. Only used when 'Use SLG Layer' is enabled.",
"default": None,
"min": 0,
"max": 100,
}),
"Advanced Feature Settings": ("RUNWAREWANANIMATEADVANCEDFEATURESETTINGS", {
"tooltip": "Connect Runware Wan Animate Advanced Feature Settings node to provide Wan Animate configuration for character animation and replacement.",
}),
}
}
@@ -50,27 +73,41 @@ class videoAdvancedFeatureInputs:
def create_advanced_feature_inputs(self, **kwargs) -> tuple[Dict[str, Any]]:
"""Create video advanced feature inputs dictionary"""
# Get parameters
# Get use parameters
useVideoCFGScale = kwargs.get("useVideoCFGScale", False)
useAudioCFGScale = kwargs.get("useAudioCFGScale", False)
useVideoNegativePrompt = kwargs.get("useVideoNegativePrompt", False)
useAudioNegativePrompt = kwargs.get("useAudioNegativePrompt", False)
useSlgLayer = kwargs.get("useSlgLayer", False)
# Get actual parameters
videoCFGScale = kwargs.get("videoCFGScale", None)
audioCFGScale = kwargs.get("audioCFGScale", None)
videoNegativePrompt = kwargs.get("videoNegativePrompt", None)
audioNegativePrompt = kwargs.get("audioNegativePrompt", None)
slgLayer = kwargs.get("slgLayer", None)
wanAnimateSettings = kwargs.get("Advanced Feature Settings", None)
# Build advanced feature inputs dictionary - only include non-None values
# Build advanced feature inputs dictionary - only include when use* is enabled
advanced_inputs = {}
if videoCFGScale is not None:
if useVideoCFGScale and videoCFGScale is not None:
advanced_inputs["videoCFGScale"] = videoCFGScale
if audioCFGScale is not None:
if useAudioCFGScale and audioCFGScale is not None:
advanced_inputs["audioCFGScale"] = audioCFGScale
if videoNegativePrompt is not None and videoNegativePrompt.strip() != "":
if useVideoNegativePrompt and videoNegativePrompt is not None and videoNegativePrompt.strip() != "":
advanced_inputs["videoNegativePrompt"] = videoNegativePrompt.strip()
if audioNegativePrompt is not None and audioNegativePrompt.strip() != "":
if useAudioNegativePrompt and audioNegativePrompt is not None and audioNegativePrompt.strip() != "":
advanced_inputs["audioNegativePrompt"] = audioNegativePrompt.strip()
if slgLayer is not None:
if useSlgLayer and slgLayer is not None:
advanced_inputs["slgLayer"] = slgLayer
# Merge advanced feature settings if provided (similar to how providerSettings are merged)
if wanAnimateSettings is not None and isinstance(wanAnimateSettings, dict) and len(wanAnimateSettings) > 0:
# Merge the settings dictionary directly into advanced_inputs
# The settings dict should already contain the correct structure (e.g., {"wanAnimate": {...}})
advanced_inputs.update(wanAnimateSettings)
return (advanced_inputs,)
# Node class mappings
+7
View File
@@ -40,6 +40,9 @@ class videoInferenceInputs:
"Reference Videos": ("RUNWAREREFERENCEVIDEOS", {
"tooltip": "Connect the Reference Videos node to provide reference video mediaUUIDs."
}),
"Reference Voices": ("RUNWAREREFERENCEVOICES", {
"tooltip": "Connect the Reference Voices node to provide reference voice URLs or mediaUUIDs."
}),
}
}
@@ -61,6 +64,7 @@ class videoInferenceInputs:
frameImages = kwargs.get("Frame Images", None)
references = kwargs.get("References", None)
referenceVideos = kwargs.get("Reference Videos", None)
referenceVoices = kwargs.get("Reference Voices", None)
inputs = {}
@@ -105,4 +109,7 @@ class videoInferenceInputs:
if referenceVideos is not None and len(referenceVideos) > 0:
inputs["referenceVideos"] = referenceVideos
if referenceVoices is not None and len(referenceVoices) > 0:
inputs["referenceVoices"] = referenceVoices
return (inputs,)
+3
View File
@@ -58,6 +58,7 @@ class videoModelSearch:
"runware:200@1 (Wan 2.1 1.3B)",
"runware:200@2 (Wan 2.1 14B)",
"runware:200@6 (Wan 2.2)",
"runware:200@8 (Wan 2.2 A14B Animate)",
"alibaba:wan@2.6 (Wan 2.6)",
],
"OpenAI": [
@@ -148,6 +149,7 @@ class videoModelSearch:
"runware:200@1": {"width": 853, "height": 480},
"runware:200@2": {"width": 853, "height": 480},
"runware:200@6": {"width": 1280, "height": 720},
"runware:200@8": {"width": 1104, "height": 832},
"alibaba:wan@2.6": {"width": 1280, "height": 720},
# OpenAI Models
@@ -237,6 +239,7 @@ class videoModelSearch:
"runware:200@1": "480p",
"runware:200@2": "480p",
"runware:200@6": "720p",
"runware:200@8": "720p",
"alibaba:wan@2.6": "720p",
# OpenAI Models
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long