编程 零成本在本地跑 Whisper:从视频自动生成双语字幕

2026-06-08 15:48:58 +0800 CST views 12

零成本在本地跑 Whisper:从视频自动生成双语字幕

很多视频没有字幕,你可能需要花钱用云服务,或者手动一个字一个字敲。

其实完全可以在自己的电脑上跑——一块 RTX 5060 Ti 16G 就够了。Faster-Whisper 比 OpenAI 原版快 2-4 倍,DeepSeek API 翻译全程不到一块钱,数据还不出门。

这篇文章拆解 whisper_v3 项目的核心代码,看看视频是怎么变成带时间戳的 SRT 字幕的。


整体架构:两个模块,各司其职

整个项目就两个核心模块:

  • VideoSubtitleGenerator:负责"听写",视频 → 音频 → 语音识别 → SRT 字幕
  • SubtitleCorrector:负责"翻译",把英文字幕变成中文(可选)
视频文件
  ↓ FFmpeg 提取音频
16kHz 单声道音频
  ↓ Faster-Whisper 语音识别
带时间戳的语音片段
  ↓ LCS 去重合并
SRT 字幕文件(英文)
  ↓ LLM 批量翻译(可选)
SRT 字幕文件(中文)

一、环境准备

1.1 FFmpeg:音视频瑞士军刀

Windows 安装三选一:

# 方法一:winget(最省心)
winget install Gyan.FFmpeg

# 方法二:chocolatey
choco install ffmpeg

# 方法三:手动下载,解压后把 bin 目录加到 PATH

验证安装:ffmpeg -version,输出版本号即成功。

1.2 下载 Whisper 模型

本项目用的是 Faster-Whisper(CTranslate2 格式),不是原生 PyTorch 模型,别下错了。

显存推荐模型下载地址
16Glarge-v3(效果最好)huggingface.co/Systran/faster-whisper-large-v3
8Gmedium 或 smallhuggingface.co/Systran/faster-whisper-medium
无独显basehuggingface.co/Systran/faster-whisper-base

解压后文件夹路径记下来,待会儿填进 config.py

1.3 Python 依赖

pip install faster-whisper pysrt openai
  • faster-whisper:语音识别引擎,比原版快 2-4 倍
  • pysrt:读写 SRT 字幕文件,不用自己正则硬拆
  • openai:对接 DeepSeek API 或本地 LLM 翻译

1.4 翻译配置(二选一,非必须)

方式一:DeepSeek API(推荐)

DeepSeek 官网 注册获取 API Key(新用户送额度),填入 config.py

DEEPSEEK_API_KEY = "sk-xxxxxxxxxxxx"
LOCAL_LLM_ENABLED = False

方式二:本地 LLM(完全离线)

