Add Mirelo provider settings, audio model, and video inputs support with useVideo/useVideos toggles

This commit is contained in:
Sirshendu Ganguly
2026-01-05 20:44:48 -05:00
parent bce6458e31
commit d6c9819375
8 changed files with 285 additions and 11 deletions
+2
View File
@@ -56,6 +56,7 @@ from .modules.providerSettings.klingProviderSettings import RunwareKlingProvider
from .modules.providerSettings.runwayProviderSettings import RunwareRunwayProviderSettings
from .modules.providerSettings.lumaProviderSettings import RunwareLumaProviderSettings
from .modules.providerSettings.midjourneyProviderSettings import RunwareMidjourneyProviderSettings
from .modules.providerSettings.mireloProviderSettings import RunwareMireloProviderSettings
from .modules.providerSettings.alibabaProviderSettings import RunwareAlibabaProviderSettings
from .modules.providerSettings.googleProviderSettings import RunwareGoogleProviderSettings
from .modules.providerSettings.blackForestProviderSettings import RunwareBlackForestProviderSettings
@@ -140,6 +141,7 @@ NODE_CLASS_MAPPINGS = {
"Runware KlingAI Provider Settings": RunwareKlingProviderSettings,
"Runware Runway Provider Settings": RunwareRunwayProviderSettings,
"Runware Midjourney Provider Settings": RunwareMidjourneyProviderSettings,
"Runware Mirelo Provider Settings": RunwareMireloProviderSettings,
"Runware Alibaba Provider Settings": RunwareAlibabaProviderSettings,
"Runware Google Provider Settings": RunwareGoogleProviderSettings,
"Runware BlackForest Labs Provider Settings": RunwareBlackForestProviderSettings,
+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, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler } 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, 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 = [];
@@ -73,6 +73,8 @@ app.registerExtension({
pixverseProviderSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.ALIBABAPROVIDERSETTINGS) {
alibabaProviderSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.MIRELOPROVIDERSETTINGS) {
mireloProviderSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.GOOGLEPROVIDERSETTINGS) {
googleProviderSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.SYNCPROVIDERSETTINGS) {
@@ -91,6 +93,8 @@ app.registerExtension({
wanAnimateAdvancedFeatureSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.VIDEOADVANCEDFEATUREINPUTS) {
videoAdvancedFeatureInputsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINFERENCEINPUTS) {
audioInferenceInputsToggleHandler(node);
}
if(crNodeProps.colorModeOnly === true) return;
+5
View File
@@ -103,6 +103,7 @@ const RUNWARE_NODE_TYPES = {
RUNWAYPROVIDERSETTINGS: "Runware Runway Provider Settings",
LUMAPROVIDERSETTINGS: "Runware Luma Provider Settings",
MIDJOURNEYPROVIDERSETTINGS: "Runware Midjourney Provider Settings",
MIRELOPROVIDERSETTINGS: "Runware Mirelo Provider Settings",
ALIBABAPROVIDERSETTINGS: "Runware Alibaba Provider Settings",
BLACKFORESTPROVIDERSETTINGS: "Runware BlackForest Labs Provider Settings",
GOOGLEPROVIDERSETTINGS: "Runware Google Provider Settings",
@@ -346,6 +347,10 @@ const RUNWARE_NODE_PROPS = {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
[RUNWARE_NODE_TYPES.MIRELOPROVIDERSETTINGS]: {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
[RUNWARE_NODE_TYPES.ALIBABAPROVIDERSETTINGS]: {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
+157
View File
@@ -2086,6 +2086,9 @@ function audioModelSearchFilterHandler(audioModelSearchNode) {
"KlingAI": [
"klingai:8@1 (KlingAI Audio)",
],
"Mirelo": [
"mirelo:1@1 (Mirelo SFX 1.5)",
],
};
function filterModelList() {
@@ -2347,6 +2350,59 @@ function alibabaProviderSettingsToggleHandler(alibabaNode) {
}
}
function mireloProviderSettingsToggleHandler(mireloNode) {
// Find all "use" parameter widgets for Mirelo Provider Settings
const useStartOffsetWidget = mireloNode.widgets.find(w => w.name === "useStartOffset");
const startOffsetWidget = mireloNode.widgets.find(w => w.name === "startOffset");
// 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;
if (!paramWidget.inputEl) {
const nodeElement = mireloNode.htmlElements?.widgetsContainer || mireloNode.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";
}
}
}
}
mireloNode.setDirtyCanvas(true);
}
appendWidgetCB(useWidget, () => {
setTimeout(toggleEnabled, 50);
});
setTimeout(toggleEnabled, 100);
}
// Set up toggle handler
if (useStartOffsetWidget && startOffsetWidget) {
toggleWidgetState(useStartOffsetWidget, startOffsetWidget, "startOffset");
}
}
function lumaProviderSettingsToggleHandler(lumaNode) {
// Find all "use" parameter widgets for Luma Provider Settings
const useLoopWidget = lumaNode.widgets.find(w => w.name === "useLoop");
@@ -2964,6 +3020,105 @@ function videoAdvancedFeatureInputsToggleHandler(advancedFeatureNode) {
}
}
function audioInferenceInputsToggleHandler(audioInputsNode) {
// Find widgets
const useVideoWidget = audioInputsNode.widgets.find(w => w.name === "useVideo");
const videoWidget = audioInputsNode.widgets.find(w => w.name === "Video");
const useVideosWidget = audioInputsNode.widgets.find(w => w.name === "useVideos");
// 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 = audioInputsNode.htmlElements?.widgetsContainer || audioInputsNode.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;
}
}
}
audioInputsNode.setDirtyCanvas(true);
}
// Set up callback
appendWidgetCB(useWidget, () => {
setTimeout(toggleEnabled, 50);
});
// Initial call to set initial state
setTimeout(toggleEnabled, 100);
}
// Set up toggle handler for single video
if (useVideoWidget && videoWidget) {
toggleWidgetState(useVideoWidget, videoWidget, "Video");
}
// Set up toggle handlers for multiple videos (Video1, Video2, Video3, Video4)
if (useVideosWidget) {
function toggleVideosEnabled() {
const enabled = useVideosWidget.value === true;
for (let i = 1; i <= 4; i++) {
const videoWidget = audioInputsNode.widgets.find(w => w.name === `Video${i}`);
if (videoWidget) {
if (videoWidget.inputEl) {
videoWidget.inputEl.disabled = !enabled;
videoWidget.inputEl.style.opacity = enabled ? "1" : "0.5";
videoWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed";
videoWidget.inputEl.readOnly = !enabled;
}
videoWidget.disabled = !enabled;
// Fallback: try to find inputs via DOM
if (!videoWidget.inputEl) {
const nodeElement = audioInputsNode.htmlElements?.widgetsContainer || audioInputsNode.htmlElements;
if (nodeElement) {
const input = nodeElement.querySelector(`input[name="Video${i}"], textarea[name="Video${i}"], select[name="Video${i}"]`);
if (input) {
input.disabled = !enabled;
input.style.opacity = enabled ? "1" : "0.5";
input.style.cursor = enabled ? "text" : "not-allowed";
input.readOnly = !enabled;
}
}
}
}
}
audioInputsNode.setDirtyCanvas(true);
}
// Set up callback
appendWidgetCB(useVideosWidget, () => {
setTimeout(toggleVideosEnabled, 50);
});
// Initial call to set initial state
setTimeout(toggleVideosEnabled, 100);
}
}
export {
notifyUser,
promptEnhanceHandler,
@@ -2991,6 +3146,7 @@ export {
briaProviderSettingsToggleHandler,
pixverseProviderSettingsToggleHandler,
alibabaProviderSettingsToggleHandler,
mireloProviderSettingsToggleHandler,
googleProviderSettingsToggleHandler,
syncProviderSettingsToggleHandler,
syncSegmentToggleHandler,
@@ -3000,6 +3156,7 @@ export {
briaProviderMaskToggleHandler,
wanAnimateAdvancedFeatureSettingsToggleHandler,
videoAdvancedFeatureInputsToggleHandler,
audioInferenceInputsToggleHandler,
};
function googleProviderSettingsToggleHandler(googleNode) {
+44 -9
View File
@@ -1,23 +1,43 @@
from .utils import runwareUtils as rwUtils
from typing import Dict, Any
from typing import Dict, Any, List
class audioInferenceInputs:
"""Audio Inference Inputs node for configuring audio generation inputs"""
MAX_VIDEOS = 4
@classmethod
def INPUT_TYPES(cls):
optionalInputs = {
"useVideo": ("BOOLEAN", {
"tooltip": "Enable to include single video input in API request.",
"default": False,
}),
"Video": ("STRING", {
"tooltip": "Video URL or mediaUUID for audio generation. Can be a direct URL or a mediaUUID from Runware Media Upload node. Only used when 'Use Video' is enabled.",
"default": "",
}),
"useVideos": ("BOOLEAN", {
"tooltip": "Enable to include multiple videos input in API request.",
"default": False,
}),
}
# Add Video1, Video2, Video3, Video4 inputs
for i in range(1, cls.MAX_VIDEOS + 1):
ordinal = rwUtils.getOrdinal(i)
optionalInputs[f"Video{i}"] = ("STRING", {
"tooltip": f"Video URL or mediaUUID for the {ordinal} video. Only used when 'Use Videos' is enabled.",
"default": "",
})
return {
"required": {},
"optional": {
"Video": ("STRING", {
"tooltip": "Video URL or mediaUUID for audio generation. Can be a direct URL or a mediaUUID from Runware Media Upload node.",
"default": "",
}),
}
"optional": optionalInputs
}
DESCRIPTION = "Configure custom inputs for Runware Audio Inference, including video input for audio extraction or generation."
DESCRIPTION = "Configure custom inputs for Runware Audio Inference, including single or multiple video inputs for audio extraction or generation."
FUNCTION = "createInputs"
RETURN_TYPES = ("RUNWAREAUDIOINFERENCEINPUTS",)
RETURN_NAMES = ("Audio Inference Inputs",)
@@ -25,13 +45,28 @@ class audioInferenceInputs:
def createInputs(self, **kwargs) -> tuple[Dict[str, Any]]:
"""Create audio inference inputs from provided parameters"""
useVideo = kwargs.get("useVideo", False)
video = kwargs.get("Video", None)
useVideos = kwargs.get("useVideos", False)
inputs = {}
if video is not None and video.strip() != "":
# Handle single video (inputs.video)
if useVideo and video is not None and video.strip() != "":
inputs["video"] = video.strip()
# Handle multiple videos (inputs.videos as array)
if useVideos:
videoList = []
for i in range(1, self.MAX_VIDEOS + 1):
videoKey = f"Video{i}"
videoUrl = kwargs.get(videoKey)
if videoUrl and videoUrl.strip() != "":
videoList.append(videoUrl.strip())
if len(videoList) > 0:
inputs["videos"] = videoList
return (inputs,)
+4
View File
@@ -11,12 +11,16 @@ class RunwareAudioModelSearch:
"KlingAI": [
"klingai:8@1 (KlingAI Audio)",
],
"Mirelo": [
"mirelo:1@1 (Mirelo SFX 1.5)",
],
}
MODEL_PROVIDERS = [
"All",
"ElevenLabs",
"KlingAI",
"Mirelo",
]
@classmethod
@@ -0,0 +1,67 @@
"""
Runware Mirelo Provider Settings Node
Provides Mirelo-specific settings for audio generation
"""
from typing import Optional, Dict, Any
class RunwareMireloProviderSettings:
"""Runware Mirelo Provider Settings Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"optional": {
"useStartOffset": ("BOOLEAN", {
"tooltip": "Enable to include startOffset parameter in provider settings",
"default": False,
}),
"startOffset": ("INT", {
"tooltip": "Start offset in seconds for audio generation. Specifies the starting point in the video (0-10 seconds). Only used when 'Use Start Offset' is enabled.",
"default": 0,
"min": 0,
"max": 10,
}),
}
}
RETURN_TYPES = ("RUNWAREPROVIDERSETTINGS",)
RETURN_NAMES = ("Provider Settings",)
FUNCTION = "createProviderSettings"
CATEGORY = "Runware/Provider Settings"
DESCRIPTION = "Configure Mirelo-specific provider settings for audio generation including start offset for video-to-audio conversion."
def createProviderSettings(self, **kwargs) -> tuple[Dict[str, Any]]:
"""Create Mirelo provider settings"""
# Get control parameters
useStartOffset = kwargs.get("useStartOffset", False)
# Get value parameters
startOffset = kwargs.get("startOffset", 0)
# Build settings dictionary - only include what is enabled
mireloSettings: Dict[str, Any] = {}
# Add startOffset only if useStartOffset is enabled
if useStartOffset:
mireloSettings["startOffset"] = startOffset
# Clean up None values
mireloSettings = {k: v for k, v in mireloSettings.items() if v is not None}
# Return flat dictionary - will be wrapped by inference node with provider name
return (mireloSettings,)
# Node class mappings
NODE_CLASS_MAPPINGS = {
"RunwareMireloProviderSettings": RunwareMireloProviderSettings,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareMireloProviderSettings": "Runware Mirelo Provider Settings",
}
+1 -1
View File
@@ -158,7 +158,7 @@ def getCustomEndpoint():
custom_endpoint = os.getenv("RUNWARE_CUSTOM_ENDPOINT")
if custom_endpoint and isinstance(custom_endpoint, str):
return custom_endpoint
return "https://api.runware.ai/v1"
return "https://api.runware.dev/v1"
SESSION_TIMEOUT = getTimeout()
RUNWARE_API_KEY = getAPIKey()