ffmpeg+Whisper:无字幕视频自动生成中文字幕完整流程
2026/8/31 7:15:15 网站建设 项目流程

平时看视频内容时,如果碰到一段没有字幕的日语视频,想要自己动手做一个“中字版”,很多人的第一反应是去网上找现成的字幕文件。但现实往往很骨感:原始视频没有外挂字幕,网上也搜不到对应翻译,真正可行的办法是自己从音频里把字幕“挖”出来。

这篇文章就把这条完整链路拆开讲清楚:音频提取、语音识别、字幕翻译、人工校对、压制合成。全文提供可直接运行的命令和 Python 脚本,适合想自己做字幕翻译、批量处理视频、或者单纯想了解字幕制作流程的读者。示例以一段日语视频为素材,最终输出一个带中文字幕的 MP4 文件。

1. 背景与核心概念

1.1 什么是“中字视频”

“中字”是中文互联网社区对“中文字幕”的简称。观众在观看日语、英语等外语视频时,如果听不懂原声,就需要依赖字幕来理解内容。中字视频通常指已经嵌入了中文字幕的视频文件,可以直接播放观看,也可以当作二次创作的素材。

中字制作并不是简单地把文本翻译出来,还需要考虑:

  • 台词出现的起始时间和结束时间;
  • 角色名、专有名词的翻译一致性;
  • 字幕在画面中的位置、大小、字体、描边效果;
  • 视频编码格式与字幕烧录方式的兼容性。

一个规范的中字视频,应该做到台词显示节奏准确、翻译通顺、风格统一,并且在不同播放器上都能正常显示。

1.2 字幕的三种存在形式

字幕按存储和显示方式,可以分成三种:

类型特点常见后缀使用场景
外挂字幕字幕是独立文件,播放时手动加载.srt、.ass视频原文件不带字幕,需要额外加载
内封字幕字幕封装进视频容器,播放器可切换常见于 .mkv同一个视频内包含多条字幕轨道
硬字幕字幕直接烧录进画面像素输出 MP4 等所有播放器都能看到,无法关闭

外挂字幕最灵活,但观众需要额外操作;内封字幕方便携带,但依赖播放器支持;硬字幕最“稳”,适合分发,也是很多中字视频最终采用的形态。本文实战部分会同时演示软字幕封装和硬字幕烧录。

1.3 中字视频的完整制作链路

把一段没有字幕的原始视频变成中字视频,核心流程如下:

原始视频 │ ▼ ffmpeg 提取音频 │ ▼ Whisper 语音识别(生成带时间轴的日文文本) │ ▼ 机器翻译为中文 │ ▼ 人工校对(检查断句、术语、时间轴) │ ▼ 生成 SRT / ASS 字幕 │ ▼ ffmpeg 压制合成 │ ▼ 中字视频

每个环节都有对应的工具和坑点。下面会按这个链路一步步完成实战。

2. 环境准备与版本说明

2.1 环境与版本

本文示例以 Ubuntu 22.04 LTS 为运行环境,Python 使用 3.10 或更高版本。Windows 和 macOS 的思路完全一致,只是安装命令略有差异,我会在关键位置补充说明。

需要安装的软件:

  • ffmpeg:用于音频提取、字幕压制;
  • Python 3:用于运行语音识别和翻译脚本;
  • pip:用于安装 Python 依赖包;
  • Aegisub:可选,用于人工校对字幕。

版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。只要 ffmpeg 版本不低于 4.x,faster-whisper 的接口在核心用法上都保持一致。

2.2 安装 ffmpeg

Ubuntu / Debian 系统可以使用 apt 安装:

sudo apt update sudo apt install -y ffmpeg

Windows 用户可以直接从 ffmpeg 官网下载编译好的二进制文件,把bin目录加入系统 PATH。macOS 用户可以使用 Homebrew:

brew install ffmpeg

安装完成后,验证版本:

ffmpeg -version

如果能看到版本号,说明安装成功。

2.3 安装 Python 依赖

本文使用 faster-whisper 做语音识别,使用 deep-translator 做机器翻译初稿。

