Files
neverbiasu-ComfyUI-ChatTTS/node.py
T
2025-04-25 01:30:40 +08:00

195 lines
5.8 KiB
Python

import os
import torch
from typing import Any, Dict
import folder_paths
from .tools.seeder import TorchSeedContext
from .tools.logger import get_logger
from .ChatTTS.core import Chat
logger = get_logger("ChatTTS")
class ChatTTSLoader:
@classmethod
def INPUT_TYPES(cls):
chattts_dir = os.path.join(folder_paths.models_dir, "chattts")
if not os.path.exists(chattts_dir):
os.makedirs(chattts_dir, exist_ok=True)
files = [
f
for f in os.listdir(chattts_dir)
if f.endswith(('.pth', '.pt', '.ckpt', '.safetensors'))
]
return {
"required": {
"model_path": (["(auto)"] + sorted(files), {"default": "(auto)"}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "load"
CATEGORY = "chattts"
def load(self, model_path):
# 获取模型路径
chattts_dir = os.path.join(folder_paths.models_dir, "chattts")
local_model_path = None
if model_path == "(auto)":
# 检查本地模型
files = [
f
for f in os.listdir(chattts_dir)
if f.endswith(('.pth', '.pt', '.ckpt', '.safetensors'))
]
if files:
# 使用本地模型
local_model_path = os.path.join(chattts_dir, files[0])
logger.info(f"Using local model: {local_model_path}")
else:
# 本地没有模型,尝试下载
logger.info("No local model found, attempting to download...")
else:
local_model_path = os.path.join(chattts_dir, model_path)
if not os.path.exists(local_model_path):
logger.error(f"Model file not found: {local_model_path}")
raise FileNotFoundError(f"Model file not found: {local_model_path}")
# 初始化ChatTTS并加载模型
chat = Chat(logger)
if local_model_path:
# 使用本地模型文件
loaded = chat.load("custom", custom_path=local_model_path)
else:
# 从huggingface下载模型
logger.info("Downloading model from huggingface...")
loaded = chat.load("huggingface", force_redownload=False)
if not loaded:
raise RuntimeError("Failed to load ChatTTS model")
logger.info(f"ChatTTS model loaded successfully")
return (chat,)
class ChatTTS_SpeakerSample:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"seed": ("INT", {"default": 2, "min": 1, "max": 4294967295}),
}
}
RETURN_TYPES = ("DICT",)
FUNCTION = "sample"
CATEGORY = "chattts"
def sample(self, model, seed):
# 严格按照 WebUI 实现,使用随机种子采样
with TorchSeedContext(seed):
spk_emb = model.sample_random_speaker()
logger.info(f"Sampled speaker with seed: {seed}")
return ({"spk_emb": spk_emb},)
class ChatTTS_Sampler:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"text": ("STRING", {"multiline": True, "dynamicPrompts": False}),
},
"optional": {
"speaker_params": ("DICT", {}),
"seed": ("INT", {"default": 2, "min": 1, "max": 4294967295}),
"temperature": (
"FLOAT",
{"default": 0.3, "min": 0.0, "max": 2.0, "step": 0.01},
),
"top_P": (
"FLOAT",
{"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01},
),
"top_K": ("INT", {"default": 20, "min": 1, "max": 100}),
"stream": ("BOOLEAN", {"default": False}),
"split_batch": ("INT", {"default": 0, "min": 0, "max": 32}),
},
}
RETURN_TYPES = ("AUDIO", "DICT")
FUNCTION = "synthesize"
CATEGORY = "chattts"
def synthesize(
self,
model,
text,
speaker_params=None,
seed=2,
temperature=0.3,
top_P=0.7,
top_K=20,
stream=False,
split_batch=0,
):
# 严格按照 WebUI funcs.py 实现
spk_emb = speaker_params.get("spk_emb", "") if speaker_params else ""
# 使用WebUI相同的参数构造
params_infer_code = ChatTTS.Chat.InferCodeParams(
spk_emb=spk_emb,
temperature=temperature,
top_P=top_P,
top_K=top_K,
manual_seed=seed,
)
with TorchSeedContext(seed):
# 按照 WebUI 的方式调用 infer 方法
wav = model.infer(
text,
skip_refine_text=True,
params_infer_code=params_infer_code,
stream=stream,
split_text=split_batch > 0,
max_split_batch=split_batch,
)
# 处理返回值
if stream:
audio = None
for sr, arr in wav:
audio = {"waveform": torch.tensor(arr).unsqueeze(0), "sample_rate": sr}
break
else:
audio = {
"waveform": torch.tensor(wav[0]).unsqueeze(0),
"sample_rate": 24000,
}
meta = {"seed": seed, "spk_emb": spk_emb}
logger.info(f"Synthesized audio: text={text[:20]}..., seed={seed}")
return (audio, meta)
NODE_CLASS_MAPPINGS = {
"ChatTTSLoader": ChatTTSLoader,
"ChatTTS_SpeakerSample": ChatTTS_SpeakerSample,
"ChatTTS_Sampler": ChatTTS_Sampler,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ChatTTSLoader": "ChatTTS 模型加载",
"ChatTTS_SpeakerSample": "ChatTTS 说话人采样",
"ChatTTS_Sampler": "ChatTTS 文本转语音",
}