Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -128,27 +128,42 @@ def ms_to_srt_time(ms):
|
|
| 128 |
return f"{hours:02d}:{minutes:02d}:{seconds:02d},{milliseconds:03d}"
|
| 129 |
|
| 130 |
|
| 131 |
-
def generate_srt(sentence_info
|
| 132 |
if not sentence_info:
|
| 133 |
return ""
|
| 134 |
srt_lines = []
|
| 135 |
for i, seg in enumerate(sentence_info, 1):
|
| 136 |
start = ms_to_srt_time(seg.get("start", 0))
|
| 137 |
end = ms_to_srt_time(seg.get("end", 0))
|
| 138 |
-
text = seg.get("text", "")
|
| 139 |
-
if clean:
|
| 140 |
-
text = re.sub(r"<\|[^>]*\|?>", "", text)
|
| 141 |
-
text = text.strip()
|
| 142 |
if text:
|
| 143 |
srt_lines.append(f"{i}\n{start} --> {end}\n{text}\n")
|
| 144 |
return "\n".join(srt_lines).strip()
|
| 145 |
|
| 146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
def model_inference(
|
| 148 |
audio_input, language, output_format, use_itn, merge_vad, merge_length, ban_emo_unk
|
| 149 |
):
|
| 150 |
if audio_input is None:
|
| 151 |
-
return "错误:请上传或录制音频。"
|
| 152 |
fs, input_wav = audio_input
|
| 153 |
if input_wav.dtype in [np.int16, np.int32]:
|
| 154 |
input_wav = input_wav.astype(np.float32) / np.iinfo(input_wav.dtype).max
|
|
@@ -159,6 +174,7 @@ def model_inference(
|
|
| 159 |
if fs != 16000:
|
| 160 |
resampler = torchaudio.transforms.Resample(orig_freq=fs, new_freq=16000)
|
| 161 |
input_wav = resampler(torch.from_numpy(input_wav).to(torch.float32)).numpy()
|
|
|
|
| 162 |
res = model.generate(
|
| 163 |
input=input_wav,
|
| 164 |
cache={},
|
|
@@ -172,25 +188,21 @@ def model_inference(
|
|
| 172 |
)
|
| 173 |
raw_text = res[0].get("text", "")
|
| 174 |
if not raw_text:
|
| 175 |
-
return "未能识别出文本。"
|
| 176 |
|
| 177 |
sentence_info = res[0].get("sentence_info", [])
|
|
|
|
| 178 |
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
)
|
| 190 |
-
elif output_format == "MASTER_DATA":
|
| 191 |
-
srt_raw = generate_srt(sentence_info, clean=False)
|
| 192 |
-
return srt_raw if srt_raw else raw_text
|
| 193 |
-
return raw_text
|
| 194 |
|
| 195 |
|
| 196 |
html_intro = """<div style="text-align: center; font-family: var(--font-sans);"><h1 style="font-size: 28px;">SenseVoice-Small 语音识别模型</h1><p>SenseVoice 是具有音频理解能力的音频基础模型,包括语音识别(ASR)、语种识别(LID)、语音情感识别(SER)和声学事件分类(AEC)或声学事件检测(AED)。</p><p style="font-size: small; color: #888;">支持 MP3, WAV, FLAC, M4A 等常见音频格式。</p></div>"""
|
|
@@ -231,6 +243,8 @@ lang_options = [
|
|
| 231 |
|
| 232 |
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
|
| 233 |
gr.HTML(html_intro)
|
|
|
|
|
|
|
| 234 |
with gr.Row():
|
| 235 |
with gr.Column(scale=2):
|
| 236 |
audio_inputs = gr.Audio(label="上传/录制音频")
|
|
@@ -258,6 +272,8 @@ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
|
|
| 258 |
fn_button = gr.Button("开始识别", variant="primary")
|
| 259 |
with gr.Column(scale=3):
|
| 260 |
text_outputs = gr.Textbox(label="识别结果", lines=25, show_copy_button=True)
|
|
|
|
|
|
|
| 261 |
fn_button.click(
|
| 262 |
fn=model_inference,
|
| 263 |
inputs=[
|
|
@@ -269,8 +285,15 @@ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
|
|
| 269 |
merge_length_slider,
|
| 270 |
ban_emo_unk_checkbox,
|
| 271 |
],
|
| 272 |
-
outputs=text_outputs,
|
| 273 |
api_name="model_inference",
|
| 274 |
)
|
| 275 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
| 128 |
return f"{hours:02d}:{minutes:02d}:{seconds:02d},{milliseconds:03d}"
|
| 129 |
|
| 130 |
|
| 131 |
+
def generate_srt(sentence_info):
|
| 132 |
if not sentence_info:
|
| 133 |
return ""
|
| 134 |
srt_lines = []
|
| 135 |
for i, seg in enumerate(sentence_info, 1):
|
| 136 |
start = ms_to_srt_time(seg.get("start", 0))
|
| 137 |
end = ms_to_srt_time(seg.get("end", 0))
|
| 138 |
+
text = re.sub(r"<\|[^>]*\|?>", "", seg.get("text", "")).strip()
|
|
|
|
|
|
|
|
|
|
| 139 |
if text:
|
| 140 |
srt_lines.append(f"{i}\n{start} --> {end}\n{text}\n")
|
| 141 |
return "\n".join(srt_lines).strip()
|
| 142 |
|
| 143 |
|
| 144 |
+
def apply_format(raw_text, sentence_info, output_format):
|
| 145 |
+
if output_format == "纯净文本":
|
| 146 |
+
return re.sub(r"<\|[^>]*\|?>", "", raw_text).strip()
|
| 147 |
+
elif output_format == "原始富文本":
|
| 148 |
+
return raw_text
|
| 149 |
+
elif output_format == "Emoji 格式":
|
| 150 |
+
return format_to_emoji(raw_text)
|
| 151 |
+
elif output_format == "SRT 字幕":
|
| 152 |
+
srt_result = generate_srt(sentence_info)
|
| 153 |
+
return (
|
| 154 |
+
srt_result if srt_result else re.sub(r"<\|[^>]*\|?>", "", raw_text).strip()
|
| 155 |
+
)
|
| 156 |
+
elif output_format == "ALL_IN_ONE":
|
| 157 |
+
srt_text = generate_srt(sentence_info)
|
| 158 |
+
return f"{raw_text}\n===SRT_DELIMITER===\n{srt_text}"
|
| 159 |
+
return raw_text
|
| 160 |
+
|
| 161 |
+
|
| 162 |
def model_inference(
|
| 163 |
audio_input, language, output_format, use_itn, merge_vad, merge_length, ban_emo_unk
|
| 164 |
):
|
| 165 |
if audio_input is None:
|
| 166 |
+
return "错误:请上传或录制音频。", None
|
| 167 |
fs, input_wav = audio_input
|
| 168 |
if input_wav.dtype in [np.int16, np.int32]:
|
| 169 |
input_wav = input_wav.astype(np.float32) / np.iinfo(input_wav.dtype).max
|
|
|
|
| 174 |
if fs != 16000:
|
| 175 |
resampler = torchaudio.transforms.Resample(orig_freq=fs, new_freq=16000)
|
| 176 |
input_wav = resampler(torch.from_numpy(input_wav).to(torch.float32)).numpy()
|
| 177 |
+
|
| 178 |
res = model.generate(
|
| 179 |
input=input_wav,
|
| 180 |
cache={},
|
|
|
|
| 188 |
)
|
| 189 |
raw_text = res[0].get("text", "")
|
| 190 |
if not raw_text:
|
| 191 |
+
return "未能识别出文本。", None
|
| 192 |
|
| 193 |
sentence_info = res[0].get("sentence_info", [])
|
| 194 |
+
cache_state = {"raw_text": raw_text, "sentence_info": sentence_info}
|
| 195 |
|
| 196 |
+
result_text = apply_format(raw_text, sentence_info, output_format)
|
| 197 |
+
return result_text, cache_state
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def on_format_change(output_format, cache_state):
|
| 201 |
+
if not cache_state or "raw_text" not in cache_state:
|
| 202 |
+
return gr.update()
|
| 203 |
+
return apply_format(
|
| 204 |
+
cache_state["raw_text"], cache_state.get("sentence_info", []), output_format
|
| 205 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
|
| 208 |
html_intro = """<div style="text-align: center; font-family: var(--font-sans);"><h1 style="font-size: 28px;">SenseVoice-Small 语音识别模型</h1><p>SenseVoice 是具有音频理解能力的音频基础模型,包括语音识别(ASR)、语种识别(LID)、语音情感识别(SER)和声学事件分类(AEC)或声学事件检测(AED)。</p><p style="font-size: small; color: #888;">支持 MP3, WAV, FLAC, M4A 等常见音频格式。</p></div>"""
|
|
|
|
| 243 |
|
| 244 |
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
|
| 245 |
gr.HTML(html_intro)
|
| 246 |
+
cached_data = gr.State(value=None)
|
| 247 |
+
|
| 248 |
with gr.Row():
|
| 249 |
with gr.Column(scale=2):
|
| 250 |
audio_inputs = gr.Audio(label="上传/录制音频")
|
|
|
|
| 272 |
fn_button = gr.Button("开始识别", variant="primary")
|
| 273 |
with gr.Column(scale=3):
|
| 274 |
text_outputs = gr.Textbox(label="识别结果", lines=25, show_copy_button=True)
|
| 275 |
+
|
| 276 |
+
# 点击识别跑 GPU 模型,并将识别结果缓存到 cached_data
|
| 277 |
fn_button.click(
|
| 278 |
fn=model_inference,
|
| 279 |
inputs=[
|
|
|
|
| 285 |
merge_length_slider,
|
| 286 |
ban_emo_unk_checkbox,
|
| 287 |
],
|
| 288 |
+
outputs=[text_outputs, cached_data],
|
| 289 |
api_name="model_inference",
|
| 290 |
)
|
| 291 |
|
| 292 |
+
# 识别完成后直接切下拉框,0.001 秒即时切换格式(不重新跑 GPU)
|
| 293 |
+
output_format_dropdown.change(
|
| 294 |
+
fn=on_format_change,
|
| 295 |
+
inputs=[output_format_dropdown, cached_data],
|
| 296 |
+
outputs=text_outputs,
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|