Files
Bjornulf_custom_nodes/text_to_speech_kokoro.py
justumen 705362d032 0.68
2025-01-24 08:56:45 +01:00

154 lines
5.8 KiB
Python

import os
import requests
import random
class Everything(str):
def __ne__(self, __value: object) -> bool:
return False
VOICE_OPTIONS = {
"af_bella": "Bella (American Female) - af_bella",
"af_nicole": "Nicole (American Female) - af_nicole",
"af_sarah": "Sarah (American Female) - af_sarah",
"af_sky": "Sky (American Female) - af_sky",
"af": "Default (American Female) - af",
"am_adam": "Adam (American Male) - am_adam",
"am_michael": "Michael (American Male) - am_michael",
"bf_emma": "Emma (British Female) - bf_emma",
"bf_isabella": "Isabella (British Female) - bf_isabella",
"bm_george": "George (British Male) - bm_george",
"bm_lewis": "Lewis (British Male) - bm_lewis"
}
# Create a reversed mapping for display to value
VOICE_DISPLAY_TO_VALUE = {v: k for k, v in VOICE_OPTIONS.items()}
LANGUAGE_OPTIONS = {
"en-us": "English (US)",
"en-gb": "English (UK)",
"fr-fr": "French",
"ja": "Japanese",
"ko": "Korean",
"cmn": "Chinese (Mandarin)"
}
def download_if_not_exists(url, dest_path):
"""Download a file from a URL if it doesn't already exist."""
if not os.path.exists(dest_path):
print(f"Downloading {os.path.basename(dest_path)}...")
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
response = requests.get(url, stream=True)
response.raise_for_status()
with open(dest_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f"Downloaded {os.path.basename(dest_path)}")
class KokoroTTS:
BASE_DIR = "Bjornulf/Kokoro"
MODEL_FILE = os.path.join(BASE_DIR, "kokoro-v0_19.onnx")
VOICES_FILE = os.path.join(BASE_DIR, "voices.bin")
VOICE_LANGUAGES = {
'af': 'en-us', 'am': 'en-us', 'bf': 'en-gb', 'bm': 'en-gb'
}
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"multiline": True}),
"voice": (list(VOICE_OPTIONS.values()), {"default": "Default (American Female) - af"}),
"language": (list(LANGUAGE_OPTIONS.keys()), {"default": "en-us"}),
"speed": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.1}),
"autoplay": ("BOOLEAN", {"default": True}),
"save_audio": ("BOOLEAN", {"default": True}),
"overwrite": ("BOOLEAN", {"default": False}),
"seed": ("INT", {"default": 0}),
},
"optional": {
"connect_to_workflow": (Everything("*"), {"forceInput": True}),
}
}
RETURN_TYPES = ("AUDIO", "STRING", "STRING", "FLOAT")
RETURN_NAMES = ("AUDIO", "audio_path", "audio_full_path", "audio_duration")
FUNCTION = "generate_audio"
CATEGORY = "Bjornulf/Kokoro"
def generate_audio(self, text: str, voice: str, language: str, speed: float,
autoplay: bool, save_audio: bool,
overwrite: bool, seed: int, connect_to_workflow: any = None):
random.seed(seed)
config = {
"model_path": self.MODEL_FILE,
"voices_path": self.VOICES_FILE,
"speed": speed,
"language": language
}
download_if_not_exists(
"https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files/kokoro-v0_19.onnx",
config["model_path"]
)
download_if_not_exists(
"https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files/voices.bin",
config["voices_path"]
)
try:
from kokoro_onnx import Kokoro
import soundfile as sf
import torch
import numpy as np
from pydub import AudioSegment
from pydub.playback import play
voice_id = VOICE_DISPLAY_TO_VALUE[voice]
kokoro = Kokoro(config["model_path"], config["voices_path"])
# Check if file exists and overwrite is False
sanitized_text = ''.join(c if c.isalnum() else '_' for c in text[:50])
save_path = os.path.join("Bjornulf_TTS_Kokoro", voice_id, f"{sanitized_text}.wav")
full_path = os.path.abspath(save_path)
if os.path.exists(full_path) and not overwrite:
print(f"File exists: {full_path}. Loading existing audio.")
samples, sample_rate = sf.read(full_path)
if autoplay:
audio_segment = AudioSegment.from_file(full_path)
play(audio_segment)
else:
# Generate new audio
samples, sample_rate = kokoro.create(
text,
voice=voice_id,
speed=config["speed"],
lang=language
)
if save_audio:
os.makedirs(os.path.dirname(full_path), exist_ok=True)
sf.write(full_path, samples, sample_rate)
if autoplay:
try:
audio_segment = AudioSegment(
samples.tobytes(),
frame_rate=sample_rate,
sample_width=samples.dtype.itemsize,
channels=1
)
play(audio_segment)
except Exception as e:
print(f"Autoplay error: {e}")
audio_tensor = torch.from_numpy(samples).unsqueeze(0)
audio_output = {"waveform": audio_tensor.unsqueeze(0), "sample_rate": sample_rate}
return (audio_output, save_path, full_path, len(samples) / sample_rate)
except Exception as e:
print(f"Error in Kokoro TTS: {e}")
return ({"waveform": torch.zeros(1, 1, 1), "sample_rate": 22050}, "", "", 0.0)