pip install faster-whisper deep-translator

faster-whisper是 OpenAI Whisper 的加速实现,内存占用更小,解码速度更快,支持 CPU 和 GPU 推理。deep-translator是一个封装了多种翻译服务接口的 Python 库,代码里可以直接调用。

如果下载速度慢,可以临时使用国内镜像源:

pip install faster-whisper deep-translator -i https://pypi.tuna.tsinghua.edu.cn/simple

2.4 项目目录规划

建议按下面的目录结构组织文件:

subtitle_workflow/ ├── input/ │ └── shiny_source.mp4 ├── audio/ │ └── audio.wav ├── srt/ │ ├── ja_raw.srt │ ├── zh_raw.srt │ └── zh_final.srt ├── output/ │ └── shiny_zh.mp4 └── scripts/ ├── transcribe.py ├── translate_srt.py └── run_all.sh
  • input/放原始视频;
  • audio/放中间音频;
  • srt/放各阶段字幕;
  • output/放最终产物;
  • scripts/放 Python 脚本和 Shell 脚本。

这样做的目的是避免中间产物混在一起。后文所有命令都基于这个目录结构。

3. 核心工具与原理拆解

3.1 ffmpeg:音视频处理的瑞士军刀

ffmpeg 是开源社区最常用的音视频处理工具,几乎所有视频处理场景都和它有关。它的基本用法是:

ffmpeg -i 输入文件 [处理参数] 输出文件

字幕制作中用到的核心参数:

  • -vn:不处理视频流,只保留音频;
  • -acodec pcm_s16le:指定音频编码为 PCM 16 位小端格式,适合后续识别;
  • -ar 16000:设置采样率为 16kHz,这是 Whisper 类语音识别模型最常用的采样率;
  • -ac 1:合并为单声道,可以避免背景音乐和立体声带来的识别干扰。

这些参数在后面实战中会用到。理解它们的作用,才能在自己遇到问题时灵活调整。

3.2 Whisper:带时间戳的语音识别

Whisper 是 OpenAI 开源的语音识别模型,支持包括日语、中文在内的多语言识别,并且会输出每句话的时间戳。faster-whisper 是其 CTranslate2 加速版本,对 CPU 和 GPU 都有更好的性能。

faster-whisper 的核心用法:

from faster_whisper import WhisperModel model = WhisperModel("small", device="cpu", compute_type="int8") segments, info = model.transcribe("audio.wav", language="ja", vad_filter=True) for segment in segments: print(segment.start, segment.end, segment.text)

模型大小有tinybasesmallmediumlarge-v3等选择。模型越大,识别准确率越高,但推理速度和内存占用也越大。对一般视频来说,smallmedium是性价比比较高的选择。language="ja"表示指定源语言为日语,如果不确定,可以去掉language参数让模型自动检测。

vad_filter=True表示开启语音活动检测,可以过滤掉没有语音的静音片段,减少无效输出。

首次使用某个模型时会自动下载模型文件,需要保持网络正常,下载后本地会缓存,后续使用不用再次下载。

3.3 SRT 字幕格式

SRT 是最简单、兼容性最好的字幕格式。它的基本结构如下:

1 00:00:01,000 --> 00:00:04,000 こんにちは 2 00:00:05,000 --> 00:00:08,000 世界

每个字幕条目包含三部分:

  • 序号;
  • 显示时间范围,格式为时:分:秒,毫秒
  • 字幕文本内容。

条目之间用空行分隔。SRT 文件通常使用 UTF-8 编码,用记事本或代码编辑器打开时不容易乱码。

ASS 格式比 SRT 复杂,支持自定义字体、颜色、位置、滚动效果等,适合对字幕样式有更高要求的场景。本文核心流程用 SRT,最后会简单补充 ASS 的转换思路。

3.4 翻译与校对

机器翻译的结果只能看作“初稿”,不能直接发布。原因有两个:

  • 语音识别本身可能存在漏字、错字;
  • 机器翻译对口语、角色语气、专有名词的处理往往生硬。

