Offexb commited on
Commit
ee3ebe0
Β·
verified Β·
1 Parent(s): e438e12

Premium UI, quota-smart GPU duration, reference auto-clean, quota panel

Browse files

1) Premium dark UI with hero header, advanced settings accordion and tips. 2) Per-request GPU duration estimation so visitors only reserve the quota they need. 3) Reference clips are auto-trimmed and normalised for closer cloning. 4) Live ZeroGPU quota panel decoded from the visitor's token plus session usage tracking. 5) Sample voice examples.

Files changed (2) hide show
  1. README.md +16 -16
  2. app.py +259 -59
README.md CHANGED
@@ -1,33 +1,33 @@
1
  ---
2
- title: IndexTTS-2 Gradio App
3
  emoji: πŸŽ™οΈ
4
- colorFrom: blue
5
- colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 5.45.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- # IndexTTS-2 Gradio App
13
 
14
- This Space provides a Gradio interface for English speech synthesis with IndexTTS-2.
15
 
16
- ## Build and runtime dependencies
17
 
18
- The Space SDK is pinned to Gradio 5.45.0. That version keeps huggingface-hub on the 0.x line required by the official IndexTTS 4.52.1 stack and avoids the Gradio 6.28/tokenizers resolver conflict.
 
19
 
20
- The static requirements file contains the official IndexTTS runtime dependencies, including the CUDA 12.8 PyTorch 2.8 pins. It intentionally omits gradio, spaces, transformers, and tokenizers:
21
 
22
- - Gradio and spaces are supplied by the Hugging Face Space runtime.
23
- - tokenizers==0.21.0 is installed lazily with --no-deps.
24
- - transformers==4.52.1 is installed lazily with --no-deps.
25
- - the official IndexTTS repository is installed lazily with --no-deps.
26
 
27
- The model snapshot is downloaded lazily to /tmp/index-tts-2 on the first generation request. The app uses IndexTTS2 from indextts.infer_v2 and passes the CUDA/configuration options required by IndexTTS-2.
28
 
29
- ## UI behavior
30
 
31
- Upload or record a reference voice, enter English text, confirm the reference-voice permission checkbox, and press Generate Voice. An optional emotion reference, emotion strength, text-emotion checkbox, and seed are available.
32
 
33
- The launcher uses the standard Python guard and queues up to four generation requests.
 
1
  ---
2
+ title: Offex Voice Studio
3
  emoji: πŸŽ™οΈ
4
+ colorFrom: indigo
5
+ colorTo: pink
6
  sdk: gradio
7
  sdk_version: 5.45.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # Offex Voice Studio
13
 
14
+ Premium Gradio interface for high-fidelity voice cloning with IndexTTS-2.
15
 
16
+ ## Quota-smart design
17
 
18
+ - All pip installs and the full model snapshot (main + auxiliary models, ~8 GB) are downloaded by a background warm-up thread on the CPU at Space startup, so the ZeroGPU allocation is used only for synthesis.
19
+ - The GPU duration is estimated per request from the input length instead of a fixed value, so visitors only reserve the quota they actually need.
20
 
21
+ ## Quality features
22
 
23
+ - Reference clips are automatically trimmed of silence, capped to 20 seconds and peak-normalised for a closer clone.
24
+ - Optional emotion reference and text-derived emotion controls.
25
+ - Seed control for reproducible generations.
 
26
 
27
+ ## Runtime notes
28
 
29
+ The Space SDK is pinned to Gradio 5.45.0 to stay on the huggingface-hub 0.x line required by the official IndexTTS 4.52.1 stack.
30
 
31
+ The static requirements file intentionally omits gradio, spaces, transformers and tokenizers; those are supplied by the Space runtime or installed lazily with --no-deps. tensorboard is pinned to 2.18.0 for NumPy 2.x compatibility (2.9.1 crashed on the removed `np.bool8` alias), and app.py also re-adds the alias defensively for any other legacy dependency.
32
 
33
+ Upload or record a reference voice, enter English text, confirm the permission checkbox, and press Generate Voice.
app.py CHANGED
@@ -5,6 +5,7 @@ import subprocess
5
  import sys
6
  import tempfile
7
  import threading
 
8
  from pathlib import Path
