字节笔记本
2026年7月20日
Python + Whisper + FFmpeg:视频字幕生成与烧录实战
Python + Whisper + FFmpeg:视频字幕生成与烧录实战
用 OpenAI Whisper 识别视频语音生成 SRT 字幕,再用 FFmpeg 将字幕烧录到视频中,支持自定义字体大小、位置和颜色。
环境准备
pip install openai-whisper pysrt
# FFmpeg 需要单独安装
# macOS
brew install ffmpeg
# Ubuntu
apt install ffmpeg字幕生成:Whisper 语音识别
基础版:生成 SRT 文件
import whisper
import pysrt
def seconds_to_srt_time(seconds):
"""将秒数转换为 SRT 时间格式 HH:MM:SS,mmm"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
millis = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
def generate_subtitles(video_path, output_path, model_size="base"):
model = whisper.load_model(model_size)
result = model.transcribe(video_path, language="zh")
subs = pysrt.SubRipFile()
for segment in result["segments"]:
item = pysrt.SubRipItem()
item.start = pysrt.SubRipTime(seconds=segment["start"])
item.end = pysrt.SubRipTime(seconds=segment["end"])
item.text = segment["text"].strip()
subs.append(item)
subs.save(output_path, encoding="utf-8")
print(f"字幕已保存到 {output_path}")
# 使用
generate_subtitles("input.mp4", "output.srt")Whisper 的 transcribe() 方法返回的 segments 包含每句话的开始时间、结束时间和文本内容,直接对应 SRT 字幕的结构。
模型选择
| 模型 | 参数量 | 速度 | 准确率 | 适用场景 |
|---|---|---|---|---|
| tiny | 39M | 最快 | 一般 | 快速预览 |
| base | 74M | 快 | 较好 | 日常使用 |
| small | 244M | 中等 | 好 | 平衡选择 |
| medium | 769M | 慢 | 很好 | 中文推荐 |
| large | 1550M | 最慢 | 最好 | 高精度需求 |
中文语音建议用 medium 或 large 模型。
字幕烧录:FFmpeg subtitles 滤镜
有了 SRT 文件后,用 FFmpeg 的 subtitles 滤镜将字幕烧录到视频中。
基础烧录
ffmpeg -i input.mp4 \
-vf "subtitles=output.srt" \
-c:a copy \
output_with_subs.mp4自定义字幕样式
通过 force_style 参数控制字幕的外观:
ffmpeg -i input.mp4 \
-vf "subtitles=output.srt:force_style='FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2'" \
-c:a copy \
output_with_subs.mp4force_style 参数说明
| 参数 | 说明 | 示例 |
|---|---|---|
FontSize | 字体大小 | 24 |
PrimaryColour | 字体颜色(BGR + Alpha,&HAABBGGRR) | &H00FFFFFF(白色) |
OutlineColour | 描边颜色 | &H00000000(黑色) |
Outline | 描边宽度 | 2 |
Alignment | 位置(1-9,对应九宫格) | 2(底部居中) |
MarginV | 垂直边距 | 20 |
BorderStyle | 边框样式 | 1(描边+阴影) |
颜色格式转换
FFmpeg 的 force_style 使用 ABGR 格式的十六进制颜色,而不是常见的 RGB:
def hex_to_bgr(hex_color):
"""将 #RRGGBB 转换为 FFmpeg 的 &H00BBGGRR 格式"""
hex_color = hex_color.lstrip("#")
r = hex_color[0:2]
g = hex_color[2:4]
b = hex_color[4:6]
return f"&H00{b}{g}{r}"例如白色 #FFFFFF → &H00FFFFFF,红色 #FF0000 → &H000000FF。
位置控制:九宫格布局
FFmpeg 字幕的 Alignment 参数对应九宫格:
7(左上) 8(上) 9(右上)
4(左) 5(中) 6(右)
1(左下) 2(下) 3(右下)常用值:
2— 底部居中(默认)8— 顶部居中5— 正中间
位置映射表
POSITION_MAP = {
"top-left": {"alignment": 7, "margin_v": 30},
"top": {"alignment": 8, "margin_v": 30},
"top-right": {"alignment": 9, "margin_v": 30},
"left": {"alignment": 4, "margin_v": 0},
"center": {"alignment": 5, "margin_v": 0},
"right": {"alignment": 6, "margin_v": 0},
"bottom-left": {"alignment": 1, "margin_v": 20},
"bottom": {"alignment": 2, "margin_v": 20},
"bottom-right": {"alignment": 3, "margin_v": 20},
}完整的字幕烧录脚本
import argparse
import subprocess
import os
def hex_to_bgr(hex_color):
hex_color = hex_color.lstrip("#")
r, g, b = hex_color[0:2], hex_color[2:4], hex_color[4:6]
return f"&H00{b}{g}{r}"
POSITION_MAP = {
"top-left": {"alignment": 7, "margin_v": 30},
"top": {"alignment": 8, "margin_v": 30},
"top-right": {"alignment": 9, "margin_v": 30},
"left": {"alignment": 4, "margin_v": 0},
"center": {"alignment": 5, "margin_v": 0},
"right": {"alignment": 6, "margin_v": 0},
"bottom-left": {"alignment": 1, "margin_v": 20},
"bottom": {"alignment": 2, "margin_v": 20},
"bottom-right": {"alignment": 3, "margin_v": 20},
}
def burn_subtitles(input_video, srt_path, output_path,
font_size=24, position="bottom",
color="#FFFFFF"):
# 获取视频宽度
probe = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width",
"-of", "default=noprint_wrappers=1:nokey=1", input_video],
capture_output=True, text=True
)
width = int(probe.stdout.strip())
text_box_width = int(width * 0.8)
pos = POSITION_MAP.get(position, POSITION_MAP["bottom"])
alignment = pos["alignment"]
margin_v = pos["margin_v"]
bgr_color = hex_to_bgr(color)
style = (
f"FontSize={font_size},"
f"PrimaryColour={bgr_color},"
f"OutlineColour=&H00000000,"
f"Outline=2,"
f"Alignment={alignment},"
f"MarginV={margin_v},"
f"BorderStyle=1"
)
cmd = [
"ffmpeg", "-i", input_video,
"-vf", f"subtitles={srt_path}:force_style='{style}'",
"-c:a", "copy",
output_path
]
subprocess.run(cmd, check=True)
print(f"已输出到 {output_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="字幕烧录工具")
parser.add_argument("input", help="输入视频路径")
parser.add_argument("srt", help="SRT 字幕文件路径")
parser.add_argument("-o", "--output", default="output.mp4", help="输出路径")
parser.add_argument("-s", "--font-size", type=int, default=24, help="字体大小")
parser.add_argument("-p", "--position", default="bottom",
choices=list(POSITION_MAP.keys()), help="字幕位置")
parser.add_argument("--color", default="#FFFFFF", help="字体颜色(十六进制)")
args = parser.parse_args()
burn_subtitles(args.input, args.srt, args.output,
args.font_size, args.position, args.color)使用示例
# 底部居中,默认白色 24 号字
python burn_subs.py input.mp4 output.srt -o out.mp4
# 顶部居中,大号黄色字幕
python burn_subs.py input.mp4 output.srt -o out.mp4 \
-p top -s 32 --color "#FFFF00"
# 底部偏上(自定义位置)
# 先在 POSITION_MAP 中添加 "bottom-high": {"alignment": 2, "margin_v": 70}常见问题
1. force_style 找不到
FFmpeg 的 subtitles 滤镜在某些版本中,force_style 参数需要用冒号分隔而不是逗号:
# 如果逗号分隔报错,尝试冒号
force_style='FontSize=24:Alignment=2:MarginV=20'2. SRT 文件路径含特殊字符
如果路径中有冒号或反斜杠,FFmpeg 会解析出错。解决方案是把文件路径中的 \ 替换为 \\ 或 \\\\,或者把 SRT 文件放在简单路径下。
3. 中文字幕显示为方块
通常是因为 FFmpeg 找不到中文字体。指定字体:
force_style='FontName=WenQuanYi Micro Hei,FontSize=24'
或者在 force_style 中用 FontFile 指定字体文件绝对路径。
4. 字幕换行
长字幕需要自动换行。可以通过 TextBoxWidth 控制换行宽度(以像素为单位):
force_style='FontSize=24,TextBoxWidth={width}'
总结
Whisper 负责语音识别生成 SRT 字幕,FFmpeg 的 subtitles 滤镜负责将字幕烧录到视频。通过 force_style 参数可以灵活控制字体大小、颜色、位置等样式。两步操作各自独立,便于调试和迭代。