Added support for Gemini TTS
This commit is contained in:
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user