LM Studio 加载模型(如 Qwen2-7B-Instruct),开启本地 API 服务(默认 http://127.0.0.1:1234/v1),然后:

LOCAL_LLM_ENABLED = True
LOCAL_LLM_API_BASE = "http://127.0.0.1:1234/v1"
LOCAL_LLM_MODEL_NAME = "qwen2-7b-instruct"

本地 LLM 的缺点是慢——生成字幕 10 分钟,翻译可能花 1 小时。

1.5 配置文件 core/config.py

# 模型路径(解压后的文件夹,不是 .bin 文件本身)
MODEL_PATH = r"D:\work\models\faster-whisper-large-v3"

# 有 NVIDIA 显卡写 "cuda",否则 "cpu"
DEVICE = "cuda"

# DeepSeek API 方式
DEEPSEEK_API_KEY = "sk-xxxxxxxxxxxx"
LOCAL_LLM_ENABLED = False

二、视频字幕生成:VideoSubtitleGenerator

核心就一个类,几个方法各司其职。

2.1 整体流程

def generate_subtitle(self, video_path, output_srt=None, language=None):
    # 1. 音频提取
    audio_path = self.extract_audio(video_path)
    # 2. Whisper 语音识别,返回带时间戳的片段
    result = self.transcribe_with_timestamp(audio_path, language)
    # 3. 合并重复字幕
    result["segments"] = self.merge_duplicate_subtitles(result["segments"])
    # 4. 生成 SRT 文件
    self.create_srt(result["segments"], output_srt)

四步:剥壳 → 听写 → 纠错 → 抄正。

2.2 音频提取:extract_audio()

第一步必须先检查视频有没有音轨——很多人踩的第一个坑就是对着没声音的视频识别半天。

def extract_audio(self, video_path: str, output_audio: str = None) -> str:
    # 用 ffprobe 检查音频轨道
    cmd_check = [
        "ffprobe", "-v", "error", "-select_streams", "a",
        "-show_entries", "stream=codec_type",
        "-of", "default=noprint_wrappers=1:nokey=1", video_path
    ]
    result = subprocess.run(cmd_check, capture_output=True, text=True)
    audio_streams = result.stdout.strip()
    if not audio_streams:
        raise ValueError(f"文件 '{os.path.basename(video_path)}' 中没有音频流")
    
    # 用 ffmpeg 提取音频
    cmd = [
        "ffmpeg", "-i", video_path,
        "-vn",              # 不要视频流
        "-acodec", "pcm_s16le",  # PCM 16bit
        "-ar", "16000",     # 16kHz 采样率(Whisper 训练格式)
        "-ac", "1",         # 单声道
        "-y", output_audio
    ]
    subprocess.run(cmd, ...)

关键参数解释:

  • -ar 16000:Whisper 训练用的是 16kHz,不是 44.1kHz。格式不匹配准确率会下降,就像让学英语的人去考法语四级。
  • -ac 1:单声道,Whisper 不需要立体声。

2.3 语音识别:transcribe_with_timestamp()

用 Faster-Whisper,半精度计算,显存占用减半、速度翻倍。

def _load_model(self):
    from faster_whisper import WhisperModel
    self.pipe = WhisperModel(
        self.model_path,
        device=self.device,
        compute_type="float16" if self.device == "cuda" else "float32"
    )

def transcribe_with_timestamp(self, audio_path: str, language=None):
    self._load_model()
    segments, info = self.pipe.transcribe(
        audio_path,
        language=language or config.WHISPER_LANGUAGE or "en",
        word_timestamps=True,
        vad_filter=True,  # 语音活动检测,过滤静音和背景音乐
        vad_parameters=dict(
            min_silence_duration_ms=500,  # 静音超过 500ms 算断句
            speech_pad_ms=200,            # 每段前后加 200ms 缓冲
            min_speech_duration_ms=100
        )
    )

VAD(Voice Activity Detection) 是关键:

  • 没开 VAD:Whisper 会把咳嗽声识别成"嗯哼",背景猫叫也不放过
  • 开了 VAD:只录人声,相当于给视频装了"只录取人声"的过滤器

speech_pad_ms=200 是给每句话加个安全气囊,防止语音被截断。

2.4 字幕去重合并:merge_duplicate_subtitles()

Whisper 有时会重复识别同一句话,比如:

[0.0-1.0] 今天我们来聊聊
[1.0-2.0] 我们来聊聊 Python
[2.0-3.0] Python 装饰器

合并算法基于最长公共子序列(LCS):

def merge_duplicate_subtitles(self, segments, similarity_threshold=0.85):
    merged = []
    i = 0
    while i < len(segments):
        current = segments[i]
        merged_text = current["text"]
        merged_start = current["start"]
        merged_end = current["end"]

        # 往后看,能合并就合并
        while i + 1 < len(segments):
            next_seg = segments[i + 1]
            is_identical = merged_text == next_seg["text"]
            similarity = self._calculate_similarity(merged_text, next_seg["text"])

            if is_identical or similarity >= similarity_threshold:
                merged_end = max(merged_end, next_seg["end"])
                i += 1
                continue
            break

        merged.append({"start": merged_start, "end": merged_end, "text": merged_text})
        i += 1
    return merged

相似度 = LCS长度 / 较长字符串长度,超过 85% 就合并。

2.5 SRT 文件生成:create_srt()

最后一步,用 pysrt 生成标准 SRT 格式。

def create_srt(self, segments, output_srt):
    subs = pysrt.SubRipFile()
    for i, segment in enumerate(segments, 1):
        start, end = segment["start"], segment["end"]

        # 防御:end <= start 时强行给 0.5 秒
        if end <= start:
            end = start + 0.5

        # 防御:单条超过 30 秒的直接丢弃(99% 是识别错误)
        if end - start > 30:
            continue

        sub = pysrt.SubRipItem()
        sub.index = i
        sub.start = timedelta(seconds=start)
        sub.end = timedelta(seconds=end)
        sub.text = segment["text"]
        subs.append(sub)

    subs.save(output_srt, encoding='utf-8')

两个防御机制:

  • end <= start:SRT 格式要求结束时间必须大于开始时间,播放器遇到无效时间戳直接罢工
  • end - start > 30:超过 30 秒一句话,要么是识别错误,要么是唐僧转世

三、字幕翻译:SubtitleCorrector

如果你只需要英文字幕,这部分可以跳过。需要中文的话,继续。

3.1 双后端初始化

def __init__(self, api_key=None, api_base=None, use_local_llm=None):
    if self.use_local_llm:
        # LM Studio 不验证 key,填个假的就行
        self.client = OpenAI(api_key="dummy", base_url=config.LOCAL_LLM_API_BASE)
    else:
        self.client = OpenAI(api_key=api_key, base_url=api_base)

3.2 批量翻译:translate_srt_file()

def translate_srt_file(self, input_srt, output_srt, batch_size=200):
    subs = pysrt.open(input_srt, encoding='utf-8')
    effective_batch_size = config.LOCAL_LLM_BATCH_SIZE if self.use_local_llm else batch_size

    translated_subs = pysrt.SubRipFile()
    for i in range(0, len(subs), effective_batch_size):
        batch = subs[i:i + effective_batch_size]
        try:
            translated_batch = self._translate_batch(batch)
            translated_subs.extend(translated_batch)
        except Exception as e:
            # 翻译失败保留原文,不中断流程
            translated_subs.extend(batch)

核心思想:分批翻译 + 失败保底。翻译失败直接保留英文,不让整个流程崩掉。

3.3 批量翻译核心:_translate_batch()

这是整个翻译模块的灵魂——怎么让 LLM 乖乖按顺序翻译、不偷懒?

def _translate_batch(self, batch):
    texts = [sub.text for sub in batch]
    combined = "\n".join([f"[{i+1}] {text}" for i, text in enumerate(texts)])

    prompt = f"""请将以下{len(batch)}条字幕翻译成中文。
要求:
1. 严格按顺序翻译,每条字幕对应一行翻译结果
2. 必须输出恰好{len(batch)}行,一行不多一行不少
3. 只返回翻译结果,不要任何解释

待翻译:
{combined}

请输出恰好{len(batch)}行翻译:"""

    response = self.client.chat.completions.create(
        model=self.model_name,
        messages=[
            {"role": "system", "content": "你是专业字幕翻译..."},
            {"role": "user", "content": prompt}
        ],
        temperature=0.3,
        max_tokens=8000,
        timeout=30
    )

    # 验证行数——LLM 经常自作聪明合并句子或漏行
    translated_lines = response.choices[0].message.content.split('\n')
    if len(translated_lines) != len(texts):
        # 重试,降低温度让它当复读机
        retry_response = self.client.chat.completions.create(
            ..., temperature=0.1  # 几乎等于复读
        )

行数验证是这段代码的灵魂。LLM 经常自作聪明合并相似句子,发现行数不对立刻用 temperature=0.1 重试——这个温度下 LLM 基本就是复读机,不敢发挥。


四、技术亮点总结

特性作用
VAD 语音活动检测过滤背景音,只识别真实人声
float16 半精度显存减半,速度翻倍
LCS 去重合并解决 Whisper 重复识别问题
时间戳安全修复防止 SRT 播放器崩溃
翻译失败保底宁可保留原文,不丢字幕
行数验证 + 重试防止 LLM 偷工减料

五、和云服务对比

对比项本文方案付费云服务
费用几乎为零(DeepSeek API 每百万字几毛钱)每次几元到几十元
数据隐私完全本地,数据不出门视频上传到第三方
速度取决于显卡(5060 Ti 够用)依赖网速和服务器排队
定制化可调整参数、可接本地 LLM只能调基础参数
适用场景定期处理大量视频偶尔用一次

全程跑在你的 5060 Ti 上,还避免了"你的教学视频出现在别人训练集里"的社死事件。


项目参考:whisper_v3 源码
依赖项目:OpenAI Whisper · Faster-Whisper · PySRT

推荐文章

Hypothesis是一个强大的Python测试库
2024-11-19 04:31:30 +0800 CST
Requests库详细介绍
2024-11-18 05:53:37 +0800 CST
php客服服务管理系统
2024-11-19 06:48:35 +0800 CST
如何在Vue3中定义一个组件?
2024-11-17 04:15:09 +0800 CST
Vue3中怎样处理组件引用?
2024-11-18 23:17:15 +0800 CST
jQuery `$.extend()` 用法总结
2024-11-19 02:12:45 +0800 CST
一个简单的打字机效果的实现
2024-11-19 04:47:27 +0800 CST
Vue3中如何进行错误处理?
2024-11-18 05:17:47 +0800 CST
php strpos查找字符串性能对比
2024-11-19 08:15:16 +0800 CST
程序员茄子在线接单