"""ZONOS3 Mini · a minimal, session-isolated ZeroGPU Space.""" import html from functools import partial import json import os from pathlib import Path import tempfile import spaces # Before any torch or upstream model imports. import gradio as gr from config import DEFAULT_TEXT, GRADIO_VERSION, LANGUAGES, MAX_CHARS, MAX_SEED, ROOT, VERSION from controller import Controller, message from logging_setup import configure_logging from starlette.middleware import Middleware from boot import EntryShell configure_logging() if gr.__version__ != GRADIO_VERSION: raise RuntimeError(f"Use Gradio {GRADIO_VERSION}; this version is part of the UI contract.") CATALOG = json.loads((ROOT / "voices.json").read_text())["voices"] UI_ONLY = os.environ.get("ZONOS_UI_ONLY") == "1" if UI_ONLY: class PreviewEngine: def prepare(self, *args): from requests_schema import InputError raise InputError("UI preview only: model inference is disabled.") engine = PreviewEngine() aligner = None else: from engine import load_engine engine = load_engine() # Synchronous startup download / load / CUDA placement. from alignment import load_aligner aligner = load_aligner() RUNTIME = Path(os.environ.get("ZONOS_RUNTIME_DIR", str(Path(tempfile.gettempdir()) / "zonos3-mini"))) controller = Controller(engine, RUNTIME, [v["name"] for v in CATALOG], aligner=aligner) ASSETS = ROOT / "assets" EXAMPLES = json.loads((ROOT / "examples.json").read_text()) DEFAULT_PICKER = json.dumps({"language":"en_us", "voice":"Savannah", "custom":False}) def picker_markup(): langs = "".join(f'' for key, label in LANGUAGES.items()) voices = "".join(f'' for v in CATALOG) return ('
UI preview · inference disabled
') text = gr.HTML(value=DEFAULT_TEXT, html_template=(ASSETS / "script.html").read_text(), css_template=(ASSETS / "script.css").read_text(), js_on_load=(ASSETS / "script.js").read_text(), elem_id="script-input") player = gr.HTML(value="{}", html_template=(ASSETS / "player.html").read_text(), css_template=(ASSETS / "player.css").read_text(), js_on_load=(ASSETS / "wave.js").read_text() + (ASSETS / "player.js").read_text(), elem_id="speech-player") picker = gr.HTML(value=DEFAULT_PICKER, html_template=picker_markup(), css_template=PICKER_CSS, js_on_load=(ASSETS / "picker.js").read_text(), elem_id="voice-picker") with gr.Row(elem_id="action-row"): generate = gr.Button("Generate speech", variant="primary", elem_id="generate-speech") cancel = gr.Button("Cancel", visible=False, elem_id="cancel-speech") status = gr.HTML(message(""), elem_id="request-status") with gr.HTML(value="{}", source="Preset", html_template=settings_markup(), css_template=(ASSETS / "settings.css").read_text(), js_on_load=(ASSETS / "settings.js").read_text(), elem_id="voice-options") as settings: reference = gr.HTML(value="null", html_template=(ASSETS / "reference.html").read_text(), css_template=(ASSETS / "reference.css").read_text(), js_on_load=(ASSETS / "reference.js").read_text(), elem_id="reference-clip") clip = gr.Audio(label="Reference voice", type="filepath", format="wav", visible=False) # Stable API wire components. The HTML UI passes its state atomically via the # documented event JS preprocessor; API callers continue using the same inputs. source = gr.Radio(["Preset", "Voice clip"], value="Preset", label="Voice source", visible=False) consent = gr.Checkbox(value=False, label="I have permission to use this voice.", visible=False) seed = gr.Number(value=0, precision=0, minimum=0, maximum=MAX_SEED, label="Seed", visible=False) randomize = gr.Checkbox(value=True, label="New seed for each take", visible=False) normalize = gr.Checkbox(value=True, label="Normalize numbers and abbreviations", visible=False) rate = gr.Slider(0, 30, value=0, step=.5, label="Speaking rate · UTF-8 bytes/second", visible=False) temperature = gr.Slider(.1, 1.5, value=.75, step=.05, label="Temperature", visible=False) top_p = gr.Slider(.1, 1, value=.69, step=.01, label="Top-p", visible=False) top_k = gr.Slider(1, 400, value=80, step=1, label="Top-k", visible=False) repetition = gr.Slider(1, 4, value=2.4, step=.1, label="Repetition penalty", visible=False) gr.HTML(f'''''', elem_id="app-credits") def example(language): script, voice, _ = load_example(language) return script, voice, gr.HTML(source="Preset") for code in EXAMPLES: getattr(settings, "example_" + code)(partial(example, code), outputs=[text, picker, settings], queue=False, api_visibility="private") event = generate.click( controller.generate, [text, picker, source, clip, consent, normalize, seed, randomize, temperature, top_p, top_k, repetition, rate], [player, status, generate, cancel, seed], api_name="generate", concurrency_limit=8, concurrency_id="speech_requests", trigger_mode="once", show_progress="minimal", show_progress_on=[status], js=(ASSETS / "generate.js").read_text(), ) finish_ui = """() => { const status = document.getElementById('status-state'); if (status) status.dataset.busy = 'false'; document.getElementById('speech-player')?.dispatchEvent(new CustomEvent('zonos:activity', {detail:false})); document.getElementById('generate-speech').disabled = document.getElementById('voice-options')?.dataset.clip === 'true' && document.getElementById('reference-clip')?.dataset.busy === 'true'; }""" event.then(fn=None, js=finish_ui, api_visibility="private") event.failure(fn=None, js=finish_ui, api_visibility="private") cancel.click(controller.cancel, outputs=[status, cancel], queue=False, api_name="cancel", show_progress="hidden") demo.unload(controller.on_disconnect) demo.queue(max_size=16, default_concurrency_limit=1, api_open=False) if __name__ == "__main__": demo.launch(theme=theme, css=(ASSETS / "style.css").read_text(), allowed_paths=[str(controller.outputs.resolve())], blocked_paths=[str((ROOT / "vendor").resolve()), str(controller.jobs.directory.resolve())], max_file_size="25mb", show_error=False, max_threads=40, footer_links=[], ssr_mode=False, app_kwargs={"middleware":[Middleware(EntryShell, shell_path=ASSETS / "boot.html")]})