Added support for Gemini TTS

This commit is contained in:
Sirshendu Ganguly
2026-04-20 16:10:35 +05:30
parent b4da68335d
commit 6dcd734c46
8 changed files with 176 additions and 21 deletions
+2
View File
@@ -42,6 +42,7 @@ from .modules.providerSettings.briaProviderSettings import RunwareBriaProviderSe
from .modules.providerSettings.briaProviderMask import RunwareBriaProviderMask
from .modules.audioInference import RunwareAudioInference
from .modules.audioInferenceSpeech import RunwareAudioInferenceSpeech
from .modules.audioInferenceSpeechVoices import RunwareAudioInferenceSpeechVoices
from .modules.audioModelSearch import RunwareAudioModelSearch
from .modules.textModelSearch import RunwareTextModelSearch
from .modules.textInferenceMessages import RunwareTextInferenceMessages
@@ -164,6 +165,7 @@ NODE_CLASS_MAPPINGS = {
"Runware Bria Provider Mask": RunwareBriaProviderMask,
"Runware Audio Inference": RunwareAudioInference,
"Runware Audio Inference Speech": RunwareAudioInferenceSpeech,
"Runware Audio Inference Speech Voices": RunwareAudioInferenceSpeechVoices,
"Runware Audio Model Search": RunwareAudioModelSearch,
"Runware Text Model Search": RunwareTextModelSearch,
"Runware Text Inference Messages": RunwareTextInferenceMessages,
+3 -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, saveTextHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, textModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, videoInferenceSpeechInputToggleHandler, regionalPromptingRegionsToggleHandler, upscalerToggleHandler, imageUpscalerSettingsToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, textInferenceSettingsToggleHandler, videoSettingsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, safetyInputsToggleHandler, imageInferenceSettingsColorPaletteToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler } from "./utils.js";
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, saveTextHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, textModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, videoInferenceSpeechInputToggleHandler, regionalPromptingRegionsToggleHandler, upscalerToggleHandler, imageUpscalerSettingsToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, textInferenceSettingsToggleHandler, videoSettingsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, safetyInputsToggleHandler, imageInferenceSettingsColorPaletteToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler, audioInferenceSpeechVoicesToggleHandler } from "./utils.js";
import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js";
const nodeInitList = [];
@@ -91,6 +91,8 @@ app.registerExtension({
regionalPromptingRegionsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINFERENCESPEECH) {
audioInferenceSpeechToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINFERENCESPEECHVOICES) {
audioInferenceSpeechVoicesToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOSETTINGS) {
audioSettingsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.TEXTINFERENCESETTINGS) {
+5
View File
@@ -82,6 +82,7 @@ const RUNWARE_NODE_TYPES = {
FRAMEIMAGES: "Runware Frame Images",
AUDIOINFERENCE: "Runware Audio Inference",
AUDIOINFERENCESPEECH: "Runware Audio Inference Speech",
AUDIOINFERENCESPEECHVOICES: "Runware Audio Inference Speech Voices",
AUDIOMODELSEARCH: "Runware Audio Model Search",
TEXTMODELSEARCH: "Runware Text Model Search",
TEXTINFERENCE: "Runware Text Inference",
@@ -299,6 +300,10 @@ const RUNWARE_NODE_PROPS = {
[RUNWARE_NODE_TYPES.AUDIOINFERENCESPEECH]: {
bgColor: DEFAULT_BGCOLOR,
},
[RUNWARE_NODE_TYPES.AUDIOINFERENCESPEECHVOICES]: {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
[RUNWARE_NODE_TYPES.AUDIOMODELSEARCH]: {
bgColor: DEFAULT_BGCOLOR,
liveSearch: true,
+62
View File
@@ -3137,6 +3137,9 @@ function audioModelSearchFilterHandler(audioModelSearchNode) {
"inworld:tts@1.5-mini (Inworld TTS-1.5 Mini)",
"inworld:tts@1.5-max (Inworld TTS-1.5 Max)",
],
"Google": [
"google:gemini@3.1-flash-tts (Gemini 3.1 Flash TTS)",
],
};
function filterModelList() {
@@ -4614,6 +4617,64 @@ function audioInferenceInputsToggleHandler(audioInputsNode) {
}
}
function audioInferenceSpeechVoicesToggleHandler(voicesNode) {
if (!voicesNode?.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) {
const nodeElement = voicesNode.htmlElements?.widgetsContainer || voicesNode.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";
}
}
}
}
voicesNode.setDirtyCanvas(true);
}
appendWidgetCB(useWidget, () => {
setTimeout(toggleEnabled, 50);
});
setTimeout(toggleEnabled, 100);
}
for (let i = 1; i <= 4; i++) {
const useVoiceWidget = voicesNode.widgets.find((w) => w && w.name === `useVoice${i}`);
const speakerWidget = voicesNode.widgets.find((w) => w && w.name === `speaker${i}`);
const voiceWidget = voicesNode.widgets.find((w) => w && w.name === `voice${i}`);
if (useVoiceWidget && speakerWidget) {
toggleWidgetState(useVoiceWidget, speakerWidget, `speaker${i}`);
}
if (useVoiceWidget && voiceWidget) {
toggleWidgetState(useVoiceWidget, voiceWidget, `voice${i}`);
}
}
}
export {
notifyUser,
promptEnhanceHandler,
@@ -4677,6 +4738,7 @@ export {
wanAnimateAdvancedFeatureSettingsToggleHandler,
videoAdvancedFeatureInputsToggleHandler,
audioInferenceInputsToggleHandler,
audioInferenceSpeechVoicesToggleHandler,
};
function googleProviderSettingsToggleHandler(googleNode) {
+12 -20
View File
@@ -14,20 +14,6 @@ class RunwareAudioInferenceSpeech:
"calm", "fluent", "whisper",
]
SPEECH_LANGUAGES = [
"Auto",
"Chinese",
"English",
"Japanese",
"Korean",
"German",
"French",
"Russian",
"Portuguese",
"Spanish",
"Italian",
]
def __init__(self):
pass
@@ -50,7 +36,7 @@ class RunwareAudioInferenceSpeech:
"default": "",
"tooltip": "Voice identifier.",
}),
"useSpeed": ("BOOLEAN", {"default": True, "tooltip": "Include speed (speech.speed)"}),
"useSpeed": ("BOOLEAN", {"default": False, "tooltip": "Include speed (speech.speed)"}),
"speed": ("FLOAT", {
"default": 1.0,
"min": 0.5,
@@ -58,7 +44,7 @@ class RunwareAudioInferenceSpeech:
"step": 0.01,
"tooltip": "Speech rate multiplier (speech.speed)",
}),
"useVolume": ("BOOLEAN", {"default": True, "tooltip": "Include volume (speech.volume)"}),
"useVolume": ("BOOLEAN", {"default": False, "tooltip": "Include volume (speech.volume)"}),
"volume": ("FLOAT", {
"default": 1.0,
"min": 1.0,
@@ -87,9 +73,12 @@ class RunwareAudioInferenceSpeech:
"default": False,
"tooltip": "Enable to include language (speech.language).",
}),
"language": (cls.SPEECH_LANGUAGES, {
"language": ("STRING", {
"default": "Auto",
"tooltip": "Language of the input text. Options: Auto, Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian. Applies to all variants. Only used when 'Use Language' is enabled.",
"tooltip": "Language of the input text (for example: en-US). Applies to all variants. Only used when 'Use Language' is enabled.",
}),
"voices": ("RUNWAREAUDIOINFERENCESPEECHVOICES", {
"tooltip": "Optional multi-speaker voices array (speech.voices[]). If provided, it takes precedence over speech.voice.",
}),
}
}
@@ -119,10 +108,13 @@ class RunwareAudioInferenceSpeech:
use_tone = kwargs.get("useTone", False)
tone_str = kwargs.get("tone", "") or ""
use_language = kwargs.get("useLanguage", False)
language = kwargs.get("language", "Auto")
language = (kwargs.get("language") or "Auto").strip()
voices = kwargs.get("voices")
speech: Dict[str, Any] = {"text": text}
if use_voice:
if isinstance(voices, list) and len(voices) > 0:
speech["voices"] = voices
elif use_voice:
speech["voice"] = voice
if use_speed:
speech["speed"] = speed
+87
View File
@@ -0,0 +1,87 @@
"""
Runware Audio Inference Speech Voices node:
Builds speech.voices[] for multi-speaker audio inference speech payloads.
"""
import re
from typing import Any, Dict, List
class RunwareAudioInferenceSpeechVoices:
"""Build speech.voices[] list for Runware Audio Inference."""
MAX_VOICES = 4
_SPEAKER_ALIAS_PATTERN = re.compile(r"^[A-Za-z0-9]+$")
@classmethod
def INPUT_TYPES(cls):
optional_inputs = {}
for i in range(1, cls.MAX_VOICES + 1):
optional_inputs[f"useVoice{i}"] = ("BOOLEAN", {
"default": i == 1,
"tooltip": f"Enable to include speaker/voice pair #{i} in speech.voices.",
})
optional_inputs[f"speaker{i}"] = ("STRING", {
"default": f"Speaker{i}",
"tooltip": "Unique alphanumeric speaker alias (speech.voices[].speaker).",
})
optional_inputs[f"voice{i}"] = ("STRING", {
"default": "",
"tooltip": "Voice name for this speaker (speech.voices[].voice).",
})
return {
"required": {},
"optional": optional_inputs,
}
RETURN_TYPES = ("RUNWAREAUDIOINFERENCESPEECHVOICES",)
RETURN_NAMES = ("voices",)
FUNCTION = "createVoices"
CATEGORY = "Runware/Audio"
DESCRIPTION = (
"Build speech.voices[] for multi-speaker synthesis. "
"Each item contains speaker (unique alphanumeric alias) and voice name."
)
def createVoices(self, **kwargs) -> tuple[List[Dict[str, str]]]:
voices: List[Dict[str, str]] = []
seen_speakers = set()
for i in range(1, self.MAX_VOICES + 1):
use_voice = bool(kwargs.get(f"useVoice{i}", i == 1))
if not use_voice:
continue
speaker = (kwargs.get(f"speaker{i}") or "").strip()
voice = (kwargs.get(f"voice{i}") or "").strip()
if not speaker or not voice:
raise Exception(
f"Voice #{i} is enabled, but speaker{i} and voice{i} must both be provided."
)
if not self._SPEAKER_ALIAS_PATTERN.match(speaker):
raise Exception(
f"speaker{i} must be alphanumeric only (got: '{speaker}')."
)
if speaker in seen_speakers:
raise Exception(
f"Duplicate speaker alias '{speaker}'. Each speaker must be unique."
)
seen_speakers.add(speaker)
voices.append({
"speaker": speaker,
"voice": voice,
})
return (voices,)
NODE_CLASS_MAPPINGS = {
"RunwareAudioInferenceSpeechVoices": RunwareAudioInferenceSpeechVoices,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareAudioInferenceSpeechVoices": "Runware Audio Inference Speech Voices",
}
+4
View File
@@ -40,6 +40,9 @@ class RunwareAudioModelSearch:
"inworld:tts@1.5-mini (Inworld TTS-1.5 Mini)",
"inworld:tts@1.5-max (Inworld TTS-1.5 Max)",
],
"Google": [
"google:gemini@3.1-flash-tts (Gemini 3.1 Flash TTS)",
],
}
MODEL_PROVIDERS = [
@@ -53,6 +56,7 @@ class RunwareAudioModelSearch:
"xAI",
"MiniMax",
"Inworld",
"Google",
]
@classmethod
File diff suppressed because one or more lines are too long