import re import sys import gradio as gr import yaml from huggingface_hub import snapshot_download try: import spaces USING_SPACES = True except ImportError: USING_SPACES = False MODEL_ID = "ILRDF/chatterbox-formosan-all-finetune" repo_dir = snapshot_download(MODEL_ID) sys.path.insert(0, repo_dir) from formosan_tts import FormosanTTS # noqa: E402 tts = FormosanTTS(repo_dir, device="cuda") def gpu_decorator(func): if USING_SPACES: return spaces.GPU(func) else: return func with open("configs/refs.yaml", encoding="utf-8") as f: refs_config = yaml.safe_load(f) # "阿美_海岸" -> "ami-x-pswl"; ethnicity "阿美" -> ["阿美_海岸", ...] LANGUAGES = {} for ref, v in refs_config.items(): LANGUAGES.setdefault(re.sub(r"_[男女]聲[12]?$", "", ref), v["lang_code"]) ETHNICITIES = sorted({lang.split("_")[0] for lang in LANGUAGES}) def languages_of(ethnicity): return [lang for lang in LANGUAGES if lang.split("_")[0] == ethnicity] def refs_of(prefix): return [r for r in refs_config if r.startswith(prefix)] @gpu_decorator def synthesize(gen_text, lang_code, ref_audio): gen_text = gen_text.strip() if len(gen_text) == 0: raise gr.Error("請勿輸入空字串。") if not ref_audio: raise gr.Error("請先錄製或上傳參考音檔。") try: wav, sr = tts.generate(gen_text, lang_code, ref_audio) except ValueError as e: raise gr.Error(f"文字中有無法轉換的字元:{e}") return sr, wav def get_title(): with open("DEMO.md", encoding="utf-8") as tong: return tong.readline().strip("# ") CSS = """@import url(https://tauhu.tw/tauhu-oo.css); .textonly textarea {border-width: 0px !important; } """ THEME = gr.themes.Default( font=( "tauhu-oo", gr.themes.GoogleFont("Source Sans Pro"), "ui-sans-serif", "system-ui", "sans-serif", ) ) JS = """ function addButtonsEvent() { const buttons = document.querySelectorAll("#head-html-block button"); buttons.forEach(button => { button.addEventListener("click", () => { navigator.clipboard.writeText(button.innerText); }); }); } """ demo = gr.Blocks(title=get_title()) with demo: with open("DEMO.md", encoding="utf-8") as tong: gr.Markdown(tong.read()) gr.HTML( "特殊符號請複製使用(滑鼠點擊即可複製): ", padding=False, elem_id="head-html-block", ) with gr.Tab("預設配音員"): with gr.Row(): with gr.Column(): default_speaker_ethnicity = gr.Dropdown( choices=ETHNICITIES, label="步驟一:選擇族別", value="阿美", filterable=False, ) default_speaker_refs = gr.Dropdown( choices=refs_of(default_speaker_ethnicity.value), label="步驟二:選擇配音員", value=refs_of(default_speaker_ethnicity.value)[0], filterable=False, ) default_speaker_gen_text_input = gr.Textbox( label="步驟三:輸入文字", value="", ) default_speaker_generate_btn = gr.Button( "步驟四:開始合成", variant="primary" ) with gr.Column(): default_speaker_audio_output = gr.Audio( label="合成結果", buttons=["download"] ) with gr.Tab("自己當配音員"): with gr.Row(): with gr.Column(): custom_speaker_ethnicity = gr.Dropdown( choices=ETHNICITIES, label="步驟一:選擇族別與語別", value="阿美", filterable=False, ) custom_speaker_language = gr.Dropdown( choices=languages_of(custom_speaker_ethnicity.value), value=languages_of(custom_speaker_ethnicity.value)[0], filterable=False, show_label=False, ) custom_speaker_ref_text = gr.Textbox( value=refs_config[refs_of(custom_speaker_language.value)[0]]["text"], interactive=False, label="步驟二:點選🎙️錄製一段清楚的聲音(建議 3 秒以上,可念下方句子),或上傳音檔", elem_classes="textonly", ) custom_speaker_audio_input = gr.Audio( type="filepath", sources=["microphone", "upload"], waveform_options=gr.WaveformOptions( sample_rate=24000, ), label="錄製或上傳", ) custom_speaker_gen_text_input = gr.Textbox( label="步驟三:輸入合成文字", value="", ) custom_speaker_generate_btn = gr.Button( "步驟四:開始合成", variant="primary" ) with gr.Column(): custom_speaker_audio_output = gr.Audio( label="合成結果", buttons=["download"] ) default_speaker_ethnicity.change( lambda ethnicity: gr.Dropdown( choices=refs_of(ethnicity), value=refs_of(ethnicity)[0], ), inputs=[default_speaker_ethnicity], outputs=[default_speaker_refs], ) def default_speaker_tts(ref, gen_text_input): return synthesize(gen_text_input, refs_config[ref]["lang_code"], refs_config[ref]["wav"]) default_speaker_generate_btn.click( default_speaker_tts, inputs=[default_speaker_refs, default_speaker_gen_text_input], outputs=[default_speaker_audio_output], ) custom_speaker_ethnicity.change( lambda ethnicity: gr.Dropdown( choices=languages_of(ethnicity), value=languages_of(ethnicity)[0], visible=len(languages_of(ethnicity)) > 1, ), inputs=[custom_speaker_ethnicity], outputs=[custom_speaker_language], ) custom_speaker_language.change( lambda lang: gr.Textbox(value=refs_config[refs_of(lang)[0]]["text"]), inputs=[custom_speaker_language], outputs=[custom_speaker_ref_text], ) def custom_speaker_tts(language, ref_audio_input, gen_text_input): return synthesize(gen_text_input, LANGUAGES[language], ref_audio_input) custom_speaker_generate_btn.click( custom_speaker_tts, inputs=[custom_speaker_language, custom_speaker_audio_input, custom_speaker_gen_text_input], outputs=[custom_speaker_audio_output], ) demo.launch(css=CSS, theme=THEME, js=JS)