Merge pull request #118 from Runware/feature-QwenTTS

Added support for Qwen
This commit is contained in:
Sirshendu Ganguly
2026-04-06 09:30:54 +05:30
committed by GitHub
6 changed files with 229 additions and 127 deletions
+17
View File
@@ -1275,6 +1275,12 @@ function audioSettingsToggleHandler(settingsNode) {
const repaintingStartWidget = settingsNode.widgets.find(w => w && w.name === "repaintingStart");
const useRepaintingEndWidget = settingsNode.widgets.find(w => w && w.name === "useRepaintingEnd");
const repaintingEndWidget = settingsNode.widgets.find(w => w && w.name === "repaintingEnd");
const useXVectorOnlyWidget = settingsNode.widgets.find(w => w && w.name === "useXVectorOnly");
const xVectorOnlyWidget = settingsNode.widgets.find(w => w && w.name === "xVectorOnly");
const useMaxNewTokensWidget = settingsNode.widgets.find(w => w && w.name === "useMaxNewTokens");
const maxNewTokensWidget = settingsNode.widgets.find(w => w && w.name === "maxNewTokens");
const useTranscriptWidget = settingsNode.widgets.find(w => w && w.name === "useTranscript");
const transcriptWidget = settingsNode.widgets.find(w => w && w.name === "transcript");
function toggleWidgetState(useWidget, paramWidget, paramName) {
if (!useWidget || !paramWidget) return;
@@ -1305,6 +1311,9 @@ function audioSettingsToggleHandler(settingsNode) {
if (useCoverConditioningScaleWidget && coverConditioningScaleWidget) toggleWidgetState(useCoverConditioningScaleWidget, coverConditioningScaleWidget, "coverConditioningScale");
if (useRepaintingStartWidget && repaintingStartWidget) toggleWidgetState(useRepaintingStartWidget, repaintingStartWidget, "repaintingStart");
if (useRepaintingEndWidget && repaintingEndWidget) toggleWidgetState(useRepaintingEndWidget, repaintingEndWidget, "repaintingEnd");
if (useXVectorOnlyWidget && xVectorOnlyWidget) toggleWidgetState(useXVectorOnlyWidget, xVectorOnlyWidget, "xVectorOnly");
if (useMaxNewTokensWidget && maxNewTokensWidget) toggleWidgetState(useMaxNewTokensWidget, maxNewTokensWidget, "maxNewTokens");
if (useTranscriptWidget && transcriptWidget) toggleWidgetState(useTranscriptWidget, transcriptWidget, "transcript");
}
function textInferenceSettingsToggleHandler(settingsNode) {
@@ -1417,6 +1426,8 @@ function audioInferenceSpeechToggleHandler(speechNode) {
const emotionWidget = speechNode.widgets.find(w => w && w.name === "emotion");
const useToneWidget = speechNode.widgets.find(w => w && w.name === "useTone");
const toneWidget = speechNode.widgets.find(w => w && w.name === "tone");
const useLanguageWidget = speechNode.widgets.find(w => w && w.name === "useLanguage");
const languageWidget = speechNode.widgets.find(w => w && w.name === "language");
function toggleWidgetState(useWidget, paramWidget, paramName) {
if (!useWidget || !paramWidget) return;
@@ -1439,6 +1450,7 @@ function audioInferenceSpeechToggleHandler(speechNode) {
if (usePitchWidget && pitchWidget) toggleWidgetState(usePitchWidget, pitchWidget, "pitch");
if (useEmotionWidget && emotionWidget) toggleWidgetState(useEmotionWidget, emotionWidget, "emotion");
if (useToneWidget && toneWidget) toggleWidgetState(useToneWidget, toneWidget, "tone");
if (useLanguageWidget && languageWidget) toggleWidgetState(useLanguageWidget, languageWidget, "language");
if (useVoiceWidget && voiceWidget) toggleWidgetState(useVoiceWidget, voiceWidget, "voice");
}
@@ -3021,6 +3033,11 @@ function audioModelSearchFilterHandler(audioModelSearchNode) {
"runware:ace-step@v1.5-base (ACE-Step v1.5 Base)",
"runware:ace-step@v1.5-turbo (ACE-Step v1.5 Turbo)",
],
"Alibaba": [
"alibaba:qwen@3-tts-1.7b-voicedesign (Qwen3 TTS 1.7B Voice Design)",
"alibaba:qwen@3-tts-1.7b-customvoice (Qwen3 TTS 1.7B Custom Voice)",
"alibaba:qwen@3-tts-1.7b-base (Qwen3 TTS 1.7B Base)",
],
"Dia": [
"runware:dia@v1.0 (Dia 1.6B)",
],
+1 -1
View File
@@ -136,7 +136,7 @@ class RunwareAudioInference:
"tooltip": "Custom inputs for audio generation (e.g., video URL for audio extraction)"
}),
"settings": ("RUNWAREAUDIOSETTINGS", {
"tooltip": "Connect Runware Audio Inference Settings for lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization."
"tooltip": "Connect Runware Audio Inference Settings for lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization, xVectorOnly, maxNewTokens, transcript, etc."
}),
"speech": ("RUNWARESPEECH", {
"tooltip": "Connect Runware Audio Inference Speech for speech synthesis (e.g. Minimax)"
+153 -124
View File
@@ -1,124 +1,153 @@
"""
Runware Audio Inference Speech node: builds the speech payload for Runware Audio Inference.
Connect its output to the "speech" input of Runware Audio Inference for speech synthesis (e.g. Minimax).
"""
from typing import Dict, Any
class RunwareAudioInferenceSpeech:
"""Builds speech payload (text, voice, speed, volume, pitch?, emotion?, tone?) for Runware Audio Inference."""
EMOTIONS = [
"happy", "sad", "angry", "fearful", "disgusted", "surprised",
"calm", "fluent", "whisper",
]
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {
"multiline": True,
"default": "Hello, this is a test of the speech synthesis model.",
"tooltip": "Text to synthesize as speech (speech.text)",
}),
},
"optional": {
"useVoice": ("BOOLEAN", {
"default": False,
"tooltip": "Enable to include voice in speech synthesis (speech.voice).",
}),
"voice": ("STRING", {
"default": "",
"tooltip": "Voice identifier.",
}),
"useSpeed": ("BOOLEAN", {"default": True, "tooltip": "Include speed (speech.speed)"}),
"speed": ("FLOAT", {
"default": 1.0,
"min": 0.5,
"max": 2.0,
"step": 0.01,
"tooltip": "Speech rate multiplier (speech.speed)",
}),
"useVolume": ("BOOLEAN", {"default": True, "tooltip": "Include volume (speech.volume)"}),
"volume": ("FLOAT", {
"default": 1.0,
"min": 1.0,
"max": 10.0,
"step": 0.1,
"tooltip": "Volume level 1-10 (speech.volume)",
}),
"usePitch": ("BOOLEAN", {"default": False, "tooltip": "Include pitch (speech.pitch)"}),
"pitch": ("FLOAT", {
"default": 0.0,
"min": -12.0,
"max": 12.0,
"step": 0.1,
"tooltip": "Pitch multiplier -12 to 12 (speech.pitch)",
}),
"useEmotion": ("BOOLEAN", {"default": False, "tooltip": "Include emotion (not available for speech.turbo)"}),
"emotion": (cls.EMOTIONS, {"default": "calm", "tooltip": "Emotion for speech (speech.emotion)"}),
"useTone": ("BOOLEAN", {"default": False, "tooltip": "Include pronunciation dictionary (speech.tone)"}),
"tone": ("STRING", {
"multiline": True,
"default": "",
"placeholder": "omg/oh my god\nother/other expansion",
"tooltip": "Pronunciation dictionary, one mapping per line (e.g. omg/oh my god)",
}),
}
}
RETURN_TYPES = ("RUNWARESPEECH",)
RETURN_NAMES = ("speech",)
FUNCTION = "createSpeech"
CATEGORY = "Runware/Audio"
DESCRIPTION = "Build speech parameters for Runware Audio Inference. Connect output to the 'speech' input of Runware Audio Inference."
def createSpeech(self, **kwargs) -> tuple:
"""Build the speech object for the API (connect to Runware Audio Inference 'speech' input)."""
text = (kwargs.get("text") or "").strip()
voice = (kwargs.get("voice") or "").strip()
use_voice = kwargs.get("useVoice", False)
use_speed = kwargs.get("useSpeed", True)
speed = float(kwargs.get("speed", 1.0))
use_volume = kwargs.get("useVolume", True)
volume = float(kwargs.get("volume", 1.0))
use_pitch = kwargs.get("usePitch", False)
pitch = float(kwargs.get("pitch", 0.0))
use_emotion = kwargs.get("useEmotion", False)
emotion = kwargs.get("emotion", "calm")
use_tone = kwargs.get("useTone", False)
tone_str = kwargs.get("tone", "") or ""
speech: Dict[str, Any] = {"text": text}
if use_voice:
speech["voice"] = voice
if use_speed:
speech["speed"] = speed
if use_volume:
speech["volume"] = volume
if use_pitch:
speech["pitch"] = pitch
if use_emotion:
speech["emotion"] = emotion
if use_tone and tone_str.strip():
tone_lines = [s.strip() for s in tone_str.replace(",", "\n").split("\n") if s.strip()]
if tone_lines:
speech["tone"] = tone_lines
if not text:
return ({},)
return (speech,)
NODE_CLASS_MAPPINGS = {
"RunwareAudioInferenceSpeech": RunwareAudioInferenceSpeech,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareAudioInferenceSpeech": "Runware Audio Inference Speech",
}
"""
Runware Audio Inference Speech node: builds the speech payload for Runware Audio Inference.
Connect its output to the "speech" input of Runware Audio Inference for speech synthesis (e.g. Minimax).
"""
from typing import Dict, Any
class RunwareAudioInferenceSpeech:
"""Builds speech payload (text, voice, speed, volume, pitch?, emotion?, tone?) for Runware Audio Inference."""
EMOTIONS = [
"happy", "sad", "angry", "fearful", "disgusted", "surprised",
"calm", "fluent", "whisper",
]
SPEECH_LANGUAGES = [
"Auto",
"Chinese",
"English",
"Japanese",
"Korean",
"German",
"French",
"Russian",
"Portuguese",
"Spanish",
"Italian",
]
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {
"multiline": True,
"default": "Hello, this is a test of the speech synthesis model.",
"tooltip": "Text to synthesize as speech (speech.text)",
}),
},
"optional": {
"useVoice": ("BOOLEAN", {
"default": False,
"tooltip": "Enable to include voice in speech synthesis (speech.voice).",
}),
"voice": ("STRING", {
"default": "",
"tooltip": "Voice identifier.",
}),
"useSpeed": ("BOOLEAN", {"default": True, "tooltip": "Include speed (speech.speed)"}),
"speed": ("FLOAT", {
"default": 1.0,
"min": 0.5,
"max": 2.0,
"step": 0.01,
"tooltip": "Speech rate multiplier (speech.speed)",
}),
"useVolume": ("BOOLEAN", {"default": True, "tooltip": "Include volume (speech.volume)"}),
"volume": ("FLOAT", {
"default": 1.0,
"min": 1.0,
"max": 10.0,
"step": 0.1,
"tooltip": "Volume level 1-10 (speech.volume)",
}),
"usePitch": ("BOOLEAN", {"default": False, "tooltip": "Include pitch (speech.pitch)"}),
"pitch": ("FLOAT", {
"default": 0.0,
"min": -12.0,
"max": 12.0,
"step": 0.1,
"tooltip": "Pitch multiplier -12 to 12 (speech.pitch)",
}),
"useEmotion": ("BOOLEAN", {"default": False, "tooltip": "Include emotion (not available for speech.turbo)"}),
"emotion": (cls.EMOTIONS, {"default": "calm", "tooltip": "Emotion for speech (speech.emotion)"}),
"useTone": ("BOOLEAN", {"default": False, "tooltip": "Include pronunciation dictionary (speech.tone)"}),
"tone": ("STRING", {
"multiline": True,
"default": "",
"placeholder": "omg/oh my god\nother/other expansion",
"tooltip": "Pronunciation dictionary, one mapping per line (e.g. omg/oh my god)",
}),
"useLanguage": ("BOOLEAN", {
"default": False,
"tooltip": "Enable to include language (speech.language).",
}),
"language": (cls.SPEECH_LANGUAGES, {
"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.",
}),
}
}
RETURN_TYPES = ("RUNWARESPEECH",)
RETURN_NAMES = ("speech",)
FUNCTION = "createSpeech"
CATEGORY = "Runware/Audio"
DESCRIPTION = (
"Build speech parameters for Runware Audio Inference (text, voice, speed, volume, pitch, emotion, tone, language, etc.). "
"Connect output to the 'speech' input of Runware Audio Inference."
)
def createSpeech(self, **kwargs) -> tuple:
"""Build the speech object for the API (connect to Runware Audio Inference 'speech' input)."""
text = (kwargs.get("text") or "").strip()
voice = (kwargs.get("voice") or "").strip()
use_voice = kwargs.get("useVoice", False)
use_speed = kwargs.get("useSpeed", True)
speed = float(kwargs.get("speed", 1.0))
use_volume = kwargs.get("useVolume", True)
volume = float(kwargs.get("volume", 1.0))
use_pitch = kwargs.get("usePitch", False)
pitch = float(kwargs.get("pitch", 0.0))
use_emotion = kwargs.get("useEmotion", False)
emotion = kwargs.get("emotion", "calm")
use_tone = kwargs.get("useTone", False)
tone_str = kwargs.get("tone", "") or ""
use_language = kwargs.get("useLanguage", False)
language = kwargs.get("language", "Auto")
speech: Dict[str, Any] = {"text": text}
if use_voice:
speech["voice"] = voice
if use_speed:
speech["speed"] = speed
if use_volume:
speech["volume"] = volume
if use_pitch:
speech["pitch"] = pitch
if use_emotion:
speech["emotion"] = emotion
if use_tone and tone_str.strip():
tone_lines = [s.strip() for s in tone_str.replace(",", "\n").split("\n") if s.strip()]
if tone_lines:
speech["tone"] = tone_lines
if use_language:
speech["language"] = language
if not text:
return ({},)
return (speech,)
NODE_CLASS_MAPPINGS = {
"RunwareAudioInferenceSpeech": RunwareAudioInferenceSpeech,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareAudioInferenceSpeech": "Runware Audio Inference Speech",
}
+6
View File
@@ -19,6 +19,11 @@ class RunwareAudioModelSearch:
"runware:ace-step@v1.5-base (ACE-Step v1.5 Base)",
"runware:ace-step@v1.5-turbo (ACE-Step v1.5 Turbo)",
],
"Alibaba": [
"alibaba:qwen@3-tts-1.7b-voicedesign (Qwen3 TTS 1.7B Voice Design)",
"alibaba:qwen@3-tts-1.7b-customvoice (Qwen3 TTS 1.7B Custom Voice)",
"alibaba:qwen@3-tts-1.7b-base (Qwen3 TTS 1.7B Base)",
],
"Dia": [
"runware:dia@v1.0 (Dia 1.6B)",
],
@@ -40,6 +45,7 @@ class RunwareAudioModelSearch:
"KlingAI",
"Mirelo",
"Ace",
"Alibaba",
"Dia",
"xAI",
"MiniMax",
+51 -2
View File
@@ -2,7 +2,7 @@
Runware Audio Inference Settings Node
Provides lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization,
bpm, keyScale, timeSignature, vocalLanguage, coverConditioningScale, repaintingStart,
and repaintingEnd for Runware Audio Inference.
repaintingEnd, xVectorOnly, maxNewTokens, transcript, and more for Runware Audio Inference.
"""
from typing import Dict, Any
@@ -151,6 +151,36 @@ class RunwareAudioSettings:
"default": 0.0,
"step": 0.01,
}),
"useXVectorOnly": ("BOOLEAN", {
"tooltip": "Enable to include xVectorOnly in audio generation settings.",
"default": False,
}),
"xVectorOnly": ("BOOLEAN", {
"tooltip": "true = speaker embedding only (no transcript needed, lower similarity). false = ICL mode (needs transcript, higher quality). Only used when 'Use X Vector Only' is enabled.",
"default": False,
"label_on": "true",
"label_off": "false",
}),
"useMaxNewTokens": ("BOOLEAN", {
"tooltip": "Enable to include maxNewTokens (audio output token cap) in audio generation settings.",
"default": False,
}),
"maxNewTokens": ("INT", {
"tooltip": "Audio output token cap. Higher = longer audio, but risk of generation hangs. Applies to all variants. Only used when 'Use Max New Tokens' is enabled.",
"default": 4096,
"min": 1,
"max": 262144,
"step": 1,
}),
"useTranscript": ("BOOLEAN", {
"tooltip": "Enable to include transcript (reference audio transcript) in audio generation settings.",
"default": False,
}),
"transcript": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "Transcript of the reference audio. Required for ICL mode; omit or leave empty for x-vector-only mode. Base variant only. Only used when 'Use Transcript' is enabled.",
}),
}
}
@@ -158,7 +188,11 @@ class RunwareAudioSettings:
RETURN_NAMES = ("settings",)
FUNCTION = "createSettings"
CATEGORY = "Runware/Audio"
DESCRIPTION = "Configure audio generation settings (lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization, bpm, keyScale, timeSignature, vocalLanguage, coverConditioningScale, repaintingStart, repaintingEnd) for Runware Audio Inference. Connect to Runware Audio Inference node."
DESCRIPTION = (
"Configure audio generation settings (lyrics, guidanceType, languageBoost, turbo, temperature, textNormalization, "
"bpm, keyScale, timeSignature, vocalLanguage, coverConditioningScale, repaintingStart, repaintingEnd, "
"xVectorOnly, maxNewTokens, transcript, etc.) for Runware Audio Inference. Connect to Runware Audio Inference node."
)
def createSettings(self, **kwargs) -> tuple[Dict[str, Any]]:
"""Create audio settings dict for API"""
@@ -188,6 +222,12 @@ class RunwareAudioSettings:
repainting_start = kwargs.get("repaintingStart", 0.0)
use_repainting_end = kwargs.get("useRepaintingEnd", False)
repainting_end = kwargs.get("repaintingEnd", 0.0)
use_x_vector_only = kwargs.get("useXVectorOnly", False)
x_vector_only = kwargs.get("xVectorOnly", False)
use_max_new_tokens = kwargs.get("useMaxNewTokens", False)
max_new_tokens = kwargs.get("maxNewTokens", 4096)
use_transcript = kwargs.get("useTranscript", False)
transcript = kwargs.get("transcript", "") or ""
settings: Dict[str, Any] = {}
@@ -230,6 +270,15 @@ class RunwareAudioSettings:
if use_repainting_end:
settings["repaintingEnd"] = float(repainting_end)
if use_x_vector_only:
settings["xVectorOnly"] = bool(x_vector_only)
if use_max_new_tokens:
settings["maxNewTokens"] = int(max_new_tokens)
if use_transcript and transcript.strip():
settings["transcript"] = transcript.strip()
return (settings,)
File diff suppressed because one or more lines are too long