fdbw commited on
Commit
d8b34c3
·
verified ·
1 Parent(s): 3f750cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -41
app.py CHANGED
@@ -2,6 +2,7 @@
2
  import sys
3
  import asyncio
4
  import re
 
5
  import torch
6
  import soundfile as sf
7
  import librosa
@@ -20,7 +21,7 @@
20
  print(f"⚙️ 正在初始化极速传译引擎 (设备: {DEVICE})")
21
 
22
  # ---------------------------------------------------------------------------
23
- # 2. 全局预加载模型 (支持 中/英/法/日/俄)
24
  # ---------------------------------------------------------------------------
25
  print("📦 [1/2] 正在加载 SenseVoiceSmall 多语言语音识别模型...")
26
  asr_model = AutoModel(
@@ -45,7 +46,7 @@
45
  print("🎉 所有模型加载完成!")
46
 
47
  # ---------------------------------------------------------------------------
48
- # 3. 5大语种映射配置 (包含 ASR 代码、LLM 名称及 TTS 高清发音人)
49
  # ---------------------------------------------------------------------------
50
  LANG_CONFIG = {
51
  "中文": {"asr": "zh", "name": "Chinese", "tag": "中", "voice": "zh-CN-XiaoxiaoNeural"},
@@ -56,30 +57,49 @@
56
  }
57
 
58
  # ---------------------------------------------------------------------------
59
- # 4. 核心处理逻辑
60
  # ---------------------------------------------------------------------------
61
- async def generate_tts(text, voice, output_path="output.mp3"):
62
- """使用 Edge-TTS 生成发音"""
 
 
 
 
63
  try:
64
- communicate = edge_tts.Communicate(text, voice)
65
- await communicate.save(output_path)
66
- return output_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  except Exception as e:
68
- print(f"TTS 生成失败: {e}")
69
  return None
70
 
 
 
 
71
  def on_audio_change(audio_path, src_lang_choice):
72
- """
73
- 步骤 3:停止录音后自动触发
74
- 作用:识别音频并立刻在界面输出【源语言文字】
75
- """
76
  if audio_path is None or not os.path.exists(str(audio_path)) or os.path.getsize(str(audio_path)) == 0:
77
  return ""
78
 
79
  src_cfg = LANG_CONFIG[src_lang_choice]
80
 
81
  try:
82
- # 极速语音识别
83
  res = asr_model.generate(
84
  input=audio_path,
85
  language=src_cfg["asr"],
@@ -87,18 +107,14 @@ def on_audio_change(audio_path, src_lang_choice):
87
  batch_size_s=60
88
  )
89
  text = res[0]["text"].strip()
90
- # 清理 SenseVoice 富文本标签 (如 <|NEUTRAL|>)
91
  text = re.sub(r"<\|.*?\|>", "", text).strip()
92
  return text
93
  except Exception as e:
94
- print(f"ASR 识别错误: {e}")
95
  return "⚠️ 识别失败,请重新录制。"
96
 
97
  def on_translate_click(src_text, src_lang_choice, tgt_lang_choice, history):
98
- """
99
- 步骤 4:点击【翻译】按钮触发
100
- 作用:将源语言文本翻译成目的语,并生成声音播放器
101
- """
102
  history = history or []
103
 
104
  if not src_text or src_text.startswith("⚠️"):
@@ -108,7 +124,7 @@ def on_translate_click(src_text, src_lang_choice, tgt_lang_choice, history):
108
  tgt_cfg = LANG_CONFIG[tgt_lang_choice]
109
 
110
  try:
111
- # 如果源语言与目标语言相同,跳过翻译
112
  if src_lang_choice == tgt_lang_choice:
113
  trans_text = src_text
114
  else:
@@ -138,27 +154,25 @@ def on_translate_click(src_text, src_lang_choice, tgt_lang_choice, history):
138
  input_len = inputs["input_ids"].shape[1]
139
  trans_text = mt_tokenizer.decode(outputs[0][input_len:], skip_special_tokens=True).strip()
140
 
141
- # 生成目标语言音频
142
- audio_output_path = "tmp_reply.mp3"
143
- audio_res = asyncio.run(generate_tts(trans_text, tgt_cfg["voice"], audio_output_path))
144
 
145
- # 更新历史记录
146
  history.append({"role": "user", "content": f"[{src_cfg['tag']}] {src_text}"})
147
  history.append({"role": "assistant", "content": f"[{tgt_cfg['tag']}] {trans_text}"})
148
 
149
- return history, audio_res
150
 
151
  except Exception as e:
152
  import traceback
153
  traceback.print_exc()
154
- history.append({"role": "assistant", "content": f"⚠️ 翻译出错: {str(e)}"})
155
  return history, None
156
 
157
  # ---------------------------------------------------------------------------
158
- # 5. Gradio 界面设计 (标准印刷字体 + 三步工作流)
159
  # ---------------------------------------------------------------------------
160
  PRINT_STYLE_CSS = """
161
- /* 强制使用标准印刷字体体系 (宋体/衬线印刷体) */
162
  body, input, button, textarea, span, div, p, label {
163
  font-family: "Georgia", "Times New Roman", "Songti SC", "SimSun", "STSong", serif !important;
164
  }
@@ -170,10 +184,10 @@ def on_translate_click(src_text, src_lang_choice, tgt_lang_choice, history):
170
  with gr.Blocks(title="极速多语种同传", css=PRINT_STYLE_CSS, theme=gr.themes.Soft()) as demo:
171
  gr.Markdown(
172
  "## 🎙️ 多语种极速传译系统\n"
173
- "支持 **中·英·法·日·俄** 5 大语种自由互译,采用标准印刷体界面。"
174
  )
175
 
176
- # 1. 选方向(两行单选)
177
  with gr.Group():
178
  src_radio = gr.Radio(
179
  choices=["中文", "英文", "法语", "日语", "俄语"],
@@ -186,26 +200,27 @@ def on_translate_click(src_text, src_lang_choice, tgt_lang_choice, history):
186
  label="2- 选择目的语 (译为什么)"
187
  )
188
 
189
- # 2. 录音与识别区
190
  with gr.Row():
191
  with gr.Column(scale=1):
192
  audio_in = gr.Audio(
193
  sources=["microphone"],
194
  type="filepath",
195
- label="2 & 3- 点击录音 / 停止录音 (显示声波)",
196
  waveform_options={"waveform_progress_color": "#FF5500"}
197
  )
198
  src_text_box = gr.Textbox(
199
- label="源语言文字 (停止录音后自动显示)",
200
  interactive=False,
201
- placeholder="录音结束后在此处自动生成识别文本..."
202
  )
203
  translate_btn = gr.Button("4- 🚀 翻译", variant="primary", size="lg")
204
 
205
  with gr.Column(scale=1):
206
  audio_out = gr.Audio(
207
- label="目的语声音 (可重放声波)",
208
- autoplay=True
 
209
  )
210
  chatbot = gr.Chatbot(
211
  label="💬 传译历史记录",
@@ -214,16 +229,16 @@ def on_translate_click(src_text, src_lang_choice, tgt_lang_choice, history):
214
  )
215
 
216
  # ---------------------------------------------------------------------------
217
- # 事件绑定 (完美流转:录音结束 -> 出识别字 -> 点翻译 -> 出目的字和声波)
218
  # ---------------------------------------------------------------------------
219
- # 录音停止/改变时:自动识别源语言文字
220
- audio_in.change(
221
  fn=on_audio_change,
222
  inputs=[audio_in, src_radio],
223
  outputs=[src_text_box]
224
  )
225
 
226
- # 点击翻译按钮:翻译文本并生成声音
227
  translate_btn.click(
228
  fn=on_translate_click,
229
  inputs=[src_text_box, src_radio, tgt_radio, chatbot],
 
2
  import sys
3
  import asyncio
4
  import re
5
+ import uuid
6
  import torch
7
  import soundfile as sf
8
  import librosa
 
21
  print(f"⚙️ 正在初始化极速传译引擎 (设备: {DEVICE})")
22
 
23
  # ---------------------------------------------------------------------------
24
+ # 2. 全局预加载模型
25
  # ---------------------------------------------------------------------------
26
  print("📦 [1/2] 正在加载 SenseVoiceSmall 多语言语音识别模型...")
27
  asr_model = AutoModel(
 
46
  print("🎉 所有模型加载完成!")
47
 
48
  # ---------------------------------------------------------------------------
49
+ # 3. 5大语种映射配置 (发音人采用兼容性极佳的微软 Neural 音色)
50
  # ---------------------------------------------------------------------------
51
  LANG_CONFIG = {
52
  "中文": {"asr": "zh", "name": "Chinese", "tag": "中", "voice": "zh-CN-XiaoxiaoNeural"},
 
57
  }
58
 
59
  # ---------------------------------------------------------------------------
60
+ # 4. 辅助函数:安全异步运行 TTS
61
  # ---------------------------------------------------------------------------
62
+ async def _async_tts(text, voice, filepath):
63
+ communicate = edge_tts.Communicate(text, voice)
64
+ await communicate.save(filepath)
65
+
66
+ def generate_tts_sync(text, voice):
67
+ """生成带随机 UUID 的唯一音频文件,防止缓存和文件占用冲突"""
68
  try:
69
+ filename = f"tts_{uuid.uuid4().hex[:8]}.mp3"
70
+ # 安全处理事件循环
71
+ try:
72
+ loop = asyncio.get_event_loop()
73
+ except RuntimeError:
74
+ loop = asyncio.new_event_loop()
75
+ asyncio.set_event_loop(loop)
76
+
77
+ if loop.is_running():
78
+ # 如果在现有的 async 循环中运行
79
+ import nest_asyncio
80
+ nest_asyncio.apply()
81
+ loop.run_until_complete(_async_tts(text, voice, filename))
82
+ else:
83
+ loop.run_until_complete(_async_tts(text, voice, filename))
84
+
85
+ if os.path.exists(filename) and os.path.getsize(filename) > 0:
86
+ return filename
87
+ return None
88
  except Exception as e:
89
+ print(f"❌ TTS 合成失败: {e}", file=sys.stderr)
90
  return None
91
 
92
+ # ---------------------------------------------------------------------------
93
+ # 5. 核心业务逻辑
94
+ # ---------------------------------------------------------------------------
95
  def on_audio_change(audio_path, src_lang_choice):
96
+ """步骤 3:停止录音后识别源语言"""
 
 
 
97
  if audio_path is None or not os.path.exists(str(audio_path)) or os.path.getsize(str(audio_path)) == 0:
98
  return ""
99
 
100
  src_cfg = LANG_CONFIG[src_lang_choice]
101
 
102
  try:
 
103
  res = asr_model.generate(
104
  input=audio_path,
105
  language=src_cfg["asr"],
 
107
  batch_size_s=60
108
  )
109
  text = res[0]["text"].strip()
 
110
  text = re.sub(r"<\|.*?\|>", "", text).strip()
111
  return text
112
  except Exception as e:
113
+ print(f"ASR 错误: {e}")
114
  return "⚠️ 识别失败,请重新录制。"
115
 
116
  def on_translate_click(src_text, src_lang_choice, tgt_lang_choice, history):
117
+ """步骤 4:点击翻译,生成目的语文字和音频"""
 
 
 
118
  history = history or []
119
 
120
  if not src_text or src_text.startswith("⚠️"):
 
124
  tgt_cfg = LANG_CONFIG[tgt_lang_choice]
125
 
126
  try:
127
+ # 1. 文本翻译
128
  if src_lang_choice == tgt_lang_choice:
129
  trans_text = src_text
130
  else:
 
154
  input_len = inputs["input_ids"].shape[1]
155
  trans_text = mt_tokenizer.decode(outputs[0][input_len:], skip_special_tokens=True).strip()
156
 
157
+ # 2. 语音合成 (生成唯一的 MP3 文件)
158
+ audio_file = generate_tts_sync(trans_text, tgt_cfg["voice"])
 
159
 
160
+ # 3. 更新历史对话记录
161
  history.append({"role": "user", "content": f"[{src_cfg['tag']}] {src_text}"})
162
  history.append({"role": "assistant", "content": f"[{tgt_cfg['tag']}] {trans_text}"})
163
 
164
+ return history, audio_file
165
 
166
  except Exception as e:
167
  import traceback
168
  traceback.print_exc()
169
+ history.append({"role": "assistant", "content": f"⚠️ 翻译失败: {str(e)}"})
170
  return history, None
171
 
172
  # ---------------------------------------------------------------------------
173
+ # 6. Gradio 界面设计 (标准印刷字体 + 交互修复)
174
  # ---------------------------------------------------------------------------
175
  PRINT_STYLE_CSS = """
 
176
  body, input, button, textarea, span, div, p, label {
177
  font-family: "Georgia", "Times New Roman", "Songti SC", "SimSun", "STSong", serif !important;
178
  }
 
184
  with gr.Blocks(title="极速多语种同传", css=PRINT_STYLE_CSS, theme=gr.themes.Soft()) as demo:
185
  gr.Markdown(
186
  "## 🎙️ 多语种极速传译系统\n"
187
+ "支持 **中·英·法·日·俄** 5 大语种自由互译。"
188
  )
189
 
190
+ # 1. 选方向
191
  with gr.Group():
192
  src_radio = gr.Radio(
193
  choices=["中文", "英文", "法语", "日语", "俄语"],
 
200
  label="2- 选择目的语 (译为什么)"
201
  )
202
 
203
+ # 2. 工作区
204
  with gr.Row():
205
  with gr.Column(scale=1):
206
  audio_in = gr.Audio(
207
  sources=["microphone"],
208
  type="filepath",
209
+ label="2 & 3- 点击录音 / 停止录音",
210
  waveform_options={"waveform_progress_color": "#FF5500"}
211
  )
212
  src_text_box = gr.Textbox(
213
+ label="源语言文字 (自动生成)",
214
  interactive=False,
215
+ placeholder="录音结束后在此处显示识别文本..."
216
  )
217
  translate_btn = gr.Button("4- 🚀 翻译", variant="primary", size="lg")
218
 
219
  with gr.Column(scale=1):
220
  audio_out = gr.Audio(
221
+ label="目的语声音 (可播放声波)",
222
+ autoplay=True,
223
+ type="filepath"
224
  )
225
  chatbot = gr.Chatbot(
226
  label="💬 传译历史记录",
 
229
  )
230
 
231
  # ---------------------------------------------------------------------------
232
+ # 事件流转
233
  # ---------------------------------------------------------------------------
234
+ # 录音结束自动识别
235
+ audio_in.stop_recording(
236
  fn=on_audio_change,
237
  inputs=[audio_in, src_radio],
238
  outputs=[src_text_box]
239
  )
240
 
241
+ # 点击翻译按钮,生成声音并播放
242
  translate_btn.click(
243
  fn=on_translate_click,
244
  inputs=[src_text_box, src_radio, tgt_radio, chatbot],