正确做法是:先用脚本完成机器翻译,然后使用 Aegisub 等工具逐条人工校对。校对时重点关注人名、地名、语气词、断句位置。比如“桑山千雪”这类角色名,一旦定了译名,就要在全文保持统一。

4. 完整实战案例

4.1 准备输入视频

假设原始视频文件名是心ノアリカ.mp4,因为文件名包含特殊字符,命令行处理时容易出问题,建议先重命名:

mkdir -p input audio srt output scripts mv 心ノアリカ.mp4 input/shiny_source.mp4

后面的命令统一使用input/shiny_source.mp4作为输入文件。

4.2 提取音频

使用 ffmpeg 从视频中提取 16kHz 单声道 WAV 音频:

ffmpeg -y -i input/shiny_source.mp4 \ -vn \ -acodec pcm_s16le \ -ar 16000 \ -ac 1 \ audio/audio.wav

参数含义:

  • -y:覆盖同名文件;
  • -vn:丢弃视频流,只保留音频;
  • -acodec pcm_s16le:输出 PCM 16 位小端编码的 WAV;
  • -ar 16000:采样率改为 16kHz;
  • -ac 1:单声道。

执行完成后,可以查看音频信息确认:

ffprobe audio/audio.wav

ffprobe是 ffmpeg 附带的媒体信息查看工具。输出中应该能看到16000 Hzmono等信息。

4.3 语音识别生成日文 SRT

scripts/目录下创建transcribe.py

# 文件路径:scripts/transcribe.py import sys from faster_whisper import WhisperModel def format_ts(seconds: float) -> str: """将秒数转换为 SRT 时间戳格式,例如 00:00:03,500""" millis = int(round(seconds * 1000)) h, remainder = divmod(millis, 3600000) m, remainder = divmod(remainder, 60000) s, ms = divmod(remainder, 1000) return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}" def write_srt(segments, output_path: str) -> None: """将语音识别片段写入 SRT 文件""" with open(output_path, "w", encoding="utf-8") as f: for idx, segment in enumerate(segments, start=1): start_ts = format_ts(segment.start) end_ts = format_ts(segment.end) text = segment.text.strip() f.write(f"{idx}\n") f.write(f"{start_ts} --> {end_ts}\n") f.write(f"{text}\n\n") def main(): audio_path = sys.argv[1] if len(sys.argv) > 1 else "audio/audio.wav" srt_path = sys.argv[2] if len(sys.argv) > 2 else "srt/ja_raw.srt" # 模型大小可以根据机器性能调整:tiny/base/small/medium/large-v3 model = WhisperModel("small", device="cpu", compute_type="int8") segments, info = model.transcribe( audio_path, language="ja", vad_filter=True, ) print(f"识别语言: {info.language}, 概率: {info.language_probability:.2f}") write_srt(segments, srt_path) print(f"字幕已写入: {srt_path}") if __name__ == "__main__": main()

运行:

cd subtitle_workflow python scripts/transcribe.py audio/audio.wav srt/ja_raw.srt

首次运行会下载 Whisper 模型,时间取决于网络状况。运行结束后,打开srt/ja_raw.srt,应该能看到类似下面的内容:

1 00:00:01,040 --> 00:00:04,120 心の在り処、探して 2 00:00:05,300 --> 00:00:08,760 私のまま、変わっていく

这里需要说明:识别结果依赖音质、背景音乐、说话人语速。如果结果不理想,可以换用更大的模型,或者对音频提前做降噪处理。

4.4 字幕翻译为中文

scripts/目录下创建translate_srt.py