9
  from typing import Optional
10
 
@@ -86,6 +87,18 @@ def _download_model() -> Path:
86
  return MODEL_DIR
87
 
88
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  def _patch_numpy_compat() -> None:
90
  """Restore NumPy aliases removed in NumPy 2.x for legacy pinned libraries.
91
 
@@ -98,16 +111,18 @@ def _patch_numpy_compat() -> None:
98
  np.bool8 = np.bool_
99
 
100
 
101
- def _download_aux_models() -> None:
102
- """Pre-download auxiliary models (w2v-bert-2.0, MaskGCT codec, CAMPPlus,
103
- BigVGAN) into the model cache on the CPU.
104
-
105
- IndexTTS2 downloads these itself when given aux_paths=None, which would
106
- happen inside the ZeroGPU allocation and waste quota on ~5 GB of downloads.
107
- """
108
- from indextts.utils.model_download import ensure_models_available
109
 
110
- ensure_models_available(str(MODEL_DIR))
 
 
 
 
 
 
111
 
112
 
113
  def _warm_up() -> None:
@@ -133,6 +148,8 @@ def _warm_up() -> None:
133
  _warmup_thread = threading.Thread(target=_warm_up, daemon=True)
134
  _warmup_thread.start()
135
 
 
 
136
 
137
  def _find_config(model_dir: Path) -> str:
138
  candidates = [
@@ -204,6 +221,29 @@ def _validate_audio(path: Optional[str], label: str) -> str:
204
  return str(audio)
205
 
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  def _set_seed(seed: int) -> None:
208
  random.seed(seed)
209
 
@@ -232,7 +272,29 @@ def _supported_kwargs(function, values: dict) -> dict:
232
  return {key: value for key, value in values.items() if key in parameters}
233
 
234
 
235
- @spaces.GPU(duration=150)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  def synthesize(
237
  reference_audio: Optional[str],
238
  text: str,
@@ -245,7 +307,7 @@ def synthesize(
245
  if not permission:
246
  raise gr.Error("Please confirm that you have permission to use the reference voice.")
247
 
248
- reference_audio = _validate_audio(reference_audio, "Reference audio")
249
  text = (text or "").strip()
250
 
251
  if not text:
@@ -272,7 +334,9 @@ def synthesize(
272
  if not isinstance(use_text_emotion, bool):
273
  raise gr.Error("Use text emotion must be a boolean.")
274
 
275
- emotion_audio = _validate_audio(emotion_audio, "Emotion audio") if emotion_audio else None
 
 
276
  output_dir = OUTPUT_DIR
277
  output_dir.mkdir(parents=True, exist_ok=True)
278
  output_path = output_dir / "index-tts-2-output.wav"
@@ -280,6 +344,7 @@ def synthesize(
280
  if output_path.exists():
281
  output_path.unlink()
282
 
 
283
  try:
284
  _set_seed(numeric_seed)
285
  tts = _get_tts()
@@ -312,63 +377,179 @@ def synthesize(
312
  if not output_path.is_file() or output_path.stat().st_size == 0:
313
  raise RuntimeError("IndexTTS-2 returned without creating an audio file.")
314
 
315
- return str(output_path)
 
316
  except gr.Error:
317
  raise
318
  except Exception as exc:
319
  raise gr.Error(f"Synthesis failed: {type(exc).__name__}: {exc}") from exc
320
 
321
 
322
- with gr.Blocks(title="Offex IndexTTS-2 Voice Clone") as demo:
323
- gr.Markdown(
324
- "# Offex IndexTTS-2 Voice Clone\n"
325
- "Generate English speech from a permitted reference voice."
326
- )
327
 
328
- with gr.Row():
329
- reference_audio = gr.Audio(
330
- label="Reference voice",
331
- sources=["upload", "microphone"],
332
- type="filepath",
333
- )
334
- emotion_audio = gr.Audio(
335
- label="Optional emotion reference",
336
- sources=["upload", "microphone"],
337
- type="filepath",
338
- )
339
 
340
- text = gr.Textbox(
341
- label="English text",
342
- placeholder="Enter the English text to synthesize...",
343
- lines=6,
344
- )
345
 
346
- permission = gr.Checkbox(
347
- label="I have permission to use this reference voice",
348
- value=False,
349
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
  with gr.Row():
352
- emotion_strength = gr.Slider(
353
- minimum=0,
354
- maximum=1,
355
- value=0.6,
356
- step=0.05,
357
- label="Emotion strength",
358
- )
359
- use_text_emotion = gr.Checkbox(
360
- label="Use emotion information from text",
361
- value=False,
362
- )
363
- seed = gr.Number(
364
- label="Seed",
365
- value=1234,
366
- precision=0,
367
- minimum=0,
368
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
- generate = gr.Button("Generate Voice", variant="primary", size="lg")
371
- output = gr.Audio(label="Generated voice", type="filepath", autoplay=True)
 
372
 
373
  generate.click(
374
  fn=synthesize,
@@ -381,9 +562,28 @@ with gr.Blocks(title="Offex IndexTTS-2 Voice Clone") as demo:
381
  use_text_emotion,
382
  seed,
383
  ],
384
- outputs=output,
 
 
 
 
385
  )
386
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
 
388
  if __name__ == "__main__":
389
- demo.queue(max_size=4).launch()
 
5
  import sys
6
  import tempfile
7
  import threading
8
+ import time
9
  from pathlib import Path
10
  from typing import Optional
11
 
 
87
  return MODEL_DIR
88
 
89
 
90
+ def _download_aux_models() -> None:
91
+ """Pre-download auxiliary models (w2v-bert-2.0, MaskGCT codec, CAMPPlus,
92
+ BigVGAN) into the model cache on the CPU.
93
+
94
+ IndexTTS2 downloads these itself when given aux_paths=None, which would
95
+ happen inside the ZeroGPU allocation and waste quota on ~5 GB of downloads.
96
+ """
97
+ from indextts.utils.model_download import ensure_models_available
98
+
99
+ ensure_models_available(str(MODEL_DIR))
100
+
101
+
102
  def _patch_numpy_compat() -> None:
103
  """Restore NumPy aliases removed in NumPy 2.x for legacy pinned libraries.
