From a5f379ed677a01aa7d895a94640967015fc06c50 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Sat, 14 Mar 2026 22:08:21 -0400 Subject: [PATCH] Added support for HeyGen Avatar --- __init__.py | 2 + clientlibs/main.js | 4 +- clientlibs/types.js | 4 ++ clientlibs/utils.js | 73 +++++++++++++++++++ modules/videoInference.py | 24 +++---- modules/videoInferenceInputs.py | 15 ++++ modules/videoInferenceSpeechInput.py | 102 +++++++++++++++++++++++++++ modules/videoModelSearch.py | 10 +++ modules/videoSettings.py | 38 ++++++++++ 9 files changed, 255 insertions(+), 17 deletions(-) create mode 100644 modules/videoInferenceSpeechInput.py diff --git a/__init__.py b/__init__.py index 84de7f3..20c9732 100644 --- a/__init__.py +++ b/__init__.py @@ -30,6 +30,7 @@ from .modules.imageWatermarkAdvancedFeature import RunwareWatermarkAdvancedFeatu from .modules.imageRegionalPromptingAdvancedFeature import RunwareRegionalPromptingAdvancedFeature from .modules.imageRegionalPromptingAdvancedFeatureRegions import RunwareRegionalPromptingAdvancedFeatureRegions from .modules.videoInference import txt2vid +from .modules.videoInferenceSpeechInput import RunwareVideoInferenceSpeechInput from .modules.videoModelSearch import videoModelSearch from .modules.videoSettings import RunwareVideoSettings from .modules.frameImages import RunwareFrameImages @@ -168,6 +169,7 @@ NODE_CLASS_MAPPINGS = { "Runware Reference Videos": referenceVideos, "Runware Reference Voices": referenceVoices, "Runware Video Inference Inputs": videoInferenceInputs, + "Runware Video Inference Speech Input": RunwareVideoInferenceSpeechInput, "Runware Video Inference Settings": RunwareVideoSettings, "Runware Lightricks Provider Settings": RunwareLightricksProviderSettings, "Runware MiniMax Provider Settings": RunwareMiniMaxProviderSettings, diff --git a/clientlibs/main.js b/clientlibs/main.js index c000920..e12b099 100644 --- a/clientlibs/main.js +++ b/clientlibs/main.js @@ -1,6 +1,6 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; -import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, regionalPromptingRegionsToggleHandler, 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 { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, videoInferenceSpeechInputToggleHandler, regionalPromptingRegionsToggleHandler, 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({ imageInferenceAdvancedFeaturesToggleHandler(node); } else if(nodeClass === RUNWARE_NODE_TYPES.WATERMARKADVANCEDFEATURE) { watermarkAdvancedFeatureToggleHandler(node); + } else if(nodeClass === RUNWARE_NODE_TYPES.VIDEOINFERENCESPEECHINPUT) { + videoInferenceSpeechInputToggleHandler(node); } else if(nodeClass === RUNWARE_NODE_TYPES.REGIONALPROMPTINGADVFEATUREREGIONS) { regionalPromptingRegionsToggleHandler(node); } else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINFERENCESPEECH) { diff --git a/clientlibs/types.js b/clientlibs/types.js index 91b86d0..1f81aa4 100644 --- a/clientlibs/types.js +++ b/clientlibs/types.js @@ -102,6 +102,7 @@ const RUNWARE_NODE_TYPES = { REFERENCEVIDEOS: "Runware Reference Videos", REFERENCEVOICES: "Runware Reference Voices", VIDEOINFERENCEINPUTS: "Runware Video Inference Inputs", + VIDEOINFERENCESPEECHINPUT: "Runware Video Inference Speech Input", VIDEOSETTINGS: "Runware Video Inference Settings", LIGHTRICKSPROVIDERSETTINGS: "Runware Lightricks Provider Settings", KLINGPROVIDERSETTINGS: "Runware KlingAI Provider Settings", @@ -300,6 +301,9 @@ const RUNWARE_NODE_PROPS = { bgColor: DEFAULT_BGCOLOR, colorModeOnly: true, }, + [RUNWARE_NODE_TYPES.VIDEOINFERENCESPEECHINPUT]: { + bgColor: DEFAULT_BGCOLOR, + }, [RUNWARE_NODE_TYPES.REGIONALPROMPTINGADVFEATURE]: { bgColor: DEFAULT_BGCOLOR, colorModeOnly: true, diff --git a/clientlibs/utils.js b/clientlibs/utils.js index 878d04d..2608800 100644 --- a/clientlibs/utils.js +++ b/clientlibs/utils.js @@ -1178,6 +1178,12 @@ function videoSettingsToggleHandler(settingsNode) { 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"); + const useBackgroundColorWidget = settingsNode.widgets.find(w => w && w.name === "useBackgroundColor"); + const backgroundColorWidget = settingsNode.widgets.find(w => w && w.name === "backgroundColor"); + const useRemoveBackgroundWidget = settingsNode.widgets.find(w => w && w.name === "useRemoveBackground"); + const removeBackgroundWidget = settingsNode.widgets.find(w => w && w.name === "removeBackground"); + const useExpressivenessWidget = settingsNode.widgets.find(w => w && w.name === "useExpressiveness"); + const expressivenessWidget = settingsNode.widgets.find(w => w && w.name === "expressiveness"); function toggleWidgetState(useWidget, paramWidget, paramName) { if (!useWidget || !paramWidget) return; @@ -1198,6 +1204,9 @@ function videoSettingsToggleHandler(settingsNode) { if (useDraftWidget && draftWidget) toggleWidgetState(useDraftWidget, draftWidget, "draft"); if (useAudioWidget && audioWidget) toggleWidgetState(useAudioWidget, audioWidget, "audio"); if (usePromptUpsamplingWidget && promptUpsamplingWidget) toggleWidgetState(usePromptUpsamplingWidget, promptUpsamplingWidget, "promptUpsampling"); + if (useBackgroundColorWidget && backgroundColorWidget) toggleWidgetState(useBackgroundColorWidget, backgroundColorWidget, "backgroundColor"); + if (useRemoveBackgroundWidget && removeBackgroundWidget) toggleWidgetState(useRemoveBackgroundWidget, removeBackgroundWidget, "removeBackground"); + if (useExpressivenessWidget && expressivenessWidget) toggleWidgetState(useExpressivenessWidget, expressivenessWidget, "expressiveness"); } function audioInferenceSpeechToggleHandler(speechNode) { @@ -2350,6 +2359,9 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "creatify:aurora@fast (Creatify Aurora Avatar Model API (720p))", "creatify:aurora@0 (Creatify Aurora Avatar Model API (720p))", ], + "HeyGen": [ + "heygen:avatar@4 (HeyGen Avatar IV)", + ], "Hunyuan": [ "runware:hunyuanvideo@1.5 (HunyuanVideo-1.5)", ], @@ -2449,6 +2461,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "xai:grok-imagine@video": {"width": 480, "height": 480}, "veed:fabric@1.0": {"width": 1280, "height": 720}, "prunaai:p-video@0": {"width": 1280, "height": 720}, + "heygen:avatar@4": {"width": 1280, "height": 720}, }; const MODEL_RESOLUTIONS = { @@ -2533,6 +2546,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "xai:grok-imagine@video": "480p", "veed:fabric@1.0": "720p", "prunaai:p-video@0": "720p", + "heygen:avatar@4": "720p", }; const DEFAULT_DIMENSIONS = {"width": 1024, "height": 576}; @@ -3931,6 +3945,64 @@ function watermarkAdvancedFeatureToggleHandler(watermarkNode) { } } +function videoInferenceSpeechInputToggleHandler(speechInputNode) { + if (!speechInputNode?.widgets) return; + + 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; + + if (!paramWidget.inputEl && paramName) { + const nodeElement = speechInputNode.htmlElements?.widgetsContainer || speechInputNode.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; + } + } + } + + speechInputNode.setDirtyCanvas(true); + } + + appendWidgetCB(useWidget, () => { + setTimeout(toggleEnabled, 50); + }); + + setTimeout(toggleEnabled, 100); + } + + const pairs = [ + ["useVoice", "voice"], + ["useText", "text"], + ["useSpeed", "speed"], + ["usePitch", "pitch"], + ["useLanguage", "language"], + ]; + + for (const [useName, paramName] of pairs) { + const useWidget = speechInputNode.widgets.find(w => w.name === useName); + const paramWidget = speechInputNode.widgets.find(w => w.name === paramName); + if (useWidget && paramWidget) { + toggleWidgetState(useWidget, paramWidget, paramName); + } + } +} + function regionalPromptingRegionsToggleHandler(regionsNode) { if (!regionsNode?.widgets) return; @@ -4122,6 +4194,7 @@ export { imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, + videoInferenceSpeechInputToggleHandler, regionalPromptingRegionsToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, diff --git a/modules/videoInference.py b/modules/videoInference.py index 378c0a2..4b93bfc 100644 --- a/modules/videoInference.py +++ b/modules/videoInference.py @@ -125,12 +125,8 @@ class txt2vid: "referenceVideos": ("RUNWAREREFERENCEVIDEOS", { "tooltip": "Connect reference video files for video generation.", }), - "speechVoice": ("STRING", { - "tooltip": "Voice for speech synthesis. Specify the voice to use for text-to-speech generation.", - }), - "speechText": ("STRING", { - "multiline": True, - "tooltip": "Text for speech synthesis. The text that will be converted to speech using the specified voice.", + "speech": ("RUNWAREVIDEOINFERENCESPEECHINPUT", { + "tooltip": "Connect a Runware Video Inference Speech Input node to set voice and text for speech synthesis.", }), "inputs": ("RUNWAREVIDEOINFERENCEINPUTS", { "tooltip": "Connect a Runware Video Inference Inputs node to provide custom inputs like image, audio, mask, and parameters for OmniHuman 1.5 and other video models.", @@ -178,8 +174,7 @@ class txt2vid: referenceImages = kwargs.get("referenceImages", None) inputAudios = kwargs.get("inputAudios", None) referenceVideos = kwargs.get("referenceVideos", None) - speechVoice = kwargs.get("speechVoice", None) - speechText = kwargs.get("speechText", None) + speech = kwargs.get("speech", None) inputs = kwargs.get("inputs", None) safetyInputs = kwargs.get("safetyInputs", None) videoAdvancedFeatureInputs = kwargs.get("videoAdvancedFeatureInputs", None) @@ -300,14 +295,11 @@ class txt2vid: genConfig[0]["referenceVideos"] = referenceVideos print(f"[Debugging] Reference videos: {rwUtils.sanitize_for_logging(genConfig[0].get('referenceVideos', []))}") - # Add speech parameters if both voice and text are provided - if speechVoice and speechVoice.strip() != "" and speechText and speechText.strip() != "": - genConfig[0]["speech"] = { - "voice": speechVoice.strip(), - "text": speechText.strip() - } - print(f"[Debugging] Speech parameters: voice='{speechVoice.strip()}', text='{speechText.strip()[:50]}...'") - + # Add speech parameters if provided from Runware Video Inference Speech Input node + if speech is not None and isinstance(speech, dict) and speech: + genConfig[0]["speech"] = speech + print(f"[Debugging] Speech parameters: {rwUtils.sanitize_for_logging(speech)}") + # Handle inputs - merge custom inputs from Video Inference Inputs node if inputs is not None: # Merge inputs from video inference inputs node diff --git a/modules/videoInferenceInputs.py b/modules/videoInferenceInputs.py index d367eb6..7072eb3 100644 --- a/modules/videoInferenceInputs.py +++ b/modules/videoInferenceInputs.py @@ -13,6 +13,10 @@ class videoInferenceInputs: "Image": ("IMAGE", { "tooltip": "Portrait image for video generation. This will be the main subject that will speak and move in the generated video." }), + "Avatar": ("STRING", { + "default": "", + "tooltip": "HeyGen avatar ID for a photo avatar or video avatar. Mutually exclusive with image_url and image_asset_id.", + }), "Frame Images": ("RUNWAREVIDEOINPUTSFRAMEIMAGES", { "tooltip": "Connect the Runware Video Inputs Frame node to provide timeline-constrained frame images." }), @@ -34,6 +38,9 @@ class videoInferenceInputs: "Frame": ("IMAGE", { "tooltip": "Frame image for video generation. Connect a Load Image node to provide the frame image." }), + "Background": ("IMAGE", { + "tooltip": "Background image for video generation. Connect a Load Image node to provide the background.", + }), "References": ("RUNWAREVIDEOINPUTSREFERENCEIMAGES", { "tooltip": "Connect the Video Inputs References node to provide reference images." }), @@ -71,15 +78,20 @@ class videoInferenceInputs: video = kwargs.get("Video", None) mask = kwargs.get("Mask", None) frame = kwargs.get("Frame", None) + background = kwargs.get("Background", None) frameImages = kwargs.get("Frame Images", None) references = kwargs.get("References", None) referenceVideos = kwargs.get("Reference Videos", None) referenceVoices = kwargs.get("Reference Voices", None) draftId = kwargs.get("draftId", "") videoId = kwargs.get("videoId", "") + avatar = kwargs.get("Avatar", "") inputs = {} + if avatar is not None and avatar.strip() != "": + inputs["avatar"] = avatar.strip() + if image is not None: inputs["image"] = rwUtils.convertTensor2IMG(image) @@ -112,6 +124,9 @@ class videoInferenceInputs: if frame is not None: inputs["frame"] = rwUtils.convertTensor2IMG(frame) + if background is not None: + inputs["background"] = rwUtils.convertTensor2IMG(background) + if frameImages is not None and len(frameImages) > 0: inputs["frameImages"] = frameImages diff --git a/modules/videoInferenceSpeechInput.py b/modules/videoInferenceSpeechInput.py new file mode 100644 index 0000000..6895e49 --- /dev/null +++ b/modules/videoInferenceSpeechInput.py @@ -0,0 +1,102 @@ +""" +Runware Video Inference Speech Input Node. +Provides voice and text for speech synthesis; connect to Runware Video Inference speech input. +""" + +from typing import Any, Dict, Tuple + + +class RunwareVideoInferenceSpeechInput: + """Runware Video Inference Speech Input Node""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + "optional": { + "useVoice": ("BOOLEAN", { + "tooltip": "Enable to include voice in speech synthesis.", + "default": False, + }), + "voice": ("STRING", { + "tooltip": "Voice ID for text-to-speech or speech-to-speech. Required when text script is provided. Only used when 'Use Voice' is enabled.", + "default": "", + }), + "useText": ("BOOLEAN", { + "tooltip": "Enable to include text in speech synthesis.", + "default": False, + }), + "text": ("STRING", { + "multiline": True, + "tooltip": "Text script for the avatar to speak. Requires voice. Mutually exclusive with audio-driven input. Only used when 'Use Text' is enabled.", + "default": "", + }), + "useSpeed": ("BOOLEAN", { + "tooltip": "Enable to set voice playback speed multiplier.", + "default": False, + }), + "speed": ("FLOAT", { + "tooltip": "Voice playback speed multiplier (0.5 - 1.5). Only used when 'Use Speed' is enabled.", + "default": 1.0, + "min": 0.5, + "max": 1.5, + "step": 0.05, + }), + "usePitch": ("BOOLEAN", { + "tooltip": "Enable to set voice pitch adjustment in semitones.", + "default": False, + }), + "pitch": ("FLOAT", { + "tooltip": "Voice pitch adjustment in semitones (-50 to 50). Only used when 'Use Pitch' is enabled.", + "default": 0.0, + "min": -50.0, + "max": 50.0, + "step": 0.5, + }), + "useLanguage": ("BOOLEAN", { + "tooltip": "Enable to set locale or accent hint for multilingual voices.", + "default": False, + }), + "language": ("STRING", { + "tooltip": "Locale or accent hint for multilingual voices, e.g. en-US. Only used when 'Use Language' is enabled.", + "default": "", + }), + }, + } + + RETURN_TYPES: Tuple[str] = ("RUNWAREVIDEOINFERENCESPEECHINPUT",) + RETURN_NAMES = ("speech",) + FUNCTION = "createSpeech" + CATEGORY = "Runware" + DESCRIPTION = ( + "Configure speech synthesis for Runware Video Inference (voice and text). " + "Connect to Runware Video Inference speech input. Both voice and text must be set for speech to be included." + ) + + def createSpeech(self, **kwargs) -> tuple: + """Build speech dict for genConfig[0]['speech'].""" + + speech: Dict[str, Any] = {} + if kwargs.get("useVoice"): + speech["voice"] = kwargs.get("voice") + if kwargs.get("useText"): + speech["text"] = kwargs.get("text") + if kwargs.get("useSpeed"): + speech["speed"] = float(kwargs.get("speed", 1.0)) + if kwargs.get("usePitch"): + speech["pitch"] = float(kwargs.get("pitch", 0.0)) + if kwargs.get("useLanguage"): + lang = (kwargs.get("language") or "").strip() + if lang: + speech["language"] = lang + + return (speech or None,) + + +NODE_CLASS_MAPPINGS = { + "RunwareVideoInferenceSpeechInput": RunwareVideoInferenceSpeechInput, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "RunwareVideoInferenceSpeechInput": "Runware Video Inference Speech Input", +} diff --git a/modules/videoModelSearch.py b/modules/videoModelSearch.py index ab0962d..5dff16a 100644 --- a/modules/videoModelSearch.py +++ b/modules/videoModelSearch.py @@ -109,6 +109,9 @@ class videoModelSearch: "creatify:aurora@fast (Creatify Aurora Avatar Model API (720p))", "creatify:aurora@0 (Creatify Aurora Avatar Model API (720p))", ], + "HeyGen": [ + "heygen:avatar@4 (HeyGen Avatar IV)", + ], "Hunyuan": [ "runware:hunyuanvideo@1.5 (HunyuanVideo-1.5)", ], @@ -248,6 +251,9 @@ class videoModelSearch: # Pruna Models (720p) "prunaai:p-video@0": {"width": 1280, "height": 720}, + + # HeyGen Models (Model ID: heygen-avatar-iv) + "heygen:avatar@4": {"width": 1280, "height": 720}, } # Model resolutions mapping @@ -372,6 +378,9 @@ class videoModelSearch: # Pruna Models "prunaai:p-video@0": "720p", + + # HeyGen Models + "heygen:avatar@4": "720p", } MODEL_ARCHITECTURES = [ @@ -391,6 +400,7 @@ class videoModelSearch: "Sync", "Bria", "Creatify", + "HeyGen", "Hunyuan", "Kandinsky", "xAI", diff --git a/modules/videoSettings.py b/modules/videoSettings.py index fd2c40d..15f9c4a 100644 --- a/modules/videoSettings.py +++ b/modules/videoSettings.py @@ -44,6 +44,32 @@ class RunwareVideoSettings: "label_on": "Enabled", "label_off": "Disabled", }), + "useBackgroundColor": ("BOOLEAN", { + "tooltip": "Enable to set background color. Required when background.type = color.", + "default": False, + }), + "backgroundColor": ("STRING", { + "tooltip": "Hex color code (e.g. #ff0000). Required when background.type = color. Only used when 'Use Background Color' is enabled.", + "default": "", + }), + "useRemoveBackground": ("BOOLEAN", { + "tooltip": "Enable to remove the avatar background. Video avatars must be trained with matting enabled.", + "default": False, + }), + "removeBackground": ("BOOLEAN", { + "tooltip": "Remove the avatar background. Only used when 'Use Remove Background' is enabled.", + "default": False, + "label_on": "Enabled", + "label_off": "Disabled", + }), + "useExpressiveness": ("BOOLEAN", { + "tooltip": "Enable to set avatar expressiveness level. Applies to photo avatars only.", + "default": False, + }), + "expressiveness": (["low", "medium", "high"], { + "tooltip": "Avatar expressiveness level. Applies to photo avatars only. Only used when 'Use Expressiveness' is enabled.", + "default": "low", + }), } } @@ -61,6 +87,12 @@ class RunwareVideoSettings: audio = kwargs.get("audio", True) use_prompt_upsampling = kwargs.get("usePromptUpsampling", True) prompt_upsampling = kwargs.get("promptUpsampling", True) + use_background_color = kwargs.get("useBackgroundColor", False) + background_color = (kwargs.get("backgroundColor") or "").strip() + use_remove_background = kwargs.get("useRemoveBackground", False) + remove_background = kwargs.get("removeBackground", False) + use_expressiveness = kwargs.get("useExpressiveness", False) + expressiveness = kwargs.get("expressiveness", "low") settings: Dict[str, Any] = {} @@ -70,6 +102,12 @@ class RunwareVideoSettings: settings["audio"] = bool(audio) if use_prompt_upsampling: settings["promptUpsampling"] = bool(prompt_upsampling) + if use_background_color and background_color: + settings["backgroundColor"] = background_color + if use_remove_background: + settings["removeBackground"] = bool(remove_background) + if use_expressiveness: + settings["expressiveness"] = expressiveness return (settings,)