ByteNoteByteNote

字节笔记本

2026年7月20日

Python + Whisper + FFmpeg:视频字幕生成与烧录实战

API中转
¥120

Python + Whisper + FFmpeg:视频字幕生成与烧录实战

用 OpenAI Whisper 识别视频语音生成 SRT 字幕,再用 FFmpeg 将字幕烧录到视频中,支持自定义字体大小、位置和颜色。

环境准备

bash
pip install openai-whisper pysrt

# FFmpeg 需要单独安装
# macOS
brew install ffmpeg

# Ubuntu
apt install ffmpeg

字幕生成:Whisper 语音识别

基础版:生成 SRT 文件

python
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 字幕的结构。

模型选择

模型参数量速度准确率适用场景
tiny39M最快一般快速预览
base74M较好日常使用
small244M中等平衡选择
medium769M很好中文推荐
large1550M最慢最好高精度需求

中文语音建议用 mediumlarge 模型。

字幕烧录:FFmpeg subtitles 滤镜

有了 SRT 文件后,用 FFmpeg 的 subtitles 滤镜将字幕烧录到视频中。

基础烧录

bash
ffmpeg -i input.mp4 \
    -vf "subtitles=output.srt" \
    -c:a copy \
    output_with_subs.mp4

自定义字幕样式

通过 force_style 参数控制字幕的外观:

bash
ffmpeg -i input.mp4 \
    -vf "subtitles=output.srt:force_style='FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2'" \
    -c:a copy \
    output_with_subs.mp4

force_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:

python
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 参数对应九宫格:

text
7(左上)  8(上)   9(右上)
4(左)    5(中)   6(右)
1(左下)  2(下)   3(右下)

常用值:

  • 2 — 底部居中(默认)
  • 8 — 顶部居中
  • 5 — 正中间

位置映射表

python
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},
}

完整的字幕烧录脚本

python
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)

使用示例

bash
# 底部居中,默认白色 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 参数需要用冒号分隔而不是逗号:

text
# 如果逗号分隔报错,尝试冒号
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 参数可以灵活控制字体大小、颜色、位置等样式。两步操作各自独立,便于调试和迭代。

分享: