File size: 7,162 Bytes
8d6ad36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""WaveGen on Hugging Face ZeroGPU. See README for an unchanged-environment local check."""

from __future__ import annotations

import argparse
import os
from pathlib import Path
import sys
import time

ROOT = Path(__file__).resolve().parent
EXAMPLE_DIR = ROOT / "src/wavegen_v_spec/infer/examples"
sys.path.insert(0, str(ROOT / "src"))

# No torch, model, or Gradio imports here: spaces must patch CUDA first.
ENGINES = {}
OUTPUT_DIR = None


def gpu_duration(ref_audio, ref_text, text, model_variant, seed, speed, mel_temperature):
    import gradio as gr
    from runtime import validate_request
    try:
        request = validate_request(ref_audio, ref_text, text, model_variant, seed, speed, mel_temperature)
    except (ValueError, OSError) as exc:
        raise gr.Error(str(exc)) from exc
    seconds = request["predicted_seconds"]
    # Conservative allocation budgets, not a promise of synthesis latency.
    return min(120, max(30, int(20 + 3 * seconds))) if model_variant == "V-Spec" else min(40, max(15, int(10 + seconds)))


def generate(ref_audio, ref_text, text, model_variant="V-Spec", seed=0, speed=1.0, mel_temperature=0.0):
    """This exact top-level function is GPU-decorated and registered with Gradio."""
    import gradio as gr
    import torch
    from runtime import save_audio, synthesize_request, validate_request
    try:
        request = validate_request(ref_audio, ref_text, text, model_variant, seed, speed, mel_temperature)
        if not torch.cuda.is_available() or not torch.cuda.is_bf16_supported():
            raise RuntimeError("A BF16-capable GPU is required for this demo.")
        if model_variant not in ENGINES or OUTPUT_DIR is None:
            raise RuntimeError("Models are not ready yet. Please retry after the Space has started.")
        started = time.perf_counter()
        sample_rate, waveform = synthesize_request(ENGINES, request)
        path = save_audio(sample_rate, waveform, OUTPUT_DIR)
        elapsed = time.perf_counter() - started
        steps = 4 if model_variant == "Flash" else 50
        return path, f"WaveGen {model_variant} 路 {steps} steps 路 Mel temp {request['mel_temperature']:g} 路 seed {request['seed']} 路 {elapsed:.1f}s"
    except ValueError as exc:
        raise gr.Error(str(exc)) from exc
    except (RuntimeError, OSError) as exc:
        # Avoid exposing server paths or model-cache details to public clients.
        print(f"Generation failed ({type(exc).__name__})", flush=True)
        raise gr.Error("Generation failed. Try a shorter sentence or retry when a GPU is available.") from exc


def create_app(generate_fn):
    import gradio as gr
    from runtime import MODEL_CHOICES, model_description
    global OUTPUT_DIR
    with gr.Blocks(title="WaveGen", analytics_enabled=False, delete_cache=(3600, 86400)) as demo:
        gr.Markdown("# WaveGen\n**NeurIPS 2026** 路 Sang-Hoon Lee and Ha-Yeong Choi 路 Ajou University and KT\n\n"
                    "Generate speech from a reference voice and your text. English and Mandarin Chinese are supported.\n\n"
                    "[Source Code](https://github.com/sh-lee-prml/WaveGen) 路 "
                    "[Models](https://huggingface.co/LeeSangHoon/WaveGen) 路 "
                    "[Demo Page](https://wavegen26.github.io/demo/) 路 "
                    "[Benchmark Demo](https://sh-lee-prml.github.io/wavegen-tts-demo/)")
        model_variant = gr.Dropdown(MODEL_CHOICES, value="V-Spec", label="Model")
        details = gr.Markdown(model_description("V-Spec"))
        gr.Markdown("Reference and generated speech must fit within **30 seconds combined**. "
                    "Use a short, clear reference and enter its exact transcript. Long text is rejected rather than cut off.")
        with gr.Row():
            with gr.Column():
                ref_audio = gr.Audio(label="Reference speech", sources=["upload", "microphone"], type="filepath")
                ref_text = gr.Textbox(label="Reference transcript", lines=3)
                text = gr.Textbox(label="Text to generate", lines=4, max_lines=10)
            with gr.Column():
                output = gr.Audio(label="Generated speech", type="filepath", format="wav",
                                  interactive=False, buttons=["download"])
                status = gr.Textbox(label="Generation details", interactive=False)
        with gr.Accordion("Advanced settings", open=False):
            with gr.Row():
                seed = gr.Number(value=0, precision=0, minimum=0, maximum=2**32 - 1, label="Seed")
                speed = gr.Slider(0.5, 2, value=1, step=0.05, label="Speed")
                mel_temperature = gr.Slider(0, 1, value=0, step=0.05, label="Mel temperature")
        button = gr.Button("Generate speech", variant="primary")
        gr.Examples(
            examples=[[str(EXAMPLE_DIR / "basic_ref_en.wav"),
                       "Some call me nature, others call me mother nature.",
                       "Here we generate something just for test."]],
            inputs=[ref_audio, ref_text, text], cache_examples=False)
        gr.Markdown("Use reference speech only with the speaker's permission. Do not use this demo for "
                    "deceptive impersonation. Clearly identify shared outputs as AI-generated audio. "
                    "The model weights are licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/).")
        model_variant.change(model_description, inputs=model_variant, outputs=details, queue=False)
        OUTPUT_DIR = Path(output.GRADIO_CACHE)
        button.click(generate_fn, inputs=[ref_audio, ref_text, text, model_variant, seed, speed, mel_temperature],
                     outputs=[output, status], concurrency_limit=1, concurrency_id="wavegen", api_name="generate")
    return demo.queue(default_concurrency_limit=1, max_size=16)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--local", action="store_true", help="Use an existing local CUDA environment without ZeroGPU scheduling.")
    parser.add_argument("--model-dir", type=Path, help="Optional existing ckpts directory for offline local verification.")
    parser.add_argument("--server-name", default=os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"))
    parser.add_argument("--server-port", type=int, default=int(os.getenv("GRADIO_SERVER_PORT", "7860")))
    args = parser.parse_args()
    if not args.local:
        import spaces  # MUST precede torch and any model imports.
    from runtime import MODEL_REPO, MODEL_REVISION, load_models
    global ENGINES
    ENGINES = load_models(model_dir=args.model_dir, repo_id=os.getenv("WAVEGEN_MODEL_REPO", MODEL_REPO),
                          revision=os.getenv("WAVEGEN_MODEL_REVISION", MODEL_REVISION))
    generate_fn = generate if args.local else spaces.GPU(duration=gpu_duration)(generate)
    demo = create_app(generate_fn)
    demo.launch(server_name=args.server_name, server_port=args.server_port,
                allowed_paths=[str(EXAMPLE_DIR / "basic_ref_en.wav")],
                max_file_size="20mb", share=False, show_error=False, ssr_mode=False)


if __name__ == "__main__":
    main()