# 文件路径:scripts/translate_srt.py import re import sys import time from pathlib import Path from deep_translator import GoogleTranslator SRT_PATTERN = re.compile( r"(\d+)\n" r"(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n" r"(.*?)(?=\n\n|\Z)", re.DOTALL, ) def parse_srt(path: str): """解析 SRT 文件,返回字幕条目列表""" text = Path(path).read_text(encoding="utf-8") entries = [] for match in SRT_PATTERN.finditer(text): entries.append({ "index": int(match.group(1)), "start": match.group(2), "end": match.group(3), "content": match.group(4).strip().replace("\n", " "), }) return entries def write_srt(entries, path: str) -> None: """将翻译后的字幕写入新的 SRT 文件""" with open(path, "w", encoding="utf-8") as f: for entry in entries: f.write(f"{entry['index']}\n") f.write(f"{entry['start']} --> {entry['end']}\n") f.write(f"{entry['content']}\n\n") def safe_translate(translator, text: str, max_retries: int = 3) -> str: """翻译并处理异常,失败时保留原文""" for attempt in range(max_retries): try: return translator.translate(text) except Exception as e: print(f"翻译失败,正在重试: {text}, 错误: {e}") if attempt == max_retries - 1: return text time.sleep(2 * (attempt + 1)) return text def main(): input_srt = sys.argv[1] if len(sys.argv) > 1 else "srt/ja_raw.srt" output_srt = sys.argv[2] if len(sys.argv) > 2 else "srt/zh_raw.srt" entries = parse_srt(input_srt) translator = GoogleTranslator(source="ja", target="zh-CN") for entry in entries: entry["content"] = safe_translate(translator, entry["content"]) print(f"[{entry['index']}] {entry['content']}") write_srt(entries, output_srt) print(f"翻译完成,已写入: {output_srt}") if __name__ == "__main__": main()

运行:

python scripts/translate_srt.py srt/ja_raw.srt srt/zh_raw.srt

说明一点:deep-translator默认调用的是公共翻译接口,可能有频率限制。这里加上了重试逻辑,翻译失败时会保留原文,避免整个脚本中断。如果翻译量很大,建议申请正规翻译服务的 API Key,然后把translator替换成对应的实现,代码结构不需要变。

4.5 字幕校对与样式调整

机器翻译完成后,不要直接压制。先用 Aegisub 打开srt/zh_raw.srt逐条校对。

校对主要看这几项:

  • 时间轴是否明显错位;
  • 译文是否通顺,是否符合人物口吻;
  • 专有名词是否统一;
  • 每条字幕长度是否适合阅读。

Aegisub 是开源的字幕编辑器,支持直接编辑 SRT,并且能在视频画面上预览字幕效果。

如果希望字幕带样式,比如字体、描边、阴影,可以把 SRT 转换成 ASS,或者在压制命令中通过force_style临时指定样式。转换工具可以使用 Python 的ass相关库,也可以直接在 Aegisub 里“另存为 ASS”。

校对完成后,建议把文件另存为srt/zh_final.srt,作为最终压制使用的字幕。

4.6 压制合成中字视频

压制分为硬字幕和软字幕两种方式。

硬字幕直接烧录进画面:

ffmpeg -y -i input/shiny_source.mp4 \ -vf "subtitles=srt/zh_final.srt:force_style='FontName=Microsoft YaHei,FontSize=20,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=1,Shadow=0,MarginV=30'" \ -c:v libx264 -crf 20 -preset medium \ -c:a copy \ output/shiny_zh.mp4

参数说明:

  • subtitles=...:ffmpeg 的字幕滤镜,用于把字幕绘制到视频上;
  • FontName=Microsoft YaHei:指定中文字体,Linux 上如果没有微软雅黑,需要先安装中文字体,或者改成系统中已经存在的字体;
  • FontSize=20:字号;
  • PrimaryColour=&H00FFFFFF:字体颜色,&H00FFFFFF表示白色;
  • OutlineColour=&H00000000:描边颜色,黑色;
  • BorderStyle=1:描边样式;
  • MarginV=30:字幕距画面底部的垂直边距;
  • -c:v libx264:使用 H.264 编码;
  • -crf 20:视频质量参数,数值越小质量越高,文件越大;
  • -preset medium:编码速度与压缩率平衡;
  • -c:a copy:音频直接复制,不重新编码。

软字幕封装成 MKV,保留多条字幕轨道:

ffmpeg -y -i input/shiny_source.mp4 \ -i srt/zh_final.srt \ -c copy \ -c:s srt \ output/shiny_zh.mkv

这种方式不会修改画面,字幕作为独立轨道封装进 MKV,播放器可以切换或关闭字幕。

4.7 一键串联

