import os
# HF Spaces only: the runtime container cannot write to $HF_HOME
# (/home/user/.cache/huggingface) — the build stage creates it as root while the
# app runs as `user`. The xet downloader keeps both its cache and its logs under
# that path, so hf_hub_download() dies with "Permission denied (os error 13)".
# Point xet at /tmp, which is writable. This must run before huggingface_hub is
# first imported, because its constants resolve these paths at import time.
os.environ.setdefault("HF_XET_CACHE", "/tmp/hf_xet")
import spaces
import html
import json
import sys
import threading
import time
import warnings
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category=UserWarning)
import pandas as pd
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(current_dir)
sys.path.append(os.path.join(current_dir, "indextts"))
import argparse
parser = argparse.ArgumentParser(
description="IndexTTS WebUI",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("--verbose", action="store_true", default=False, help="Enable verbose mode")
parser.add_argument("--port", type=int, default=7860, help="Port to run the web UI on")
parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to run the web UI on")
parser.add_argument("--model_dir", type=str, default="./checkpoints", help="Model checkpoints directory")
parser.add_argument("--version", type=str, default="2.5", choices=["2", "2.5"], help="Model version to use")
parser.add_argument("--fp16", action="store_true", default=False, help="Use FP16 for inference if available")
parser.add_argument("--deepspeed", action="store_true", default=False, help="Use DeepSpeed to accelerate if available")
parser.add_argument("--cuda_kernel", action="store_true", default=False, help="Use CUDA kernel for inference if available")
parser.add_argument("--accel", action="store_true", default=False, help="Use GPT2 acceleration engine if available")
parser.add_argument("--torch_compile", action="store_true", default=False, help="Use torch.compile to optimize s2mel if available")
parser.add_argument("--qwen_emo", action="store_true", default=False, help="Load QwenEmotion even on a low-VRAM GPU, where it is skipped by default")
parser.add_argument("--gui_seg_tokens", type=int, default=120, help="GUI: Max tokens per generation segment")
cmd_args = parser.parse_args()
# Validate optional acceleration dependencies early, so missing extras fail
# at startup instead of halfway through inference.
def _require_optional_extra(flag_name, module_name, install_cmd):
try:
__import__(module_name)
except ImportError:
parser.error(
f"--{flag_name} requires {module_name}, which is not installed. "
f"Install it with: {install_cmd}"
)
if cmd_args.accel:
_require_optional_extra("accel", "flash_attn", "uv sync --extra accel")
if cmd_args.torch_compile:
_require_optional_extra("torch_compile", "triton", "uv sync --extra torch_compile")
REQUIRED_FILES = {
"2": ["bpe.model", "gpt.pth", "s2mel.pth", "wav2vec2bert_stats.pt"],
"2.5": [
"gpt.pth",
"s2mel.pth",
"codec.pth",
"multilingual_zh_ja_yue_char_del.tiktoken",
"wav2vec2bert_stats.pt",
],
}
MODEL_REPO = {
"2": "IndexTeam/IndexTTS-2",
"2.5": "IndexTeam/IndexTTS-2.5",
}
required_files = REQUIRED_FILES[cmd_args.version]
missing = [f for f in required_files if not os.path.exists(os.path.join(cmd_args.model_dir, f))]
if missing:
print(
f"Model directory {cmd_args.model_dir} is incomplete for v{cmd_args.version} "
f"(missing: {', '.join(missing)}). Downloading {MODEL_REPO[cmd_args.version]}..."
)
from indextts.utils.model_download import snapshot_download
try:
snapshot_download(MODEL_REPO[cmd_args.version], local_dir=cmd_args.model_dir)
except Exception as e:
print(f"Failed to download model to {cmd_args.model_dir}: {e}")
sys.exit(1)
missing = [f for f in required_files if not os.path.exists(os.path.join(cmd_args.model_dir, f))]
if missing:
print(f"Failed to download model to {cmd_args.model_dir} (still missing: {', '.join(missing)}). Please download it manually.")
sys.exit(1)
print("Model downloaded successfully.")
from indextts.utils.model_download import ensure_config_available
try:
ensure_config_available(cmd_args.model_dir, version=cmd_args.version)
except Exception as e:
print(f"Failed to download config.yaml: {e}")
sys.exit(1)
IS_V25 = cmd_args.version == "2.5"
import gradio as gr
from indextts.utils.examples_downloader import ensure_examples_available
from tools.i18n.i18n import I18nAuto
if IS_V25:
from indextts.infer_v2_5 import IndexTTS2
else:
from indextts.infer_v2 import IndexTTS2
i18n = I18nAuto(language="Auto")
MODE = 'local'
# Download example audio files if missing
ensure_examples_available()
LOW_VRAM_THRESHOLD_GB = 10.0
def detect_vram_gb():
# HF Spaces only: ZeroGPU attaches the device inside @spaces.GPU functions,
# so there is no GPU at import time and probing here would either report
# nothing useful or initialise CUDA too early. This Space runs on a 24 GB
# A10G, so the low-VRAM path is not needed.
return None
_vram_gb = detect_vram_gb()
LOW_VRAM = _vram_gb is not None and _vram_gb < LOW_VRAM_THRESHOLD_GB
HALF_PRECISION = cmd_args.fp16 or LOW_VRAM
LOAD_QWEN_EMO = cmd_args.qwen_emo or not LOW_VRAM
if LOW_VRAM:
print(
f">> {_vram_gb:.1f} GB VRAM detected (< {LOW_VRAM_THRESHOLD_GB:.0f} GB): "
"enabling half precision"
+ ("" if LOAD_QWEN_EMO else " and skipping QwenEmotion")
)
if not LOAD_QWEN_EMO:
print(">> Emotion control from a text description is unavailable; pass --qwen_emo to force it.")
def build_tts(use_accel=False, use_torch_compile=False):
"""Build an IndexTTS2 instance with the requested acceleration options."""
import torch
kwargs = dict(
model_dir=cmd_args.model_dir,
cfg_path=os.path.join(cmd_args.model_dir, "config.yaml"),
use_deepspeed=cmd_args.deepspeed,
use_cuda_kernel=cmd_args.cuda_kernel,
use_accel=use_accel,
use_torch_compile=use_torch_compile,
use_qwen_emo=LOAD_QWEN_EMO,
)
if IS_V25:
use_bf16 = HALF_PRECISION and torch.cuda.is_bf16_supported()
if HALF_PRECISION and not use_bf16:
print(">> BF16 is not supported on this device, falling back to full precision.")
kwargs["use_bf16"] = use_bf16
else:
kwargs["use_fp16"] = HALF_PRECISION
return IndexTTS2(**kwargs)
tts = build_tts(use_accel=cmd_args.accel, use_torch_compile=cmd_args.torch_compile)
# 支持的语言列表
LANGUAGES = {
"中文": "zh_CN",
"English": "en_US"
}
EMO_CHOICES_ALL = [i18n("与音色参考音频相同"),
i18n("使用情感参考音频"),
i18n("使用情感向量控制"),
i18n("使用情感描述文本控制")]
EMO_CHOICES_OFFICIAL = EMO_CHOICES_ALL[:-1] # skip experimental features
EMO_CHOICES_EXPERIMENTAL = EMO_CHOICES_ALL if LOAD_QWEN_EMO else EMO_CHOICES_OFFICIAL
os.makedirs("outputs/tasks",exist_ok=True)
os.makedirs("prompts",exist_ok=True)
MAX_LENGTH_TO_USE_SPEED = 70
example_cases = []
with open("examples/cases.jsonl", "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
example = json.loads(line)
if example.get("emo_audio",None):
emo_audio_path = os.path.join("examples",example["emo_audio"])
else:
emo_audio_path = None
case = [os.path.join("examples", example.get("prompt_audio", "sample_prompt.wav")),
EMO_CHOICES_ALL[example.get("emo_mode",0)],
example.get("text"),
emo_audio_path,
example.get("emo_weight",1.0),
example.get("emo_text",""),
example.get("emo_vec_1",0),
example.get("emo_vec_2",0),
example.get("emo_vec_3",0),
example.get("emo_vec_4",0),
example.get("emo_vec_5",0),
example.get("emo_vec_6",0),
example.get("emo_vec_7",0),
example.get("emo_vec_8",0),
]
# v2.5 needs the language per-example so ja/es/ar demos auto-select it
if IS_V25:
case.append(example.get("lang", "ZH"))
example_cases.append(case)
def get_example_cases(include_experimental = False):
if include_experimental and LOAD_QWEN_EMO:
return example_cases # show every example
# exclude emotion control mode 3 (emotion from text description)
return [x for x in example_cases if x[1] != EMO_CHOICES_ALL[3]]
def format_glossary_markdown():
"""将词汇表转换为Markdown表格格式"""
if IS_V25 or not hasattr(tts, 'normalizer') or not tts.normalizer.term_glossary:
return i18n("暂无术语")
lines = [f"| {i18n('术语')} | {i18n('中文读法')} | {i18n('英文读法')} |"]
lines.append("|---|---|---|")
for term, reading in tts.normalizer.term_glossary.items():
zh = reading.get("zh", "") if isinstance(reading, dict) else reading
en = reading.get("en", "") if isinstance(reading, dict) else reading
lines.append(f"| {term} | {zh} | {en} |")
return "\n".join(lines)
@spaces.GPU
def gen_single(emo_control_method,prompt, text,
lang_choice,
emo_ref_path, emo_weight,
vec1, vec2, vec3, vec4, vec5, vec6, vec7, vec8,
emo_text,emo_random,
max_text_tokens_per_segment=120,
duration_factor=1.0,
*args, progress=gr.Progress()):
output_path = None
if not output_path:
output_path = os.path.join("outputs", f"spk_{int(time.time())}.wav")
# set gradio progress
tts.gr_progress = progress
do_sample, top_p, top_k, temperature, \
length_penalty, num_beams, repetition_penalty, max_mel_tokens = args
kwargs = {
"do_sample": bool(do_sample),
"top_p": float(top_p),
"top_k": int(top_k) if int(top_k) > 0 else None,
"temperature": float(temperature),
"length_penalty": float(length_penalty),
"num_beams": num_beams,
"repetition_penalty": float(repetition_penalty),
"max_mel_tokens": int(max_mel_tokens),
# "typical_sampling": bool(typical_sampling),
# "typical_mass": float(typical_mass),
}
if type(emo_control_method) is not int:
emo_control_method = emo_control_method.value
if emo_control_method == 0: # emotion from speaker
emo_ref_path = None # remove external reference audio
if emo_control_method == 1: # emotion from reference audio
pass
if emo_control_method == 2: # emotion from custom vectors
vec = [vec1, vec2, vec3, vec4, vec5, vec6, vec7, vec8]
vec = tts.normalize_emo_vec(vec, apply_bias=True)
else:
# don't use the emotion vector inputs for the other modes
vec = None
if emo_text == "":
# erase empty emotion descriptions; `infer()` will then automatically use the main prompt
emo_text = None
print(f"Emo control mode:{emo_control_method},weight:{emo_weight},vec:{vec}")
infer_kwargs = dict(
spk_audio_prompt=prompt, text=text,
output_path=output_path,
emo_audio_prompt=emo_ref_path, emo_alpha=emo_weight,
emo_vector=vec,
use_emo_text=(emo_control_method==3), emo_text=emo_text, use_random=emo_random,
verbose=cmd_args.verbose,
max_text_tokens_per_segment=int(max_text_tokens_per_segment),
duration_factor=float(duration_factor),
**kwargs,
)
if IS_V25:
infer_kwargs["lang"] = lang_choice or "ZH"
output = tts.infer(**infer_kwargs)
return gr.update(value=output,visible=True)
def update_prompt_audio():
update_button = gr.update(interactive=True)
return update_button
def create_warning_message(warning_text):
return gr.HTML(f"
{html.escape(warning_text)}
")
def create_experimental_warning_message():
return create_warning_message(i18n('提示:此功能为实验版,结果尚不稳定,我们正在持续优化中。'))
with gr.Blocks(
title=f"IndexTTS-{cmd_args.version} Demo",
css="""
/* Make the voice reference audio upload area more compact. */
#prompt_audio_compact .audio-container,
#prompt_audio_compact .upload-container {
min-height: 110px !important;
}
#prompt_audio_compact .empty {
min-height: 80px !important;
}
""",
) as demo:
mutex = threading.Lock()
arxiv_id = "2601.03888" if IS_V25 else "2506.21619"
gr.HTML(f'''
IndexTTS-{cmd_args.version}
''')
with gr.Tab(i18n("音频生成")):
os.makedirs("prompts", exist_ok=True)
# Voice reference section
gr.Markdown(f"### {i18n('音色参考音频')}")
prompt_audio = gr.Audio(
label="",
key="prompt_audio",
sources=["upload", "microphone"],
type="filepath",
elem_classes=["compact-audio"],
elem_id="prompt_audio_compact",
)
# Text input and generation section
gr.Markdown(f"### {i18n('文本')}")
with gr.Row(equal_height=False):
with gr.Column(scale=2):
input_text_single = gr.TextArea(
label="",
key="input_text_single",
placeholder=i18n("请输入目标文本"),
info=f"{i18n('当前模型版本')}{tts.model_version or '1.0'}",
lines=5,
)
if IS_V25:
lang_dropdown = gr.Dropdown(
choices=["ZH", "EN", "JA", "AR", "ES"],
value="ZH",
label=i18n("语言"),
)
else:
lang_dropdown = gr.State(value=None)
duration_factor = gr.Slider(
label=i18n("时长系数"), minimum=0.5, maximum=2.0, value=1.0, step=0.01,
info=f'{i18n("快")} ← — {i18n("不变")} — → {i18n("慢")}',
key="duration_factor",
)
with gr.Column(scale=1):
gen_button = gr.Button(
i18n("生成语音"), key="gen_button", interactive=True
)
output_audio = gr.Audio(
label=i18n("生成结果"), visible=True, key="output_audio"
)
with gr.Row():
experimental_checkbox = gr.Checkbox(label=i18n("显示实验功能"), value=False)
_has_glossary = not IS_V25 and hasattr(tts, 'normalizer')
glossary_checkbox = gr.Checkbox(label=i18n("开启术语词汇读音"), value=tts.normalizer.enable_glossary if _has_glossary else False, visible=_has_glossary)
with gr.Accordion(i18n("功能设置")):
# 情感控制选项部分
with gr.Row():
emo_control_method = gr.Radio(
choices=EMO_CHOICES_OFFICIAL,
type="index",
value=EMO_CHOICES_OFFICIAL[0],label=i18n("情感控制方式"))
# we MUST have an extra, INVISIBLE list of *all* emotion control
# methods so that gr.Dataset() can fetch ALL control mode labels!
# otherwise, the gr.Dataset()'s experimental labels would be empty!
emo_control_method_all = gr.Radio(
choices=EMO_CHOICES_ALL,
type="index",
value=EMO_CHOICES_ALL[0], label=i18n("情感控制方式"),
visible=False) # do not render
# 情感参考音频部分
with gr.Group(visible=False) as emotion_reference_group:
with gr.Row():
emo_upload = gr.Audio(label=i18n("上传情感参考音频"), type="filepath")
# 情感随机采样
with gr.Row(visible=False) as emotion_randomize_group:
emo_random = gr.Checkbox(label=i18n("情感随机采样"), value=False)
# 情感向量控制部分
with gr.Group(visible=False) as emotion_vector_group:
with gr.Row():
with gr.Column():
vec1 = gr.Slider(label=i18n("喜"), minimum=0.0, maximum=1.0, value=0.0, step=0.05)
vec2 = gr.Slider(label=i18n("怒"), minimum=0.0, maximum=1.0, value=0.0, step=0.05)
vec3 = gr.Slider(label=i18n("哀"), minimum=0.0, maximum=1.0, value=0.0, step=0.05)
vec4 = gr.Slider(label=i18n("惧"), minimum=0.0, maximum=1.0, value=0.0, step=0.05)
with gr.Column():
vec5 = gr.Slider(label=i18n("厌恶"), minimum=0.0, maximum=1.0, value=0.0, step=0.05)
vec6 = gr.Slider(label=i18n("低落"), minimum=0.0, maximum=1.0, value=0.0, step=0.05)
vec7 = gr.Slider(label=i18n("惊喜"), minimum=0.0, maximum=1.0, value=0.0, step=0.05)
vec8 = gr.Slider(label=i18n("平静"), minimum=0.0, maximum=1.0, value=0.0, step=0.05)
with gr.Group(visible=False) as emo_text_group:
create_experimental_warning_message()
with gr.Row():
emo_text = gr.Textbox(label=i18n("情感描述文本"),
placeholder=i18n("请输入情绪描述(或留空以自动使用目标文本作为情绪描述)"),
value="",
info=i18n("例如:委屈巴巴、危险在悄悄逼近"))
with gr.Row(visible=False) as emo_weight_group:
emo_weight = gr.Slider(label=i18n("情感权重"), minimum=0.0, maximum=1.0, value=0.65, step=0.01)
# 术语词汇表管理
with gr.Accordion(i18n("自定义术语词汇读音"), open=False, visible=_has_glossary and tts.normalizer.enable_glossary) as glossary_accordion:
gr.Markdown(i18n("自定义个别专业术语的读音"))
with gr.Row():
with gr.Column(scale=1):
glossary_term = gr.Textbox(
label=i18n("术语"),
placeholder="IndexTTS2",
)
glossary_reading_zh = gr.Textbox(
label=i18n("中文读法"),
placeholder="Index T-T-S 二",
)
glossary_reading_en = gr.Textbox(
label=i18n("英文读法"),
placeholder="Index T-T-S two",
)
btn_add_term = gr.Button(i18n("添加术语"), scale=1)
with gr.Column(scale=2):
glossary_table = gr.Markdown(
value=format_glossary_markdown()
)
with gr.Accordion(i18n("高级生成参数设置"), open=False, visible=True) as advanced_settings_group:
with gr.Row():
with gr.Column(scale=1):
gr.Markdown(f"**{i18n('GPT2 采样设置')}** _{i18n('参数会影响音频多样性和生成速度详见')} [Generation strategies](https://huggingface.co/docs/transformers/main/en/generation_strategies)._")
with gr.Row():
do_sample = gr.Checkbox(label="do_sample", value=True, info=i18n("是否进行采样"))
temperature = gr.Slider(label="temperature", minimum=0.1, maximum=2.0, value=0.8, step=0.1)
with gr.Row():
top_p = gr.Slider(label="top_p", minimum=0.0, maximum=1.0, value=0.8, step=0.01)
top_k = gr.Slider(label="top_k", minimum=0, maximum=100, value=30, step=1)
num_beams = gr.Slider(label="num_beams", value=3, minimum=1, maximum=10, step=1)
with gr.Row():
repetition_penalty = gr.Number(label="repetition_penalty", precision=None, value=10.0, minimum=0.1, maximum=20.0, step=0.1)
length_penalty = gr.Number(label="length_penalty", precision=None, value=0.0, minimum=-2.0, maximum=2.0, step=0.1)
max_mel_tokens = gr.Slider(label="max_mel_tokens", value=1500, minimum=50, maximum=tts.cfg.gpt.max_mel_tokens, step=10, info=i18n("生成Token最大数量,过小导致音频被截断"), key="max_mel_tokens")
# with gr.Row():
# typical_sampling = gr.Checkbox(label="typical_sampling", value=False, info="不建议使用")
# typical_mass = gr.Slider(label="typical_mass", value=0.9, minimum=0.0, maximum=1.0, step=0.1)
with gr.Column(scale=2):
gr.Markdown(f'**{i18n("分句设置")}** _{i18n("参数会影响音频质量和生成速度")}_')
with gr.Row():
initial_value = max(20, min(tts.cfg.gpt.max_text_tokens, cmd_args.gui_seg_tokens))
max_text_tokens_per_segment = gr.Slider(
label=i18n("分句最大Token数"), value=initial_value, minimum=20, maximum=tts.cfg.gpt.max_text_tokens, step=2, key="max_text_tokens_per_segment",
info=i18n("建议80~200之间,值越大,分句越长;值越小,分句越碎;过小过大都可能导致音频质量不高"),
)
with gr.Accordion(i18n("预览分句结果"), open=True) as segments_settings:
segments_preview = gr.Dataframe(
headers=[i18n("序号"), i18n("分句内容"), i18n("Token数")],
key="segments_preview",
wrap=True,
)
advanced_params = [
do_sample, top_p, top_k, temperature,
length_penalty, num_beams, repetition_penalty, max_mel_tokens,
# typical_sampling, typical_mass,
]
# we must use `gr.Dataset` to support dynamic UI rewrites, since `gr.Examples`
# binds tightly to UI and always restores the initial state of all components,
# such as the list of available choices in emo_control_method.
# these components are NOT "connected". it just reads the column labels/available
# states from them, so we MUST link to the "all options" versions of all components,
# such as `emo_control_method_all` (to be able to see EXPERIMENTAL text labels)!
example_components = [prompt_audio,
emo_control_method_all, # important: support all mode labels!
input_text_single,
emo_upload,
emo_weight,
emo_text,
vec1, vec2, vec3, vec4, vec5, vec6, vec7, vec8]
# v2.5: append the language column so ja/es/ar examples auto-select it
if IS_V25:
example_components.append(lang_dropdown)
example_table = gr.Dataset(label="Examples",
samples_per_page=20,
samples=get_example_cases(include_experimental=False),
type="values",
components=example_components
)
def on_example_click(example):
print(f"Example clicked: ({len(example)} values) = {example!r}")
updates = [
gr.update(value=example[0]),
gr.update(value=example[1]),
gr.update(value=example[2]),
gr.update(value=example[3]),
gr.update(value=example[4]),
gr.update(value=example[5]),
gr.update(value=example[6]),
gr.update(value=example[7]),
gr.update(value=example[8]),
gr.update(value=example[9]),
gr.update(value=example[10]),
gr.update(value=example[11]),
gr.update(value=example[12]),
gr.update(value=example[13]),
]
# v2.5: also restore the per-example language
if IS_V25:
updates.append(gr.update(value=example[14]))
return updates
# click() event works on both desktop and mobile UI
example_outputs = [prompt_audio,
emo_control_method,
input_text_single,
emo_upload,
emo_weight,
emo_text,
vec1, vec2, vec3, vec4, vec5, vec6, vec7, vec8]
if IS_V25:
example_outputs.append(lang_dropdown)
example_table.click(on_example_click,
inputs=[example_table],
outputs=example_outputs
)
def on_input_text_change(text, max_text_tokens_per_segment):
if text and len(text) > 0:
if IS_V25:
# v2.5 uses tiktoken encoder, no split_segments; show token count only
tokens = tts.tokenizer.encode(text, allowed_special='all')
data = [[0, text, len(tokens)]]
else:
text_tokens_list = tts.tokenizer.tokenize(text)
segments = tts.tokenizer.split_segments(text_tokens_list, max_text_tokens_per_segment=int(max_text_tokens_per_segment))
data = []
for i, s in enumerate(segments):
segment_str = ''.join(s)
tokens_count = len(s)
data.append([i, segment_str, tokens_count])
return {
segments_preview: gr.update(value=data, visible=True, type="array"),
}
else:
df = pd.DataFrame([], columns=[i18n("序号"), i18n("分句内容"), i18n("Token数")])
return {
segments_preview: gr.update(value=df),
}
# 术语词汇表事件处理函数
def on_add_glossary_term(term, reading_zh, reading_en):
"""添加术语到词汇表并自动保存"""
if IS_V25 or not hasattr(tts, 'normalizer'):
return gr.update()
term = term.rstrip()
reading_zh = reading_zh.rstrip()
reading_en = reading_en.rstrip()
if not term:
gr.Warning(i18n("请输入术语"))
return gr.update()
if not reading_zh and not reading_en:
gr.Warning(i18n("请至少输入一种读法"))
return gr.update()
# 构建读法数据
if reading_zh and reading_en:
reading = {"zh": reading_zh, "en": reading_en}
elif reading_zh:
reading = {"zh": reading_zh}
elif reading_en:
reading = {"en": reading_en}
else:
reading = reading_zh or reading_en
# 添加到词汇表
tts.normalizer.term_glossary[term] = reading
# 自动保存到文件
try:
tts.normalizer.save_glossary_to_yaml(tts.glossary_path)
gr.Info(i18n("词汇表已更新"), duration=1)
except Exception as e:
gr.Error(i18n("保存词汇表时出错"))
print(f"Error details: {e}")
return gr.update()
# 更新Markdown表格
return gr.update(value=format_glossary_markdown())
def on_method_change(emo_control_method):
if emo_control_method == 1: # emotion reference audio
return (gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=True)
)
elif emo_control_method == 2: # emotion vectors
return (gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=True)
)
elif emo_control_method == 3: # emotion text description
return (gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=True)
)
else: # 0: same as speaker voice
return (gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False)
)
emo_control_method.change(on_method_change,
inputs=[emo_control_method],
outputs=[emotion_reference_group,
emotion_randomize_group,
emotion_vector_group,
emo_text_group,
emo_weight_group]
)
def on_experimental_change(is_experimental, current_mode_index):
# 切换情感控制选项
new_choices = EMO_CHOICES_EXPERIMENTAL if is_experimental else EMO_CHOICES_OFFICIAL
# if their current mode selection doesn't exist in new choices, reset to 0.
# we don't verify that OLD index means the same in NEW list, since we KNOW it does.
new_index = current_mode_index if current_mode_index < len(new_choices) else 0
return (
gr.update(choices=new_choices, value=new_choices[new_index]),
gr.update(samples=get_example_cases(include_experimental=is_experimental)),
)
experimental_checkbox.change(
on_experimental_change,
inputs=[experimental_checkbox, emo_control_method],
outputs=[emo_control_method, example_table]
)
def on_glossary_checkbox_change(is_enabled):
"""控制术语词汇表的可见性"""
if IS_V25 or not hasattr(tts, 'normalizer'):
return gr.update(visible=False)
tts.normalizer.enable_glossary = is_enabled
return gr.update(visible=is_enabled)
glossary_checkbox.change(
on_glossary_checkbox_change,
inputs=[glossary_checkbox],
outputs=[glossary_accordion]
)
input_text_single.change(
on_input_text_change,
inputs=[input_text_single, max_text_tokens_per_segment],
outputs=[segments_preview]
)
max_text_tokens_per_segment.change(
on_input_text_change,
inputs=[input_text_single, max_text_tokens_per_segment],
outputs=[segments_preview]
)
prompt_audio.upload(update_prompt_audio,
inputs=[],
outputs=[gen_button])
def on_demo_load():
"""页面加载时重新加载glossary数据"""
if IS_V25 or not hasattr(tts, 'normalizer'):
return gr.update()
try:
tts.normalizer.load_glossary_from_yaml(tts.glossary_path)
except Exception as e:
gr.Error(i18n("加载词汇表时出错"))
print(f"Failed to reload glossary on page load: {e}")
return gr.update(value=format_glossary_markdown())
# 术语词汇表事件绑定
btn_add_term.click(
on_add_glossary_term,
inputs=[glossary_term, glossary_reading_zh, glossary_reading_en],
outputs=[glossary_table]
)
# 页面加载时重新加载glossary
demo.load(
on_demo_load,
inputs=[],
outputs=[glossary_table]
)
gen_button.click(gen_single,
inputs=[emo_control_method,prompt_audio, input_text_single,
lang_dropdown,
emo_upload, emo_weight,
vec1, vec2, vec3, vec4, vec5, vec6, vec7, vec8,
emo_text,emo_random,
max_text_tokens_per_segment,
duration_factor,
*advanced_params,
],
outputs=[output_audio])
if __name__ == "__main__":
demo.queue(20)
demo.launch(server_name=cmd_args.host, server_port=cmd_args.port)