诸神缄默不语-个人CSDN博文目录

官方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:正是因为存在绝对正义,所以我们接受现实的相对正义。但是不要因为现实的相对正义,我们就认为这个世界没有正义。因为如果当你认为这个世界没有正义。

需要注意:

  1. 如果不设置punc_model的话输出会没有标点符号,是每个汉字之间空一格的那种形式。
    标点符号只有以下这几种选项:
        - <unk>
        - _
        - ,
        - 。
        - ?
        - 、
    
  2. 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]]}]
    
  3. model.generate()的Input参数可以是wav、mp3、aac等各种音频文件格式
  4. model.generate()如果增加output_dir参数将会在这个文件夹下生成一个1best_recog文件夹,在里面生成text, timestamp, token三个文本文件。
  5. model.generate()中还可以添加参数热词hotword,多个热词加空格隔开,或者输入文件名,文件中每个热词换行分隔1
  6. 如果想要生成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. 常见问题

  1. FileNotFoundError: [WinError 2] 系统找不到指定的文件。
    解决方案:安装FFmpeg,可以参考这篇博文:【最新】windows电脑FFmpeg安装教程手把手详解_windows安装ffmpeg-CSDN博客
    参考资料:为什么读取本地音频报错 · Issue #2213 · modelscope/FunASR

6. 本文撰写过程中使用的参考资料

  1. 模型的下载 · 文档中心
  2. 这几个模型名到底都能填写什么? · Issue #2026 · modelscope/FunASR
  3. 用FunASR轻松实现音频转SRT字幕:完整脚本与解析_音频转为字幕srt-CSDN博客

在这里插入图片描述


  1. 如何加入多个热词 · Issue #2445 ·modelscope/FunASR ↩︎

Logo

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

更多推荐