104
 
 
111
  np.bool8 = np.bool_
112
 
113
 
114
+ def _download_example_voice() -> Optional[str]:
115
+ """Fetch one sample reference voice so users can try the app instantly."""
116
+ try:
117
+ from huggingface_hub import hf_hub_download
 
 
 
 
118
 
119
+ return hf_hub_download(
120
+ repo_id="IndexTeam/IndexTTS-2-Demo",
121
+ repo_type="space",
122
+ filename="examples/voice_02.wav",
123
+ )
124
+ except Exception:
125
+ return None
126
 
127
 
128
  def _warm_up() -> None:
 
148
  _warmup_thread = threading.Thread(target=_warm_up, daemon=True)
149
  _warmup_thread.start()
150
 
151
+ EXAMPLE_VOICE = _download_example_voice()
152
+
153
 
154
  def _find_config(model_dir: Path) -> str:
155
  candidates = [
 
221
  return str(audio)
222
 
223
 
224
+ def _clean_reference(path: str) -> str:
225
+ """Trim silence, cap length and normalise the reference clip for a closer clone."""
226
+ try:
227
+ import librosa
228
+ import numpy as np
229
+ import soundfile as sf
230
+
231
+ audio, sample_rate = librosa.load(path, sr=None, mono=True)
232
+ trimmed, _ = librosa.effects.trim(audio, top_db=30)
233
+ if len(trimmed) < sample_rate // 2:
234
+ trimmed = audio
235
+ trimmed = trimmed[: int(sample_rate * 20.0)]
236
+ peak = float(np.max(np.abs(trimmed))) if trimmed.size else 0.0
237
+ if peak > 0:
238
+ trimmed = trimmed * (0.9 / peak)
239
+ cleaned = f"{path}.cleaned.wav"
240
+ sf.write(cleaned, trimmed, sample_rate)
241
+ return cleaned
242
+ except Exception as exc:
243
+ print(f"[reference-clean] skipped: {type(exc).__name__}: {exc}", flush=True)
244
+ return path
245
+
246
+
247
  def _set_seed(seed: int) -> None:
248
  random.seed(seed)
249
 
 
272
  return {key: value for key, value in values.items() if key in parameters}
273
 
274
 
275
+ def _estimate_duration(
276
+ reference_audio: Optional[str],
277
+ text: str,
278
+ permission: bool,
279
+ emotion_audio: Optional[str],
280
+ emotion_strength: float,
281
+ use_text_emotion: bool,
282
+ seed,
283
+ ) -> int:
284
+ """Request only as much ZeroGPU time as this request realistically needs.
285
+
286
+ The platform pre-checks the declared duration against the visitor's
287
+ remaining quota, so smaller realistic durations let people fit far more
288
+ generations into their daily allowance.
289
+ """
290
+ if _tts is None:
291
+ return 150 # cold start: one-time model load plus synthesis
292
+
293
+ characters = len((text or "").strip())
294
+ return int(min(120, 30 + characters * 0.05))
295
+
296
+
297
+ @spaces.GPU(duration=_estimate_duration)
298
  def synthesize(
299
  reference_audio: Optional[str],
300
  text: str,
 
307
  if not permission:
308
  raise gr.Error("Please confirm that you have permission to use the reference voice.")
309
 
310
+ reference_audio = _clean_reference(_validate_audio(reference_audio, "Reference audio"))
311
  text = (text or "").strip()
312
 
313
  if not text:
 
334
  if not isinstance(use_text_emotion, bool):
335
  raise gr.Error("Use text emotion must be a boolean.")
336
 
337
+ emotion_audio = (
338
+ _clean_reference(_validate_audio(emotion_audio, "Emotion audio")) if emotion_audio else None
339
+ )
340
  output_dir = OUTPUT_DIR
341
  output_dir.mkdir(parents=True, exist_ok=True)
342
  output_path = output_dir / "index-tts-2-output.wav"
 
344
  if output_path.exists():
345
  output_path.unlink()
346
 
347
+ started = time.perf_counter()
348
  try:
349
  _set_seed(numeric_seed)
350
  tts = _get_tts()
 
377
  if not output_path.is_file() or output_path.stat().st_size == 0:
378
  raise RuntimeError("IndexTTS-2 returned without creating an audio file.")
379
 
380
+ elapsed = round(time.perf_counter() - started, 1)
381
+ return str(output_path), elapsed
382
  except gr.Error:
383
  raise
384
  except Exception as exc:
385
  raise gr.Error(f"Synthesis failed: {type(exc).__name__}: {exc}") from exc
386
 
387
 
388
+ # --------------------------------------------------------------------------
389
+ # Quota panel helpers (CPU only, never touches the GPU)
390
+ # --------------------------------------------------------------------------
 
 
391
 
392
+ DAILY_HINT = "Free accounts get ~5 min/day, PRO ~40 min/day. The window resets 24h after your first use."
 
 
 
 
 
 
 
 
 
 
393
 
 
 
 
 
 
394
 
395
+ def _read_ip_token(request) -> dict:
396
+ """Decode the ZeroGPU identity token HF attaches to every request."""
397
+ import base64
398
+ import json
399
+
400
+ headers = getattr(request, "headers", None) or {}
401
+ token = headers.get("x-ip-token") or headers.get("X-IP-Token")
402
+ if not token:
403
+ return {}
404
+ try:
405
+ payload = json.loads(base64.urlsafe_b64decode(f"{token.split('.')[1]}=="))
406
+ return payload if isinstance(payload, dict) else {}
407
+ except Exception:
408
+ return {}
409
+
410
+
411
+ def _quota_html(request, used_seconds: float) -> str:
412
+ claims = _read_ip_token(request)
413
+ identity = claims.get("user") or ("signed-in visitor" if claims.get("sub") else "guest (not signed in)")
414
+
415
+ used = max(0.0, float(used_seconds or 0))
416
+ bar = min(100, round(used / 300 * 100)) # free-tier daily allowance estimate
417
+ color = "#22c55e" if bar < 50 else "#eab308" if bar < 80 else "#ef4444"
418
+
419
+ return f"""
420
+ <div class="quota-card">
421
+ <div class="quota-title">⚑ ZeroGPU quota</div>
422
+ <div class="quota-row"><span>Identity</span><b>{identity}</b></div>
423
+ <div class="quota-row"><span>GPU time used on this Space (this session)</span><b>~{used:.0f}s</b></div>
424
+ <div class="quota-bar"><div class="quota-fill" style="width:{bar}%;background:{color};"></div></div>
425
+ <div class="quota-note">Bar = your session usage vs a ~5 min free daily allowance (estimate). {DAILY_HINT}</div>
426
+ <div class="quota-note">πŸ’‘ This Space auto-trims requests to the minimum GPU time needed, so your quota lasts longer. Upgrade to <a href="https://huggingface.co/pricing" target="_blank" style="color:#818cf8;">HF PRO</a> for ~8x quota.</div>
427
+ </div>
428
+ """
429
+
430
+
431
+ def _update_session_usage(used_seconds, last_elapsed, request: gr.Request):
432
+ total = float(used_seconds or 0) + float(last_elapsed or 0)
433
+ return total, _quota_html(request, total)
434
+
435
+
436
+ def _refresh_quota(request: gr.Request):
437
+ return _quota_html(request, 0)
438
+
439
+
440
+ # --------------------------------------------------------------------------
441
+ # Premium UI
442
+ # --------------------------------------------------------------------------
443
+
444
+ CUSTOM_CSS = """
445
+ .gradio-container {max-width: 1180px !important; margin: 0 auto !important;}
446
+ #hero {
447
+ background: linear-gradient(120deg, #4f46e5 0%, #7c3aed 45%, #db2777 100%);
448
+ border-radius: 18px; padding: 26px 30px; margin-bottom: 8px;
449
+ box-shadow: 0 12px 32px rgba(79, 70, 229, 0.35);
450
+ }
451
+ #hero h1 {color: #ffffff; font-size: 2rem; font-weight: 800; margin: 0 0 6px 0; letter-spacing: -0.02em;}
452
+ #hero p {color: rgba(255, 255, 255, 0.88); margin: 0; font-size: 0.98rem;}
453
+ #hero .hero-badges {margin-top: 12px;}
454
+ #hero .badge {
455
+ display: inline-block; background: rgba(255, 255, 255, 0.16); color: #fff;
456
+ border: 1px solid rgba(255, 255, 255, 0.25); border-radius: 999px;
457
+ padding: 3px 12px; font-size: 0.78rem; margin-right: 6px;
458
+ }
459
+ #generate-btn {
460
+ background: linear-gradient(120deg, #4f46e5, #db2777) !important;
461
+ border: none !important; color: #fff !important; font-weight: 700 !important;
462
+ font-size: 1.02rem !important; height: 52px !important;
463
+ box-shadow: 0 8px 22px rgba(219, 39, 119, 0.45) !important;
464
+ }
465
+ #generate-btn:hover {filter: brightness(1.08);}
466
+ .quota-card {
467
+ background: #0f172a; border: 1px solid #1e293b; border-radius: 14px;
468
+ padding: 16px 20px; margin-top: 10px;
469
+ }
470
+ .quota-title {font-weight: 700; font-size: 1rem; margin-bottom: 10px; color: #e2e8f0;}
471
+ .quota-row {display: flex; justify-content: space-between; font-size: 0.9rem; margin: 4px 0; color: #94a3b8;}
472
+ .quota-row b {color: #e2e8f0;}
473
+ .quota-bar {height: 10px; background: #1e293b; border-radius: 999px; overflow: hidden; margin: 10px 0;}
474
+ .quota-fill {height: 100%; border-radius: 999px; transition: width .4s ease;}
475
+ .quota-note {font-size: 0.78rem; color: #64748b; margin-top: 6px; line-height: 1.5;}
476
+ .tips-card {font-size: 0.92rem; line-height: 1.7;}
477
+ footer {display: none !important;}
478
+ """
479
+
480
+ HEADER_HTML = """
481
+ <div id="hero">
482
+ <h1>πŸŽ™οΈ Offex Voice Studio</h1>
483
+ <p>Clone any voice from a short reference clip β€” high fidelity, GPU-accelerated, powered by IndexTTS-2.</p>
484
+ <div class="hero-badges">
485
+ <span class="badge">🎧 Voice cloning</span>
486
+ <span class="badge">🎭 Emotion control</span>
487
+ <span class="badge">⚑ Quota-smart</span>
488
+ </div>
489
+ </div>
490
+ """
491
+
492
+ TIPS_MD = """
493
+ **For the closest possible clone:**
494
+ 1. **Reference clip:** use 5–15 seconds of **clean speech** from one speaker only (no music, no background noise). The app auto-trims silence and normalises volume, but it cannot remove background noise.
495
+ 2. **Emotion reference:** add a second clip of the same voice speaking with the emotion you want β€” this dramatically improves expressiveness.
496
+ 3. **Keep texts short** β€” shorter texts synthesize faster and use less of your daily GPU quota.
497
+ 4. **Seed:** keep the same seed to get consistent output across retries; change it to explore variations.
498
+ """
499
+
500
+ with gr.Blocks(title="Offex Voice Studio β€” AI Voice Cloning", theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"), css=CUSTOM_CSS) as demo:
501
+ gr.HTML(HEADER_HTML)
502
 
503
  with gr.Row():
504
+ with gr.Column(scale=5):
505
+ reference_audio = gr.Audio(
506
+ label="🎧 Reference voice (5–15s of clean speech works best)",
507
+ sources=["upload", "microphone"],
508
+ type="filepath",
509
+ )
510
+ with gr.Accordion("🎭 Emotion reference (optional β€” big quality boost)", open=False):
511
+ emotion_audio = gr.Audio(
512
+ label="Emotion reference (same voice, desired emotion)",
513
+ sources=["upload", "microphone"],
514
+ type="filepath",
515
+ )
516
+ with gr.Column(scale=7):
517
+ text = gr.Textbox(
518
+ label="πŸ“ Text to speak (English)",
519
+ placeholder="Type the text you want in the cloned voice...",
520
+ lines=5,
521
+ max_lines=12,
522
+ )
523
+ permission = gr.Checkbox(
524
+ label="βœ… I have permission to use this reference voice",
525
+ value=False,
526
+ )
527
+ generate = gr.Button("✨ Generate Voice", variant="primary", size="lg", elem_id="generate-btn")
528
+ output = gr.Audio(label="πŸ”Š Generated voice", type="filepath", autoplay=True, show_download_button=True)
529
+
530
+ with gr.Accordion("βš™οΈ Advanced settings", open=False):
531
+ with gr.Row():
532
+ emotion_strength = gr.Slider(
533
+ minimum=0,
534
+ maximum=1,
535
+ value=0.6,
536
+ step=0.05,
537
+ label="Emotion strength",
538
+ )
539
+ use_text_emotion = gr.Checkbox(
540
+ label="Derive emotion from the text itself",
541
+ value=False,
542
+ )
543
+ seed = gr.Number(
544
+ label="Seed (same seed = consistent output)",
545
+ value=1234,
546
+ precision=0,
547
+ minimum=0,
548
+ )
549
 
550
+ usage_state = gr.State(0.0)
551
+ elapsed_box = gr.Number(visible=False, value=0)
552
+ quota_html = gr.HTML(elem_id="quota-panel")
553
 
554
  generate.click(
555
  fn=synthesize,
 
562
  use_text_emotion,
563
  seed,
564
  ],
565
+ outputs=[output, elapsed_box],
566
+ ).then(
567
+ fn=_update_session_usage,
568
+ inputs=[usage_state, elapsed_box],
569
+ outputs=[usage_state, quota_html],
570
  )
571
 
572
+ demo.load(fn=_refresh_quota, inputs=None, outputs=quota_html)
573
+
574
+ with gr.Accordion("πŸ’‘ Tips for the most accurate clone", open=False):
575
+ gr.Markdown(TIPS_MD)
576
+
577
+ if EXAMPLE_VOICE:
578
+ gr.Examples(
579
+ examples=[
580
+ [EXAMPLE_VOICE, "Hello there! This is my cloned voice, speaking naturally and clearly."],
581
+ [EXAMPLE_VOICE, "Welcome to Offex Voice Studio. Today is a wonderful day to create something amazing."],
582
+ ],
583
+ inputs=[reference_audio, text],
584
+ label="Try it instantly with a sample voice",
585
+ )
586
+
587
 
588
  if __name__ == "__main__":
589
+ demo.queue(max_size=8).launch()