Packed-TTS-Demo / app.py
HiMind's picture
Upload app.py
086ea6f verified
Raw
History Blame Contribute Delete
6.97 kB
from __future__ import annotations
import os
import threading
from functools import lru_cache
from pathlib import Path
from typing import Optional, Tuple
import gradio as gr
import numpy as np
import torch
from huggingface_hub import hf_hub_download
from PackedTTS import PackedTTS
MODEL_REPO_ID = os.getenv("PACKEDTTS_MODEL_REPO_ID", "HiMind/Packed-TTS")
BUNDLE_FILENAME = os.getenv("PACKEDTTS_BUNDLE_FILENAME", "tts.pt")
LOCAL_BUNDLE_PATH = Path(BUNDLE_FILENAME)
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
_MODEL_LOCK = threading.Lock()
_MODEL: Optional[PackedTTS] = None
_MODEL_ERROR: Optional[str] = None
_MODEL_PATH: Optional[Path] = None
# ---------------------------
# MODEL LOADING
# ---------------------------
def _resolve_bundle_path() -> Path:
if LOCAL_BUNDLE_PATH.exists():
return LOCAL_BUNDLE_PATH
return Path(
hf_hub_download(
repo_id=MODEL_REPO_ID,
repo_type="model",
filename=BUNDLE_FILENAME,
)
)
def _load_model() -> PackedTTS:
global _MODEL, _MODEL_ERROR, _MODEL_PATH
with _MODEL_LOCK:
if _MODEL is not None:
return _MODEL
bundle_path = _resolve_bundle_path()
_MODEL_PATH = bundle_path
_MODEL = PackedTTS.load(bundle_path, device=DEVICE)
_MODEL_ERROR = None
return _MODEL
def _safe_model():
try:
return _load_model(), None
except Exception:
return None, _MODEL_ERROR or "Model failed to load."
# ---------------------------
# CATALOG
# ---------------------------
@lru_cache(maxsize=1)
def _catalog() -> dict:
model, err = _safe_model()
if model is None:
return {
"voices": [],
"emotions": [],
"error": err,
"bundle_path": None,
}
emotions = model.list_emotions()
return {
"voices": model.list_voices(),
"emotions": list(emotions.keys()),
"emotion_counts": emotions,
"error": None,
"bundle_path": str(_MODEL_PATH),
}
def _choice(items):
return ["Auto / default", *items]
def _norm(x):
if not x or str(x).lower() in {"auto", "default", "auto / default"}:
return None
return str(x)
# ---------------------------
# MODEL CALL
# ---------------------------
def _generate(text, voice, emotion, voice_ref, emo_ref,
cfg_weight, temperature, exaggeration, seed):
try:
model = _load_model()
sr, audio, meta = model.generate(
text=text,
voice=_norm(voice),
emotion=_norm(emotion),
voice_ref=voice_ref or None,
emo_ref=emo_ref or None,
cfg_weight=float(cfg_weight),
temperature=float(temperature),
exaggeration=float(exaggeration),
seed=int(seed),
)
audio = np.nan_to_num(np.asarray(audio, dtype=np.float32))
return (sr, audio), (
f"Voice: {meta.get('voice')} | "
f"Emotion: {meta.get('emotion')} | "
f"SR: {sr}"
)
except Exception as e:
return None, f"❌ {type(e).__name__}: {e}"
# ---------------------------
# REFRESH UI
# ---------------------------
def _refresh():
c = _catalog()
voices = _choice(c["voices"])
emotions = _choice(c["emotions"])
status = (
f"❌ {c['error']}" if c["error"]
else f"✅ Loaded | Voices: {len(c['voices'])} | Emotions: {len(c['emotions'])}"
)
return (
gr.update(choices=voices, value="Auto / default"),
gr.update(choices=emotions, value="Auto / default"),
status,
)
# ---------------------------
# PRESETS
# ---------------------------
def p_basic():
return "Hello world test", "Sarah", "Disgust", 0.5, 0.8, 0.5, 42
def p_voice():
return "Voice only test", "Sarah", "Auto / default", 0.5, 0.8, 0.5, 42
def p_emotion():
return "Emotion only test", "Auto / default", "Happy", 0.5, 0.8, 0.5, 42
def p_default():
return "Default settings test", "Auto / default", "Auto / default", 0.5, 0.8, 0.5, 42
def p_expressive():
return "Very expressive speech test", "Sarah", "Angry", 0.7, 0.9, 0.6, 123
def p_refs():
return "Reference audio mode test", "Auto / default", "Auto / default", 0.5, 0.8, 0.5, 42
# ---------------------------
# UI
# ---------------------------
with gr.Blocks(title="PackedTTS", theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🎙️ PackedTTS — Voice + Emotion Synthesis")
status = gr.Markdown("Loading model...")
with gr.Row():
text = gr.Textbox(label="Text", lines=4)
with gr.Row():
voice = gr.Dropdown(label="Voice", choices=["Auto / default"], value="Auto / default")
emotion = gr.Dropdown(label="Emotion", choices=["Auto / default"], value="Auto / default")
with gr.Row():
voice_ref = gr.Audio(label="Voice reference", type="filepath", sources=["upload"])
emo_ref = gr.Audio(label="Emotion reference", type="filepath", sources=["upload"])
with gr.Row():
cfg = gr.Slider(0, 1.5, value=0.5, label="CFG")
temp = gr.Slider(0.1, 2.0, value=0.8, label="Temperature")
expo = gr.Slider(0, 1.0, value=0.5, label="Exaggeration")
seed = gr.Number(value=42, precision=0, label="Seed")
with gr.Row():
btn_gen = gr.Button("Generate", variant="primary")
btn_refresh = gr.Button("Refresh")
audio = gr.Audio(label="Output", type="numpy")
meta = gr.Textbox(label="Info", lines=2)
# ---------------- PRESETS ----------------
gr.Markdown("## Presets")
with gr.Row():
b1 = gr.Button("Basic")
b2 = gr.Button("Voice")
b3 = gr.Button("Emotion")
b4 = gr.Button("Default")
b5 = gr.Button("Expressive")
b6 = gr.Button("Refs")
# ---------------- IMPORTANT: api_name=False everywhere ----------------
btn_gen.click(
_generate,
[text, voice, emotion, voice_ref, emo_ref, cfg, temp, expo, seed],
[audio, meta],
api_name=False
)
btn_refresh.click(
_refresh,
[],
[voice, emotion, status],
api_name=False
)
demo.load(
_refresh,
[],
[voice, emotion, status],
api_name=False
)
b1.click(p_basic, [], [text, voice, emotion, cfg, temp, expo, seed], api_name=False)
b2.click(p_voice, [], [text, voice, emotion, cfg, temp, expo, seed], api_name=False)
b3.click(p_emotion, [], [text, voice, emotion, cfg, temp, expo, seed], api_name=False)
b4.click(p_default, [], [text, voice, emotion, cfg, temp, expo, seed], api_name=False)
b5.click(p_expressive, [], [text, voice, emotion, cfg, temp, expo, seed], api_name=False)
b6.click(p_refs, [], [text, voice, emotion, cfg, temp, expo, seed], api_name=False)
# IMPORTANT FIX: prevents schema crash again
demo.queue(
default_concurrency_limit=1,
api_open=False
).launch()