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
+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