Files
Aryan185-ComfyUI-ExternalAP…/elevenlabs_tts.py
T

137 lines
5.4 KiB
Python

import os
import io
import torch
import requests
import soundfile as sf
class ElevenLabsTTSNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"multiline": True, "default": ""}),
"model_id": ([
"eleven_multilingual_v2",
"eleven_turbo_v2_5",
"eleven_flash_v2_5",
"eleven_flash_v2",
"eleven_turbo_v2",
"eleven_multilingual_v1",
"eleven_v3"
],),
"output_format": ([
"mp3_44100_128",
"mp3_22050_32",
"mp3_44100_32",
"mp3_44100_64",
"mp3_44100_96",
"mp3_44100_192",
"pcm_8000",
"pcm_16000",
"pcm_22050",
"pcm_24000",
"pcm_44100",
"pcm_48000",
"ulaw_8000",
"alaw_8000",
"opus_48000_32",
"opus_48000_64",
"opus_48000_96",
"opus_48000_128",
"opus_48000_192"
],),
"voice_id": ("STRING", {"multiline": False, "default": "oPM3trUCF4e0vTcsrMQr"}),
"stability": ("FLOAT", {"default": 0.50, "min": 0.0, "max": 1.0, "step": 0.01}),
"similarity_boost": ("FLOAT", {"default": 0.50, "min": 0.0, "max": 1.0, "step": 0.01}),
"speed": ("FLOAT", {"default": 1.0, "min": 0.25, "max": 2.0, "step": 0.01}),
"style": ("FLOAT", {"default": 0.50, "min": 0.0, "max": 1.0, "step": 0.01}),
"use_speaker_boost": ("BOOLEAN", {"default": True}),
"seed": ("INT", {"default": 40, "min": 0, "max": 4294967294}),
"api_key": ("STRING", {"multiline": False, "default": "", "tooltip": "Directly put ElevenLabs API key or .env variable name (XI_API_KEY)"}),
},
"optional": {
"previous_text": ("STRING", {"multiline": True, "default": ""}),
"next_text": ("STRING", {"multiline": True, "default": ""}),
}
}
RETURN_TYPES = ("AUDIO",)
RETURN_NAMES = ("audio",)
FUNCTION = "generate_speech"
CATEGORY = "audio/generation"
def generate_speech(self, text, api_key, voice_id, model_id, output_format,
stability, similarity_boost, speed, style, use_speaker_boost, seed,
previous_text="", next_text=""):
if not text.strip():
raise ValueError("Text input cannot be empty.")
key = os.environ.get(api_key.strip(), api_key.strip()) or os.environ.get("XI_API_KEY")
if not key:
raise ValueError("No API key provided.")
url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}"
headers = {
"xi-api-key": key,
"Content-Type": "application/json"
}
if model_id == "eleven_v3":
allowed_stabilities = [0.0, 0.5, 1.0]
original_stability = stability
stability = min(allowed_stabilities, key=lambda x: abs(x - original_stability))
if stability != original_stability:
print(f"For 'eleven_v3' model, stability value must be one of: [0.0, 0.5, 1.0] (0.0 = Creative, 0.5 = Natural, 1.0 = Robust). Rounding stability to {stability}.")
voice_settings = {
"stability": stability,
"similarity_boost": similarity_boost,
"speed": speed,
"style": style,
"use_speaker_boost": use_speaker_boost
}
data = {
"text": text,
"voice_settings": voice_settings,
"model_id": model_id,
"seed": seed,
"output_format": output_format
}
if model_id == "eleven_v3":
if previous_text.strip() or next_text.strip():
print("Providing previous_text or next_text is not yet supported with the 'eleven_v3' model. Ignoring these inputs.")
else:
if previous_text.strip():
data["previous_text"] = previous_text
if next_text.strip():
data["next_text"] = next_text
response = requests.post(url, json=data, headers=headers)
if response.status_code != 200:
raise Exception(f"ElevenLabs 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_id', '')}-{kwargs.get('seed', 40)}"
NODE_CLASS_MAPPINGS = {"ElevenLabsTTSNode": ElevenLabsTTSNode}
NODE_DISPLAY_NAME_MAPPINGS = {"ElevenLabsTTSNode": "ElevenLabs TTS"}