Added node for OpenAI TTS
This commit is contained in:
+3
-2
@@ -11,8 +11,9 @@ from .nano_banana import NODE_CLASS_MAPPINGS as NANO_BANANA_MAPPINGS, NODE_DISPL
|
||||
from .gemini_tts import NODE_CLASS_MAPPINGS as GEMINI_TTS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as GEMINI_TTS_DISPLAY
|
||||
from .elevenlabs_tts import NODE_CLASS_MAPPINGS as ELEVENLABS_TTS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as ELEVENLABS_TTS_DISPLAY
|
||||
from .flux2_replicate import NODE_CLASS_MAPPINGS as FLUX2_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as FLUX2_DISPLAY
|
||||
from .openai_tts import NODE_CLASS_MAPPINGS as OPENAI_TTS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as OPENAI_TTS_DISPLAY
|
||||
from .cleanup import NODE_CLASS_MAPPINGS as CLEANUP_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as CLEANUP_DISPLAY
|
||||
|
||||
NODE_CLASS_MAPPINGS = {**PRO_MAPPINGS, **GEMINI_MAPPINGS, **GEMINI_DIAR_MAPPINGS, **GPT_IMAGE_MAPPINGS, **IMAGEN_IMAGE_MAPPINGS, **IMAGEN_EDIT_MAPPINGS, **VEO_MAPPINGS, **VEO_GEMINI_MAPPINGS, **GEMINI_SEGMENT_MAPPINGS, **NANO_BANANA_MAPPINGS, **GEMINI_TTS_MAPPINGS, **ELEVENLABS_TTS_MAPPINGS, **FLUX2_MAPPINGS, **CLEANUP_MAPPINGS}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {**PRO_DISPLAY, **GEMINI_DISPLAY, **GEMINI_DIAR_DISPLAY, **GPT_IMAGE_DISPLAY, **IMAGEN_IMAGE_DISPLAY, **IMAGEN_EDIT_DISPLAY, **VEO_DISPLAY, **VEO_GEMINI_DISPLAY, **GEMINI_SEGMENT_DISPLAY, **NANO_BANANA_DISPLAY, **GEMINI_TTS_DISPLAY, **ELEVENLABS_TTS_DISPLAY, **FLUX2_DISPLAY, **CLEANUP_DISPLAY}
|
||||
NODE_CLASS_MAPPINGS = {**PRO_MAPPINGS, **GEMINI_MAPPINGS, **GEMINI_DIAR_MAPPINGS, **GPT_IMAGE_MAPPINGS, **IMAGEN_IMAGE_MAPPINGS, **IMAGEN_EDIT_MAPPINGS, **VEO_MAPPINGS, **VEO_GEMINI_MAPPINGS, **GEMINI_SEGMENT_MAPPINGS, **NANO_BANANA_MAPPINGS, **GEMINI_TTS_MAPPINGS, **ELEVENLABS_TTS_MAPPINGS, **FLUX2_MAPPINGS, **OPENAI_TTS_MAPPINGS, **CLEANUP_MAPPINGS}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {**PRO_DISPLAY, **GEMINI_DISPLAY, **GEMINI_DIAR_DISPLAY, **GPT_IMAGE_DISPLAY, **IMAGEN_IMAGE_DISPLAY, **IMAGEN_EDIT_DISPLAY, **VEO_DISPLAY, **VEO_GEMINI_DISPLAY, **GEMINI_SEGMENT_DISPLAY, **NANO_BANANA_DISPLAY, **GEMINI_TTS_DISPLAY, **ELEVENLABS_TTS_DISPLAY, **FLUX2_DISPLAY, **OPENAI_TTS_DISPLAY, **CLEANUP_DISPLAY}
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import os
|
||||
import io
|
||||
import torch
|
||||
import requests
|
||||
import soundfile as sf
|
||||
|
||||
class OpenAITTSNode:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "default": ""}),
|
||||
"model": ([
|
||||
"gpt-4o-mini-tts",
|
||||
"tts-1",
|
||||
"tts-1-hd"
|
||||
],),
|
||||
"voice": ([
|
||||
"alloy",
|
||||
"ash",
|
||||
"coral",
|
||||
"echo",
|
||||
"fable",
|
||||
"onyx",
|
||||
"nova",
|
||||
"sage",
|
||||
"shimmer"
|
||||
],),
|
||||
"response_format": ([
|
||||
"mp3",
|
||||
"opus",
|
||||
"aac",
|
||||
"flac",
|
||||
"wav",
|
||||
"pcm"
|
||||
],),
|
||||
"speed": ("FLOAT", {"default": 1.0, "min": 0.25, "max": 4.0, "step": 0.01}),
|
||||
"api_key": ("STRING", {"multiline": False, "default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"instructions": ("STRING", {"multiline": True, "default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO",)
|
||||
RETURN_NAMES = ("audio",)
|
||||
FUNCTION = "generate_speech"
|
||||
CATEGORY = "audio/generation"
|
||||
|
||||
def generate_speech(self, text, api_key, model, voice, response_format, speed, instructions=""):
|
||||
|
||||
if not text.strip():
|
||||
raise ValueError("Text input cannot be empty.")
|
||||
|
||||
key = api_key.strip() or os.environ.get("OPENAI_API_KEY")
|
||||
if not key:
|
||||
raise ValueError("No API key provided. Set OPENAI_API_KEY environment variable or provide it in the node.")
|
||||
|
||||
# Check if instructions are used with incompatible models
|
||||
if instructions.strip() and model in ["tts-1", "tts-1-hd"]:
|
||||
raise ValueError(f"Instructions parameter is not supported with model '{model}'. Please use 'gpt-4o-mini-tts' instead.")
|
||||
|
||||
url = "https://api.openai.com/v1/audio/speech"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
data = {
|
||||
"model": model,
|
||||
"input": text,
|
||||
"voice": voice,
|
||||
"response_format": response_format,
|
||||
"speed": speed
|
||||
}
|
||||
|
||||
# Add instructions only if provided and model supports it
|
||||
if instructions.strip() and model not in ["tts-1", "tts-1-hd"]:
|
||||
data["instructions"] = instructions
|
||||
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"OpenAI API Error: {response.status_code}, {response.text}")
|
||||
|
||||
# Decode audio with soundfile
|
||||
audio_buffer = io.BytesIO(response.content)
|
||||
waveform, sample_rate = sf.read(audio_buffer, dtype='float32')
|
||||
waveform = torch.from_numpy(waveform)
|
||||
|
||||
# Ensure correct shape [channels, samples]
|
||||
if waveform.dim() == 1:
|
||||
waveform = waveform.unsqueeze(0)
|
||||
else:
|
||||
waveform = waveform.t()
|
||||
|
||||
return ({"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate},)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return f"{kwargs.get('text', '')}-{kwargs.get('voice', '')}-{kwargs.get('model', '')}"
|
||||
|
||||
NODE_CLASS_MAPPINGS = {"OpenAITTSNode": OpenAITTSNode}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"OpenAITTSNode": "OpenAI Text-to-Speech"}
|
||||
Reference in New Issue
Block a user