如果流程已经跑通,可以把命令写成一个 Shell 脚本:

# 文件路径:scripts/run_all.sh #!/usr/bin/env bash set -euo pipefail INPUT="input/shiny_source.mp4" AUDIO="audio/audio.wav" JA_SRT="srt/ja_raw.srt" ZH_SRT="srt/zh_final.srt" OUTPUT="output/shiny_zh.mp4" echo ">>> 1. 提取音频" ffmpeg -y -i "$INPUT" -vn -acodec pcm_s16le -ar 16000 -ac 1 "$AUDIO" echo ">>> 2. 语音识别" python scripts/transcribe.py "$AUDIO" "$JA_SRT" echo ">>> 3. 机器翻译" python scripts/translate_srt.py "$JA_SRT" "$ZH_SRT" echo ">>> 4. 压制中字视频" ffmpeg -y -i "$INPUT" \ -vf "subtitles=$ZH_SRT:force_style='FontName=Microsoft YaHei,FontSize=20'" \ -c:v libx264 -crf 20 -preset medium \ -c:a copy \ "$OUTPUT" echo ">>> 完成: $OUTPUT"

需要给脚本增加执行权限:

chmod +x scripts/run_all.sh ./scripts/run_all.sh

注意:这个链路是“半自动”的,机器翻译后需要人工校对,最好把zh_final.srt的人工校对穿插在第 3 步和第 4 步之间。实际使用时不要在机器翻译后直接压制,除非你接受初稿质量。

5. 常见问题与排查思路

5.1 常见问题速查表

问题现象常见原因解决思路
ffmpeg 提示 command not foundffmpeg 未安装按系统安装 ffmpeg,Windows 检查 PATH
音频提取后文件为空输入视频没有音轨用 ffprobe 查看音轨信息
识别结果全是英文未指定语言参数设置 language="ja"
识别结果为空背景音乐干扰或 VAD 过滤过强关闭 vad_filter,或换用更大模型
SRT 中文乱码文件编码不是 UTF-8用 UTF-8 编码保存 SRT
压制时提示字体找不到系统缺少中文字体安装中文字体,或改成系统已有字体名
硬字幕中文显示为方块字体不支持中文使用中文字体,如 Noto Sans CJK、微软雅黑
翻译脚本频繁报错免费翻译接口限流增加重试间隔,或换用付费翻译 API
压制后音画不同步视频本身帧率异常先检查原始视频,必要时加 -vsync 参数
字幕时间轴整体偏移视频片头片尾裁剪过在 SRT 中整体调整时间偏移

5.2 案例一:识别结果为空

如果transcribe.py执行完,SRT 文件里没有任何字幕,首先检查音频是否正常:

ffprobe audio/audio.wav

确认文件大小不为零,时长合理。然后尝试去掉vad_filter

segments, info = model.transcribe( audio_path, language="ja", vad_filter=False, )

有些视频背景音乐明显,VAD 会把带人声的片段误判为噪声,导致识别内容被过滤。关闭 VAD 后,虽然可能会多出一些无效时间戳,但至少能看到识别结果。

5.3 案例二:中文字幕在压制后乱码

乱码一般是编码问题。SRT 文件必须保存为 UTF-8 编码。Windows 记事本默认可能是 GBK 编码,可以用 VS Code 打开后检查右下角编码是否显示 UTF-8。

另外,ffmpeg 的subtitles滤镜在读取字幕文件时,对文件路径的转义很敏感。Windows 路径中的反斜杠和盘符冒号需要额外处理,建议把字幕路径改成相对路径,并先运行一个简单命令测试:

ffmpeg -i input/shiny_source.mp4 -vf "subtitles=srt/zh_final.srt" -c:a copy output/test.mp4

如果还是不显示,就优先检查字体问题。

5.4 案例三:翻译结果太生硬

机器翻译对口语化台词、隐藏含义、文化梗的处理能力有限。比如日语中的“よろしくお願いします”直译和意译差别很大,机器容易译得僵硬。

这种情况下,人工校对是绕不开的。如果希望机器翻译质量更好,可以把待翻译文本先交给大语言模型翻译接口,再人工检查。但要注意翻译内容的隐私和合规,不要上传敏感信息。

