IndexTTS-2 / app.py
harp-dev's picture
Check inputs before reserving the GPU, and label the output for what it holds.
2c906c0 verified
Raw History Blame Contribute Delete
3.69 kB
"""PyHarp front end for the original IndexTTS2 inference implementation."""
from __future__ import annotations
import sys
import uuid
from pathlib import Path
import gradio as gr
import spaces
from pyharp import ModelCard, Subcategory, build_endpoint
ROOT = Path(__file__).resolve().parent
sys.path.append(str(ROOT / "indextts"))
from indextts.infer_v2 import IndexTTS2 # noqa: E402
from tools.download_files import download_model_from_huggingface # noqa: E402
MODEL_DIR = ROOT / "checkpoints"
OUTPUT_DIR = ROOT / "outputs"
OUTPUT_DIR.mkdir(exist_ok=True)
download_model_from_huggingface(str(MODEL_DIR), str(MODEL_DIR / "hf_cache"))
tts = IndexTTS2(
model_dir=str(MODEL_DIR),
cfg_path=str(MODEL_DIR / "config.yaml"),
use_fp16=False,
use_deepspeed=False,
use_cuda_kernel=False,
)
def synthesize(text, voice_reference, emotion_reference, emotion_weight):
# Checked before the GPU is reserved, so a request missing an input spends none of the
# caller's ZeroGPU allowance
text = (text or "").strip()
if not text:
raise gr.Error("Please enter text to synthesize.")
if not voice_reference:
raise gr.Error("Please upload a voice reference recording.")
return speak(text, voice_reference, emotion_reference, emotion_weight)
@spaces.GPU
def speak(text, voice_reference, emotion_reference, emotion_weight):
output_path = OUTPUT_DIR / f"harp_{uuid.uuid4().hex}.wav"
try:
return tts.infer(
spk_audio_prompt=voice_reference,
text=text,
output_path=str(output_path),
emo_audio_prompt=emotion_reference or None,
emo_alpha=float(emotion_weight) * 0.8 if emotion_reference else 1.0,
max_text_tokens_per_segment=120,
)
except Exception as exc:
raise gr.Error(f"IndexTTS2 synthesis failed: {exc}") from exc
with gr.Blocks(title="IndexTTS2") as demo:
input_components = [
gr.Textbox(label="Text to Synthesize", lines=3).set_info(
"Chinese or English text to synthesize."
),
gr.Audio(type="filepath", label="Voice Reference").set_info(
"Required voice sample; the model uses up to 15 seconds."
),
gr.Audio(type="filepath", label="Emotion Reference")
.harp_required(False)
.set_info("Optional. Leave empty to follow the voice reference."),
gr.Slider(0.0, 1.0, value=0.8, step=0.05, label="Emotion Weight").set_info(
"Strength of the optional emotion reference."
),
]
output_components = [
gr.Audio(type="filepath", label="Synthesized Speech").set_info(
"The text spoken in the reference voice."
),
]
build_endpoint(
model_card=ModelCard(
name="IndexTTS2",
description=(
"Zero-shot speech synthesis with a voice reference and optional emotion "
"reference. Repository: https://github.com/index-tts/index-tts Paper: "
"https://arxiv.org/abs/2506.21619 Released under the bilibili Model Use "
"License, which forbids using the model or its output to train or "
"improve another AI model, except a non-commercial one."
),
author="Siyi Zhou, Yiquan Zhou, Yi He, Xun Zhou, Jinchao Wang, Wei Deng, Jingchen Shu",
tags=[Subcategory.TEXT_TO_SPEECH, 'voice-cloning', 'emotion-control'],
),
input_components=input_components,
output_components=output_components,
process_fn=synthesize,
)
if __name__ == "__main__":
demo.queue(default_concurrency_limit=1).launch(show_error=True, pwa=True)