Reduce post-synthesis UI flicker by removing 1s status timer.

Refresh status every 60s only, shorten synth log, update log before audio, and isolate repaint regions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-12 17:46:03 +08:00
parent 038e00fbcf
commit 97c11e08e0
+39 -20
View File
@@ -6,6 +6,7 @@ Gradio Web 中控:音色锁定 → Whisper 识别 → Gemma4 润色 → ChatTT
from __future__ import annotations from __future__ import annotations
import logging import logging
import re
import shutil import shutil
import sys import sys
import uuid import uuid
@@ -127,16 +128,27 @@ def ui_check_ollama() -> str:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 模块 4ChatTTS 音频合成 # 模块 4ChatTTS 音频合成
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def ui_synthesize(polished_text: str, voice_label: str) -> tuple[str | None, str]: def _short_synth_log(msg: str, ok: bool) -> str:
"""【TTS 合成】生成最终 wav 配音文件""" """合成日志简短显示,避免长路径触发大面积重绘闪屏"""
if not ok:
return f"{msg}"
chars = re.search(r"朗读\s*(\d+)\s*字", msg)
segs = re.search(r"\s*(\d+)\s*段", msg)
if chars:
seg_note = f"{segs.group(1)} 段拼接" if segs else ""
return f"✅ 配音完成({chars.group(1)}{seg_note})。请用下方播放器试听、下载。"
return "✅ 配音完成。请用下方播放器试听、下载。"
def ui_synthesize(polished_text: str, voice_label: str) -> tuple[str, str | None]:
"""【TTS 合成】生成最终 wav 配音文件。先更新日志再更新播放器,减轻闪屏。"""
if not polished_text or not polished_text.strip(): if not polished_text or not polished_text.strip():
return None, "请先完成 Gemma4 润色。" return "请先完成 Gemma4 润色。", None
voice_id = label_to_voice_id(voice_label) voice_id = label_to_voice_id(voice_label)
ok, msg, wav_path = generate_voice(polished_text, voice_id=voice_id) ok, msg, wav_path = generate_voice(polished_text, voice_id=voice_id)
if ok and wav_path: return _short_synth_log(msg, ok), wav_path if ok else None
return wav_path, f"{msg}"
return None, f"{msg}"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -863,10 +875,25 @@ gradio-app,
.status-err { border-left-color: #ef4444 !important; background: #2a1515 !important; } .status-err { border-left-color: #ef4444 !important; background: #2a1515 !important; }
.status-err .status-body { color: #fecaca !important; } .status-err .status-body { color: #fecaca !important; }
/* 状态栏与播放器隔离重绘,减轻合成完成后的闪屏 */
.status-row {
contain: layout style paint;
min-height: 88px;
}
.gradio-container .audio-container, .gradio-container .audio-container,
.gradio-container .upload-container { .gradio-container .upload-container {
border: 2px dashed #4b5563 !important; border: 2px dashed #4b5563 !important;
background: #1a2332 !important; background: #1a2332 !important;
contain: layout style paint;
min-height: 100px;
}
.gradio-container .audio-container audio,
.gradio-container .audio-container canvas,
.gradio-container .waveform-container {
background: #1a2332 !important;
}
.gradio-container .pipeline-step-card textarea {
contain: layout style;
} }
footer { visibility: hidden; } footer { visibility: hidden; }
@@ -918,11 +945,8 @@ def ui_check_ollama_html(force: bool = False) -> str:
def ui_initial_load() -> tuple[str, str]: def ui_initial_load() -> tuple[str, str]:
"""首屏立即返回,不发起网络请求,避免平板白屏等待""" """首屏加载:检测 Ollama + 音色(仅一次,不用高频 Timer 避免闪屏)"""
return ( return ui_refresh_status_html(force=False)
_status_html("Ollama 节点", "后台检测中,请稍候…", "warn"),
ui_speaker_status_html(),
)
def ui_refresh_status_html(force: bool = False) -> tuple[str, str]: def ui_refresh_status_html(force: bool = False) -> tuple[str, str]:
@@ -998,19 +1022,13 @@ def build_app() -> gr.Blocks:
outputs=[ollama_status, speaker_status], outputs=[ollama_status, speaker_status],
) )
# 首屏秒开:仅本地检测音色,Ollama 延后到 Timer
demo.load( demo.load(
fn=ui_initial_load, fn=ui_initial_load,
outputs=[ollama_status, speaker_status], outputs=[ollama_status, speaker_status],
) )
# 1 秒后后台检测 Ollama;之后每 30s 刷新(30s 内走缓存 # 仅低频刷新状态(去掉 1s Timer,避免合成后整页闪屏
status_timer = gr.Timer(value=1, active=True) status_timer_slow = gr.Timer(value=60, active=True)
status_timer.tick(
fn=lambda: ui_refresh_status_html(force=False),
outputs=[ollama_status, speaker_status],
)
status_timer_slow = gr.Timer(value=30, active=True)
status_timer_slow.tick( status_timer_slow.tick(
fn=lambda: ui_refresh_status_html(force=True), fn=lambda: ui_refresh_status_html(force=True),
outputs=[ollama_status, speaker_status], outputs=[ollama_status, speaker_status],
@@ -1100,7 +1118,8 @@ def build_app() -> gr.Blocks:
synth_btn.click( synth_btn.click(
ui_synthesize, ui_synthesize,
[polished_text, tts_voice], [polished_text, tts_voice],
[output_audio, synth_log], [synth_log, output_audio],
show_progress="hidden",
) )
# ---- Tab 3: 一键生产 ---- # ---- Tab 3: 一键生产 ----