Spaces:
Running on Zero
Running on Zero
Add permanent saved voices + mobile-friendly premium UI
Browse filesAdds two cleaned, normalised 20s reference voices to assets/ so no upload is needed. Quick-pick buttons and one-tap examples use them; the reference is preloaded by default. Shorter labels and CSS media queries fix cramped mobile rendering.
- .gitattributes +2 -0
- app.py +36 -24
- assets/voice_1.wav +3 -0
- assets/voice_2.wav +3 -0
.gitattributes
CHANGED
|
@@ -43,3 +43,5 @@ training_data/19535.png filter=lfs diff=lfs merge=lfs -text
|
|
| 43 |
training_data/19536.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
training_data/19540.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
training_data/19542.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 43 |
training_data/19536.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
training_data/19540.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
training_data/19542.png filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
assets/voice_1.wav filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
assets/voice_2.wav filter=lfs diff=lfs merge=lfs -text
|
app.py
CHANGED
|
@@ -111,18 +111,13 @@ def _patch_numpy_compat() -> None:
|
|
| 111 |
np.bool8 = np.bool_
|
| 112 |
|
| 113 |
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 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,9 +143,6 @@ 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 = [
|
| 156 |
model_dir / "config.yaml",
|
|
@@ -475,6 +467,16 @@ CUSTOM_CSS = """
|
|
| 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 = """
|
|
@@ -503,25 +505,33 @@ with gr.Blocks(title="Offex Voice Studio β AI Voice Cloning", theme=gr.themes.
|
|
| 503 |
with gr.Row():
|
| 504 |
with gr.Column(scale=5):
|
| 505 |
reference_audio = gr.Audio(
|
| 506 |
-
label="
|
| 507 |
sources=["upload", "microphone"],
|
| 508 |
type="filepath",
|
|
|
|
| 509 |
)
|
| 510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
emotion_audio = gr.Audio(
|
| 512 |
-
label="Emotion reference
|
| 513 |
sources=["upload", "microphone"],
|
| 514 |
type="filepath",
|
| 515 |
)
|
| 516 |
with gr.Column(scale=7):
|
| 517 |
text = gr.Textbox(
|
| 518 |
-
label="
|
| 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
|
| 525 |
value=False,
|
| 526 |
)
|
| 527 |
generate = gr.Button("β¨ Generate Voice", variant="primary", size="lg", elem_id="generate-btn")
|
|
@@ -574,14 +584,16 @@ with gr.Blocks(title="Offex Voice Studio β AI Voice Cloning", theme=gr.themes.
|
|
| 574 |
with gr.Accordion("π‘ Tips for the most accurate clone", open=False):
|
| 575 |
gr.Markdown(TIPS_MD)
|
| 576 |
|
| 577 |
-
|
|
|
|
|
|
|
| 578 |
gr.Examples(
|
| 579 |
examples=[
|
| 580 |
-
[
|
| 581 |
-
[
|
| 582 |
],
|
| 583 |
inputs=[reference_audio, text],
|
| 584 |
-
label="
|
| 585 |
)
|
| 586 |
|
| 587 |
|
|
|
|
| 111 |
np.bool8 = np.bool_
|
| 112 |
|
| 113 |
|
| 114 |
+
ASSETS_DIR = Path(__file__).resolve().parent / "assets"
|
| 115 |
+
MY_VOICES = [
|
| 116 |
+
("ποΈ My Voice 1", ASSETS_DIR / "voice_1.wav"),
|
| 117 |
+
("ποΈ My Voice 2", ASSETS_DIR / "voice_2.wav"),
|
| 118 |
+
]
|
| 119 |
+
MY_VOICES = [(label, str(path)) for label, path in MY_VOICES if path.is_file()]
|
| 120 |
+
DEFAULT_VOICE = MY_VOICES[0][1] if MY_VOICES else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
|
| 123 |
def _warm_up() -> None:
|
|
|
|
| 143 |
_warmup_thread = threading.Thread(target=_warm_up, daemon=True)
|
| 144 |
_warmup_thread.start()
|
| 145 |
|
|
|
|
|
|
|
|
|
|
| 146 |
def _find_config(model_dir: Path) -> str:
|
| 147 |
candidates = [
|
| 148 |
model_dir / "config.yaml",
|
|
|
|
| 467 |
.quota-note {font-size: 0.78rem; color: #64748b; margin-top: 6px; line-height: 1.5;}
|
| 468 |
.tips-card {font-size: 0.92rem; line-height: 1.7;}
|
| 469 |
footer {display: none !important;}
|
| 470 |
+
#foot {text-align: center; color: #64748b; margin-top: 20px; font-size: 0.85rem;}
|
| 471 |
+
#foot span {opacity: .75;}
|
| 472 |
+
@media (max-width: 768px) {
|
| 473 |
+
#hero {padding: 18px 16px; border-radius: 14px;}
|
| 474 |
+
#hero h1 {font-size: 1.35rem;}
|
| 475 |
+
#hero p {font-size: 0.85rem;}
|
| 476 |
+
.gradio-container {padding: 8px !important;}
|
| 477 |
+
.quota-row {flex-direction: column; gap: 1px;}
|
| 478 |
+
#generate-btn {height: 46px !important; font-size: 0.95rem !important;}
|
| 479 |
+
}
|
| 480 |
"""
|
| 481 |
|
| 482 |
HEADER_HTML = """
|
|
|
|
| 505 |
with gr.Row():
|
| 506 |
with gr.Column(scale=5):
|
| 507 |
reference_audio = gr.Audio(
|
| 508 |
+
label="Reference voice",
|
| 509 |
sources=["upload", "microphone"],
|
| 510 |
type="filepath",
|
| 511 |
+
value=DEFAULT_VOICE,
|
| 512 |
)
|
| 513 |
+
if MY_VOICES:
|
| 514 |
+
gr.Markdown("**β‘ Quick-pick β no upload needed:**")
|
| 515 |
+
with gr.Row():
|
| 516 |
+
for label, path in MY_VOICES:
|
| 517 |
+
gr.Button(label, size="sm").click(
|
| 518 |
+
fn=lambda p=path: p, outputs=reference_audio
|
| 519 |
+
)
|
| 520 |
+
with gr.Accordion("π Emotion reference (optional β big boost)", open=False):
|
| 521 |
emotion_audio = gr.Audio(
|
| 522 |
+
label="Emotion reference",
|
| 523 |
sources=["upload", "microphone"],
|
| 524 |
type="filepath",
|
| 525 |
)
|
| 526 |
with gr.Column(scale=7):
|
| 527 |
text = gr.Textbox(
|
| 528 |
+
label="Text to speak",
|
| 529 |
placeholder="Type the text you want in the cloned voice...",
|
| 530 |
lines=5,
|
| 531 |
max_lines=12,
|
| 532 |
)
|
| 533 |
permission = gr.Checkbox(
|
| 534 |
+
label="β
I have permission to use this voice",
|
| 535 |
value=False,
|
| 536 |
)
|
| 537 |
generate = gr.Button("β¨ Generate Voice", variant="primary", size="lg", elem_id="generate-btn")
|
|
|
|
| 584 |
with gr.Accordion("π‘ Tips for the most accurate clone", open=False):
|
| 585 |
gr.Markdown(TIPS_MD)
|
| 586 |
|
| 587 |
+
gr.HTML("<div id='foot'>Built with <span>β€οΈ</span> in India Β· Powered by IndexTTS-2 on ZeroGPU</div>")
|
| 588 |
+
|
| 589 |
+
if MY_VOICES:
|
| 590 |
gr.Examples(
|
| 591 |
examples=[
|
| 592 |
+
[MY_VOICES[0][1], "Hello there! This is my cloned voice, speaking naturally and clearly."],
|
| 593 |
+
[MY_VOICES[-1][1], "Welcome to Offex Voice Studio. Today is a wonderful day to create something amazing."],
|
| 594 |
],
|
| 595 |
inputs=[reference_audio, text],
|
| 596 |
+
label="One-tap examples with your saved voices",
|
| 597 |
)
|
| 598 |
|
| 599 |
|
assets/voice_1.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:539b877c780d3c13645f8dad702269a94125121cc23819cc291dbe5daa720767
|
| 3 |
+
size 1764044
|
assets/voice_2.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:acb7378e322eb12d8836281972e6f0c9ff1ea8ef1aee9d8dc2e670f8622f2c0d
|
| 3 |
+
size 1764044
|