Files
VoiceStudio/ui.py
T

207 lines
8.2 KiB
Python

import gradio as gr
from gradio_client import Client, handle_file
import tempfile
import os
from omnivoice.utils.lang_map import LANG_NAMES, lang_display_name
_ALL_LANGUAGES = ["Auto"] + sorted(lang_display_name(n) for n in LANG_NAMES)
_CATEGORIES = {
"Gender / 性别": ["Male / 男", "Female / 女"],
"Age / 年龄": [
"Child / 儿童",
"Teenager / 少年",
"Young Adult / 青年",
"Middle-aged / 中年",
"Elderly / 老年",
],
"Pitch / 音调": [
"Very Low Pitch / 极低音调",
"Low Pitch / 低音调",
"Moderate Pitch / 中音调",
"High Pitch / 高音调",
"Very High Pitch / 极高音调",
],
"Style / 风格": ["Whisper / 耳语"],
"English Accent / 英文口音": [
"American Accent / 美式口音",
"Australian Accent / 澳大利亚口音",
"British Accent / 英国口音",
"Chinese Accent / 中国口音",
"Canadian Accent / 加拿大口音",
"Indian Accent / 印度口音",
"Korean Accent / 韩国口音",
"Portuguese Accent / 葡萄牙口音",
"Russian Accent / 俄罗斯口音",
"Japanese Accent / 日本口音",
],
"Chinese Dialect / 中文方言": [
"Henan Dialect / 河南话",
"Shaanxi Dialect / 陕西话",
"Sichuan Dialect / 四川话",
"Guizhou Dialect / 贵州话",
"Yunnan Dialect / 云南话",
"Guilin Dialect / 桂林话",
"Jinan Dialect / 济南话",
"Shijiazhuang Dialect / 石家庄话",
"Gansu Dialect / 甘肃话",
"Ningxia Dialect / 宁夏话",
"Qingdao Dialect / 青岛话",
"Northeast Dialect / 东北话",
],
}
_ATTR_INFO = {
"English Accent / 英文口音": "Only effective for English speech.",
"Chinese Dialect / 中文方言": "Only effective for Chinese speech.",
}
client = Client("k2-fsa/OmniVoice")
def _lang_dropdown(label="Language (optional) / 语种 (可选)", value="Auto"):
return gr.Dropdown(
label=label,
choices=_ALL_LANGUAGES,
value=value,
allow_custom_value=False,
interactive=True,
)
def _gen_settings():
with gr.Accordion("Generation Settings (optional)", open=False):
sp = gr.Slider(0.5, 1.5, value=1.0, step=0.05, label="Speed")
du = gr.Number(value=0, label="Duration (seconds)") # Used 0 instead of None to match number input requirements, handle inside logic if needed
ns = gr.Slider(4, 64, value=32, step=1, label="Inference Steps")
dn = gr.Checkbox(label="Denoise", value=True)
gs = gr.Slider(0.0, 4.0, value=2.0, step=0.1, label="Guidance Scale (CFG)")
pp = gr.Checkbox(label="Preprocess Prompt", value=True)
po = gr.Checkbox(label="Postprocess Output", value=True)
return ns, gs, dn, sp, du, pp, po
theme = gr.themes.Soft(font=["Inter", "Arial", "sans-serif"])
css = """
.gradio-container {max-width: 100% !important; font-size: 16px !important;}
.gradio-container h1 {font-size: 1.5em !important;}
.gradio-container .prose {font-size: 1.1em !important;}
.compact-audio audio {height: 60px !important;}
.compact-audio .waveform {min-height: 80px !important;}
"""
with gr.Blocks(theme=theme, css=css, title="OmniVoice Client UI") as demo:
gr.Markdown(
"""
# OmniVoice Client UI
This UI connects remotely to the `k2-fsa/OmniVoice` space via API. It supports Voice Clone and Voice Design.
"""
)
with gr.Tabs():
# ================= Voice Clone Tab =================
with gr.TabItem("Voice Clone"):
with gr.Row():
with gr.Column(scale=1):
vc_text = gr.Textbox(label="Text to Synthesize / 待合成文本", lines=4)
vc_ref_audio = gr.Audio(label="Reference Audio / 参考音频", type="filepath", elem_classes="compact-audio")
vc_ref_text = gr.Textbox(label="Reference Text (optional) / 参考音频文本(可选)", lines=2)
vc_lang = _lang_dropdown()
with gr.Accordion("Instruct (optional)", open=False):
vc_instruct = gr.Textbox(label="Instruct", lines=2)
vc_ns, vc_gs, vc_dn, vc_sp, vc_du, vc_pp, vc_po = _gen_settings()
vc_btn = gr.Button("Generate / 生成", variant="primary")
with gr.Column(scale=1):
vc_audio = gr.Audio(label="Output Audio / 合成结果", type="filepath")
vc_status = gr.Textbox(label="Status / 状态", lines=2)
def clone_wrapper(text, lang, ref_aud, ref_text, instruct, ns, gs, dn, sp, du, pp, po):
if not ref_aud:
return None, "Error: Must provide reference audio for Voice Clone."
try:
result = client.predict(
text=text or "",
lang=lang or "Auto",
ref_aud=handle_file(ref_aud),
ref_text=ref_text or "",
instruct=instruct or "",
ns=ns,
gs=gs,
dn=dn,
sp=sp,
du=du or 0,
pp=pp,
po=po,
api_name="/_clone_fn"
)
return result[0], result[1]
except Exception as e:
return None, f"Error: {e}"
vc_btn.click(
clone_wrapper,
inputs=[vc_text, vc_lang, vc_ref_audio, vc_ref_text, vc_instruct, vc_ns, vc_gs, vc_dn, vc_sp, vc_du, vc_pp, vc_po],
outputs=[vc_audio, vc_status]
)
# ================= Voice Design Tab =================
with gr.TabItem("Voice Design"):
with gr.Row():
with gr.Column(scale=1):
vd_text = gr.Textbox(label="Text to Synthesize / 待合成文本", lines=4)
vd_lang = _lang_dropdown()
vd_groups = []
for _cat, _choices in _CATEGORIES.items():
vd_groups.append(
gr.Dropdown(
label=_cat,
choices=["Auto"] + _choices,
value="Auto",
info=_ATTR_INFO.get(_cat),
)
)
vd_ns, vd_gs, vd_dn, vd_sp, vd_du, vd_pp, vd_po = _gen_settings()
vd_btn = gr.Button("Generate / 生成", variant="primary")
with gr.Column(scale=1):
vd_audio = gr.Audio(label="Output Audio / 合成结果", type="filepath")
vd_status = gr.Textbox(label="Status / 状态", lines=2)
def design_wrapper(text, lang, ns, gs, dn, sp, du, pp, po, *groups):
try:
# groups mapped to param_9 ... param_14
result = client.predict(
text=text or "",
lang=lang or "Auto",
ns=ns,
gs=gs,
dn=dn,
sp=sp,
du=du or 0,
pp=pp,
po=po,
param_9=groups[0],
param_10=groups[1],
param_11=groups[2],
param_12=groups[3],
param_13=groups[4],
param_14=groups[5],
api_name="/_design_fn"
)
return result[0], result[1]
except Exception as e:
return None, f"Error: {e}"
vd_btn.click(
design_wrapper,
inputs=[vd_text, vd_lang, vd_ns, vd_gs, vd_dn, vd_sp, vd_du, vd_pp, vd_po] + vd_groups,
outputs=[vd_audio, vd_status]
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)