Webui #1
@@ -485,6 +485,65 @@ def load_character(name: str):
|
|||||||
return info, ref_path
|
return info, ref_path
|
||||||
|
|
||||||
|
|
||||||
|
def list_character_voices(name: str) -> dict[str, str]:
|
||||||
|
"""label -> reference audio path, for every voice available for a
|
||||||
|
character: the base voice, every approved emotion_library entry, and
|
||||||
|
every pending (not yet approved) emotion-cache candidate. Used both by
|
||||||
|
the clone-as-character tool below and could back a similar picker
|
||||||
|
anywhere else a character's variants need listing."""
|
||||||
|
cards = read_character_cards()
|
||||||
|
card = cards.get(name)
|
||||||
|
if not card or "_error" in card:
|
||||||
|
return {}
|
||||||
|
voices = {}
|
||||||
|
ref_audio = card.get("reference_audio")
|
||||||
|
if ref_audio:
|
||||||
|
voices["Base (natural voice)"] = str(SERVICE_STATION_DIR / ref_audio)
|
||||||
|
for emo, entry in (card.get("emotion_library") or {}).items():
|
||||||
|
ref = entry.get("reference_audio")
|
||||||
|
if ref:
|
||||||
|
voices[f"{emo} (approved)"] = str(SERVICE_STATION_DIR / ref)
|
||||||
|
cache_dir = card["_dir"] / "emotion-cache"
|
||||||
|
if cache_dir.is_dir():
|
||||||
|
for p in sorted(cache_dir.glob("*.wav")):
|
||||||
|
voices[f"{p.stem} (cached, not approved)"] = str(p)
|
||||||
|
return voices
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_voice_choices(name: str):
|
||||||
|
voices = list_character_voices(name)
|
||||||
|
labels = list(voices.keys())
|
||||||
|
return gr.update(choices=labels, value=labels[0] if labels else None)
|
||||||
|
|
||||||
|
|
||||||
|
def clone_as_character(character_name: str, voice_label: str, text: str, language: str):
|
||||||
|
if not character_name:
|
||||||
|
raise gr.Error("Pick a character first.")
|
||||||
|
if not voice_label:
|
||||||
|
raise gr.Error("Pick a voice (base or one of its emotions).")
|
||||||
|
if not text or not text.strip():
|
||||||
|
raise gr.Error("Enter some text for them to say.")
|
||||||
|
|
||||||
|
voices = list_character_voices(character_name)
|
||||||
|
reference = voices.get(voice_label)
|
||||||
|
if not reference:
|
||||||
|
raise gr.Error(f"Could not resolve '{voice_label}' for {character_name} - try Refresh.")
|
||||||
|
|
||||||
|
_free_comfyui_memory()
|
||||||
|
env = None
|
||||||
|
if TTS_LD_LIBRARY_PATH:
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["LD_LIBRARY_PATH"] = TTS_LD_LIBRARY_PATH
|
||||||
|
out_path = WORKDIR / f"clone_{int(time.time())}.wav"
|
||||||
|
_run(
|
||||||
|
[str(TTS_PYTHON), str(TTS_GENERATE), "--text", text.strip(),
|
||||||
|
"--reference", reference, "--language", language, "--output", str(out_path)],
|
||||||
|
cwd=TTS_DIR, env=env,
|
||||||
|
)
|
||||||
|
transcript = _whisper_transcribe(str(out_path))
|
||||||
|
return str(out_path), transcript
|
||||||
|
|
||||||
|
|
||||||
def create_character(
|
def create_character(
|
||||||
name: str, role: str, prose: str, language: str,
|
name: str, role: str, prose: str, language: str,
|
||||||
current_ref: str, history: list,
|
current_ref: str, history: list,
|
||||||
@@ -775,6 +834,20 @@ with gr.Blocks(title="Audio-demo pipeline studio") as demo:
|
|||||||
char_info = gr.Markdown()
|
char_info = gr.Markdown()
|
||||||
char_audio = gr.Audio(label="Reference audio", type="filepath", interactive=False)
|
char_audio = gr.Audio(label="Reference audio", type="filepath", interactive=False)
|
||||||
|
|
||||||
|
gr.Markdown(
|
||||||
|
"## Clone text as this character\n"
|
||||||
|
"Say anything in the selected character's voice - the base voice, an "
|
||||||
|
"approved emotion, or a pending (not yet approved) candidate from the "
|
||||||
|
"cache. Useful both for \"how would they say this?\" and for testing an "
|
||||||
|
"emotion candidate with real dialogue instead of the fixed test line below."
|
||||||
|
)
|
||||||
|
clone_voice_dd = gr.Dropdown(label="Voice", choices=list(list_character_voices(None).keys()))
|
||||||
|
clone_text_tb = gr.Textbox(label="Text to speak", lines=3, placeholder="Type anything.")
|
||||||
|
clone_language_dd = gr.Dropdown(LANGUAGES, value="English", label="Language")
|
||||||
|
clone_btn = gr.Button("Clone", variant="primary")
|
||||||
|
clone_audio = gr.Audio(label="Cloned speech", type="filepath", interactive=False)
|
||||||
|
clone_transcript_box = gr.Textbox(label="Whisper transcript")
|
||||||
|
|
||||||
gr.Markdown("## Generate a new emotion candidate")
|
gr.Markdown("## Generate a new emotion candidate")
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
emotion_char_dd = gr.Dropdown(label="Character", choices=character_names())
|
emotion_char_dd = gr.Dropdown(label="Character", choices=character_names())
|
||||||
@@ -793,10 +866,25 @@ with gr.Blocks(title="Audio-demo pipeline studio") as demo:
|
|||||||
outputs=[char_dd, emotion_char_dd],
|
outputs=[char_dd, emotion_char_dd],
|
||||||
)
|
)
|
||||||
char_dd.change(load_character, inputs=[char_dd], outputs=[char_info, char_audio])
|
char_dd.change(load_character, inputs=[char_dd], outputs=[char_info, char_audio])
|
||||||
|
char_dd.change(refresh_voice_choices, inputs=[char_dd], outputs=[clone_voice_dd])
|
||||||
|
clone_btn.click(
|
||||||
|
clone_as_character,
|
||||||
|
inputs=[char_dd, clone_voice_dd, clone_text_tb, clone_language_dd],
|
||||||
|
outputs=[clone_audio, clone_transcript_box],
|
||||||
|
)
|
||||||
gen_emotion_btn.click(
|
gen_emotion_btn.click(
|
||||||
generate_emotion_candidate,
|
generate_emotion_candidate,
|
||||||
inputs=[emotion_char_dd, emotion_word_tb, emotion_model_dd],
|
inputs=[emotion_char_dd, emotion_word_tb, emotion_model_dd],
|
||||||
outputs=[emotion_ref_audio, emotion_preview_audio, emotion_status],
|
outputs=[emotion_ref_audio, emotion_preview_audio, emotion_status],
|
||||||
|
).then(
|
||||||
|
# So a freshly-generated candidate is immediately pickable in
|
||||||
|
# "Clone text as this character" without reselecting the
|
||||||
|
# character to force a refresh. Keyed off char_dd (the
|
||||||
|
# browser above), not emotion_char_dd - the clone tool
|
||||||
|
# always uses whichever character char_dd is showing, and
|
||||||
|
# those two dropdowns can legitimately point at different
|
||||||
|
# characters at once.
|
||||||
|
refresh_voice_choices, inputs=[char_dd], outputs=[clone_voice_dd],
|
||||||
)
|
)
|
||||||
# Dropdown choices are otherwise fixed at server-start time, so a
|
# Dropdown choices are otherwise fixed at server-start time, so a
|
||||||
# character created earlier in the same running session (or by
|
# character created earlier in the same running session (or by
|
||||||
|
|||||||
Reference in new issue
Block a user