6. 最佳实践与工程建议

6.1 文件与命名规范

字幕制作过程会产生大量中间文件,命名规范非常重要。建议遵循:

  • 原始视频、中间音频、各阶段字幕分目录存放;
  • 文件名中不要带空格,使用下划线代替;
  • 字幕文件名标注语言和阶段,例如ja_raw.srtzh_raw.srtzh_final.srt
  • 输出文件标注最终状态,例如shiny_zh.mp4

如果要做多集视频,可以再加一层集数目录,避免文件互相覆盖。

6.2 翻译质量工程化

机器翻译只能作为初稿,想提升质量,可以从几个方向入手:

  • 建立术语表,把角色名、地名、专有名词整理成统一的对照表;
  • 对台词按角色分开翻译,保持人物语气一致;
  • 识别后先清理无意义文本,比如“あの”“えっと”等语气词可以根据语境决定是否保留;
  • 校对时重点看时间轴与文本长度是否匹配,字幕太长读者会跟不上。

在脚本层面,可以把翻译结果输出为 JSON 格式,方便二次修改后再写回 SRT。

6.3 字幕视觉规范

压制硬字幕时注意:

  • 字体选择:推荐中文字体,如 Noto Sans CJK、思源黑体、微软雅黑;
  • 字号大小:1080p 视频常用 16 到 22 号,4K 视频需要适当放大;
  • 边距:字幕底部距画面边缘建议保留一定距离,不要贴边;
  • 描边:深色描边或半透明底框可以提高可读性;
  • 多行字幕:单条字幕不要超过两行,一行中文大约 16 到 20 个字为宜。

如果视频画面中已经有原生日文或说明文字,要注意中文字幕不要遮挡重要的画面信息。

6.4 版权与合规

制作中字视频时,要特别注意版权问题。本文介绍的流程适用于满足以下条件之一的场景:

  • 个人学习、研究用途;
  • 已经获得版权方授权;
  • 视频本身允许二次创作和翻译传播。

不要大规模传播未授权的中字视频,也不要用中字视频谋利。如果你为他人制作字幕,应确认对方有合法使用该素材的权利。

6.5 批量化与断点续跑

如果处理多集视频,不要简单套循环。建议在脚本中加入断点续跑机制:

  • 每完成一步,生成一个标记文件;
  • 已识别过的音频不重复识别;
  • 已翻译过的字幕不重复翻译,除非删除标记文件;
  • 每个步骤的日志分别记录,方便定位失败点。

思路如下:

if [ ! -f "srt/ja_raw.srt" ]; then python scripts/transcribe.py audio/audio.wav srt/ja_raw.srt fi if [ ! -f "srt/zh_final.srt" ]; then python scripts/translate_srt.py srt/ja_raw.srt srt/zh_final.srt fi

这样即使中途失败,重新执行脚本时不会从头开始,能节省大量时间。

7. 总结与学习路线

这篇文章从一条完整的字幕生产链路出发,完成了音频提取、语音识别、字幕翻译、人工校对和压制合成。你掌握了 ffmpeg 的基础用法、faster-whisper 的语音识别脚本、SRT 字幕格式的解析与生成,以及硬字幕和软字幕的两种压制方式。

下一步可以继续深入的方向包括:

  • ASS 字幕样式语法,掌握更精细的排版和特效;
  • 用大语言模型接口替换简单的机器翻译,建立台词角色级翻译流程;
  • 把语音识别和翻译封装成 Web 服务,做成上传视频自动出字幕的小工具;
  • 对中文识别场景做针对性调参,处理中英混排字幕;
  • 学习 ffmpeg 的滤镜链,把音频降噪、响度统一集成进流程。

最后给一个实际经验:做批量视频字幕时,先拿一段 30 秒左右的片段跑通全流程,确认模型、字体、编码都没问题,再投入完整视频处理。否则几十分钟素材跑完才发现字幕样式不对,返工成本会很高。字幕制作没有一步到位的魔法,耐心校对才是质量的关键。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询