如何在Win10系统本地部署语音转文字模型FunASR
本文介绍如何在Win10系统本地部署语音转文字模型FunASR
官方GitHub项目:https://github.com/modelscope/FunASR
1. 配置Python 3虚拟环境
我是用Anaconda来管理Python 3虚拟环境,可以参考我写的另一篇博文:Anaconda教程(持续更新ing…)
2. 安装FunASR环境
其实也不用太在乎环境依赖问题,我就是用的老版PyTorch所以没安torchaudio所以在安装torchaudio的过程中直接安了新版的PyTorch,虽然其实我的cuda版本不符合要求,但是反正cuda is_available了……
总之能跑,又不是不能跑,别管那么多。
(2025.12.12补充:很诡异,我用1.2.6版本就可以用GPU,但是升级到1.2.7版本就不行了,但是降回1.2.6版本就可以了?)
在虚拟环境中运行如下命令:
pip install funasr
pip install modelscope
modelscope包是下载模型权重用的,如果不用SDK下载或者直接用huggingface下载可以不安这个包。
如果用torchaudio的话就补上:pip install torchaudio
3. 下载模型权重
如果时间充裕的话其实每次都可以直接在脚本里下载,但是我选择提前下好,在脚本里推理的时候就直接调用。
我是因为只用中文,所以就直接下了中文模型和对应的punctuation模型。
可以直接运行如下代码:
from modelscope import snapshot_download
model_dir = snapshot_download('iic/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch',
cache_dir="pretrained_checkpoints")
model_dir = snapshot_download('iic/punc_ct-transformer_cn-en-common-vocab471067-large',
cache_dir="pretrained_checkpoints")
4. 运行语音转文字脚本
代码:
from funasr import AutoModel
from funasr.utils.postprocess_utils import rich_transcription_postprocess
model_dir = r"pretrained_checkpoints\iic\speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch"
punc_dir=r"pretrained_checkpoints\iic\punc_ct-transformer_cn-en-common-vocab471067-large"
model = AutoModel(
model=model_dir,
vad_model="fsmn-vad",
vad_kwargs={"max_single_segment_time": 30000},
device="cuda:0",
punc_model=punc_dir,
)
res = model.generate(
input=r"pretrained_checkpoints\iic\speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch\example\asr_example.wav",
cache={},
language="zn",
use_itn=True,
batch_size_s=60,
merge_vad=True,
merge_length_s=15,
)
text = rich_transcription_postprocess(res[0]["text"])
print(text)
输出text:正是因为存在绝对正义,所以我们接受现实的相对正义。但是不要因为现实的相对正义,我们就认为这个世界没有正义。因为如果当你认为这个世界没有正义。
需要注意:
- 如果不设置punc_model的话输出会没有标点符号,是每个汉字之间空一格的那种形式。
标点符号只有以下这几种选项:- <unk> - _ - , - 。 - ? - 、 res的键为key、text、timestamp
timestamp就是每个token的时间戳,一个token的时间戳是一个开始时间和结束时间的二元列表,所以timestamp整个值是一个嵌套列表。
res示例:[{'key': 'asr_example', 'text': '正是因为存在绝对正义,所以我们接受现实的相对正义。但是不要因为现实的相对正义, 我们就认为这个世界没有正义。因为如果当你认为这个世界没有正义。', 'timestamp': [[410, 650], [650, 830], [830, 990], [990, 1150], [1150, 1350], [1350, 1550], [1550, 1710], [1710, 1830], [1830, 1950], [1950, 2190], [2270, 2430], [2430, 2550], [2550, 2630], [2630, 2750], [2750, 2970], [2970, 3210], [3310, 3550], [3570, 3730], [3730, 3870], [3870, 3970], [3970, 4150], [4150, 4330], [4330, 4570], [5230, 5410], [5410, 5610], [5610, 5710], [5710, 5910], [5910, 6070], [6070, 6210], [6210, 6450], [6490, 6610], [6610, 6750], [6750, 6890], [6890, 7130], [7130, 7250], [7250, 7450], [7450, 7610], [7610, 7730], [7730, 7930], [7930, 8090], [8090, 8290], [8290, 8390], [8390, 8550], [8550, 8690], [8690, 8930], [9030, 9270], [9410, 9590], [9590, 9770], [9770, 10010], [10470, 10710], [10710, 10870], [10870, 11070], [11070, 11270], [11270, 11370], [11370, 11510], [11510, 11670], [11670, 11790], [11790, 11910], [11910, 12010], [12010, 12090], [12090, 12270], [12270, 12390], [12390, 12570], [12570, 12690], [12690, 12895]]}]model.generate()的Input参数可以是wav、mp3、aac等各种音频文件格式model.generate()如果增加output_dir参数将会在这个文件夹下生成一个1best_recog文件夹,在里面生成text, timestamp, token三个文本文件。model.generate()中还可以添加参数热词hotword,多个热词加空格隔开,或者输入文件名,文件中每个热词换行分隔1- 如果想要生成srt字幕文件,可以在
model.generate()之后加上如下代码:
输出示例:def format_time_ms(milliseconds): """将毫秒数格式化为SRT时间戳格式 (HH:MM:SS,mmm)""" seconds, ms = divmod(milliseconds, 1000) hours, remainder = divmod(seconds, 3600) minutes, seconds = divmod(remainder, 60) return f"{hours:02d}:{minutes:02d}:{seconds:02d},{ms:03d}" def save_as_srt_with_timestamp_array( res, srt_file_path, max_chars_per_line=60, max_duration_ms=4000 ): """ 将识别结果保存为SRT字幕文件 Args: res: 识别结果,包含文本和时间戳 srt_file_path: 输出SRT文件路径 max_chars_per_line: 每行最大字符数(避免一行过长) max_duration_ms: 字幕最大持续时间(毫秒),避免单句字幕过长 """ if not res or len(res) == 0: print("结果为空") return # 从结果中提取文本和时间戳 result = res[0] text = result.get("text", "").replace("\t", "").replace("\n", "") timestamps = result.get("timestamp", []) # 验证时间戳数组长度与文本长度匹配 text_units = [] # 存储文本单元(中文字符或英文单词) time_mapping = [] # 存储每个文本单元的时间戳 i = 0 timestamp_idx = 0 while i < len(text) and timestamp_idx < len(timestamps): char = text[i] # 处理英文字母(按单词分割) if char.isalpha() and char.isascii(): word_start = i while i < len(text) and text[i].isalpha() and text[i].isascii(): i += 1 word = text[word_start:i] text_units.append(word) if timestamp_idx < len(timestamps): time_mapping.append(timestamps[timestamp_idx]) timestamp_idx += 1 # 处理中文字符 elif "\u4e00" <= char <= "\u9fff": text_units.append(char) if timestamp_idx < len(timestamps): time_mapping.append(timestamps[timestamp_idx]) timestamp_idx += 1 i += 1 # 处理标点符号和空格(不单独占用时间戳) else: if text_units: # 如果前面有文本单元 pass # 中文的空格不用加。对英文的处理还没做 else: # 如果是开头的标点,创建新单元 text_units.append(char) if timestamp_idx < len(timestamps): time_mapping.append(timestamps[timestamp_idx]) # 注意:对于单独标点,我们可能不需要时间戳,但为了保持对齐,这里还是使用一个 timestamp_idx += 1 i += 1 # 合并连续的时间戳为字幕 with open(srt_file_path, "w", encoding="utf-8") as f: paragraph = 0 current_sentence = "" current_start_idx = -1 current_start_time = 0 current_end_time = 0 for idx in range(len(text_units)): unit = text_units[idx] start_time, end_time = time_mapping[idx] # 如果是第一个单元,初始化 if current_start_idx == -1: current_sentence = unit current_start_idx = idx current_start_time = start_time current_end_time = end_time else: # 检查时间是否连续(当前开始时间等于前一个结束时间) # 考虑到浮点数精度问题,允许微小的差异 is_time_continuous = abs(start_time - time_mapping[idx - 1][1]) < 1 # 检查是否超过最大字符数或最大持续时间 new_sentence = current_sentence + unit duration = end_time - current_start_time too_long = ( len(new_sentence) > max_chars_per_line or duration > max_duration_ms ) # 如果不连续或超过限制,结束当前字幕 if not is_time_continuous or too_long: # 写入当前字幕 paragraph += 1 f.write(f"{paragraph}\n") f.write( f"{format_time_ms(int(current_start_time))} --> {format_time_ms(int(current_end_time))}\n" ) f.write(f"{current_sentence}\n\n") # 开始新字幕 current_sentence = unit current_start_idx = idx current_start_time = start_time current_end_time = end_time else: # 时间连续且未超限,合并到当前字幕 current_sentence += unit current_end_time = end_time # 写入最后一句字幕 if current_sentence: paragraph += 1 f.write(f"{paragraph}\n") f.write( f"{format_time_ms(int(current_start_time))} --> {format_time_ms(int(current_end_time))}\n" ) f.write(f"{current_sentence}\n") # 生成SRT文件 srt_output_path = "111.srt" save_as_srt_with_timestamp_array(res, srt_output_path)1 00:00:00,410 --> 00:00:02,190 正是因为存在绝对正义 2 00:00:02,270 --> 00:00:03,210 所以我们接受 3 00:00:03,310 --> 00:00:03,550 现 4 00:00:03,570 --> 00:00:04,570 实的相对正义 5 00:00:05,230 --> 00:00:06,450 但是不要因为现 6 00:00:06,490 --> 00:00:08,930 实的相对正义我们就认为这个世界 7 00:00:09,030 --> 00:00:09,270 没 8 00:00:09,410 --> 00:00:10,010 有正义 9 00:00:10,470 --> 00:00:12,895 因为如果当你认为这个世界没有正义
5. 常见问题
FileNotFoundError: [WinError 2] 系统找不到指定的文件。
解决方案:安装FFmpeg,可以参考这篇博文:【最新】windows电脑FFmpeg安装教程手把手详解_windows安装ffmpeg-CSDN博客
参考资料:为什么读取本地音频报错 · Issue #2213 · modelscope/FunASR
6. 本文撰写过程中使用的参考资料
- 模型的下载 · 文档中心
- 这几个模型名到底都能填写什么? · Issue #2026 · modelscope/FunASR
- 用FunASR轻松实现音频转SRT字幕:完整脚本与解析_音频转为字幕srt-CSDN博客

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)