​Python + FFmpeg 实现 MP4 与 SRT 字幕无损合并(完整方案)​​

在日常视频处理中,将独立字幕(如 B 站下载的 .srt 文件)合并到视频中并保持无损,是高频需求。本文基于 ​FFmpeg​ 与 ​Python 标准库,提供一套稳定、可复用、支持中文的解决方案,核心特点是不重新编码视频/音频(速度快、无损)​正确处理 UTF-8 中文字幕设置字幕语言元数据

图片[1]_​Python + FFmpeg 实现 MP4 与 SRT 字幕无损合并(完整方案)​​_知途无界

一、实现目标

  • 合并 MP4 视频与 SRT 字幕为一个新视频文件;
  • 不重新编码视频/音频(保持原画质/音质,合并速度快);
  • 正确处理 UTF-8 编码的中文字幕(避免乱码);
  • 设置字幕语言元数据(如 chi,方便播放器识别);
  • 封装为可复用的 Python 函数,支持灵活调用。

二、环境准备

1. 安装 FFmpeg

FFmpeg 是核心工具,需先安装并配置环境变量。

  • Ubuntu/Debian​: sudo apt update && sudo apt install ffmpeg -y
  • macOS(Homebrew)​​: brew install ffmpeg
  • Windows​:
    FFmpeg 官网 下载编译好的二进制包(如 ffmpeg-master-latest-win64-gpl.zip),解压后将 bin 目录(含 ffmpeg.exe)添加到系统 PATH(或通过完整路径调用)。
  • 验证安装​:
    终端执行 ffmpeg -version,输出版本信息则成功。

2. Python 环境

仅需 Python 3.7+,​无需第三方库​(使用标准库 subprocessos 等)。

三、核心思路:FFmpeg 命令解析

合并字幕的本质是通过 FFmpeg 将 SRT 字幕封装到 MP4 容器中,关键命令如下:

ffmpeg -sub_charenc UTF-8 -i input.mp4 -i subtitle.srt -c:v copy -c:a copy -c:s mov_text -metadata:s:s:0 language=chi output.mp4

参数详解

参数作用
-sub_charenc UTF-8强制指定字幕编码为 UTF-8(解决中文乱码问题,必须加!)
-i input.mp4输入视频文件
-i subtitle.srt输入字幕文件(SRT 格式)
-c:v copy视频流不重新编码(直接复制,保持原画质,速度快)
-c:a copy音频流不重新编码(直接复制,保持原音质)
-c:s mov_text字幕流编码为 mov_text(MP4 容器支持的字幕格式,兼容主流播放器)
-metadata:s:s:0 language=chi设置第 0 条字幕流的语言为 chi(中文,播放器可识别)
output.mp4输出合并后的视频文件

四、完整 Python 实现代码

以下代码封装为函数,支持文件校验、错误处理、灵活参数配置:

import subprocess
import os
from typing import Optional

def merge_subtitle_to_video(
    video_path: str,
    subtitle_path: str,
    output_path: str,
    subtitle_language: str = "chi",
    overwrite_output: bool = True
) -> None:
    """
    将 MP4 视频与 SRT 字幕无损合并(不重新编码视频/音频)
    
    :param video_path: 输入视频文件路径(MP4)
    :param subtitle_path: 输入字幕文件路径(SRT)
    :param output_path: 输出合并后的视频路径(MP4)
    :param subtitle_language: 字幕语言元数据(如 "chi" 表示中文,"eng" 表示英文)
    :param overwrite_output: 是否覆盖已存在的输出文件(默认 True)
    :raises FileNotFoundError: 视频或字幕文件不存在
    :raises subprocess.CalledProcessError: FFmpeg 执行失败
    """
    # 1. 校验输入文件是否存在
    if not os.path.isfile(video_path):
        raise FileNotFoundError(f"视频文件不存在: {video_path}")
    if not os.path.isfile(subtitle_path):
        raise FileNotFoundError(f"字幕文件不存在: {subtitle_path}")
    
    # 2. 确保输出目录存在
    output_dir = os.path.dirname(output_path)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir, exist_ok=True)
    
    # 3. 构建 FFmpeg 命令(参数列表形式,避免 shell 注入风险)
    cmd = [
        "ffmpeg",
        "-sub_charenc", "UTF-8",  # 强制字幕编码为 UTF-8(关键!)
        "-i", video_path,         # 输入视频
        "-i", subtitle_path,      # 输入字幕
        "-c:v", "copy",           # 视频流不重新编码
        "-c:a", "copy",           # 音频流不重新编码
        "-c:s", "mov_text",       # 字幕编码为 MP4 支持的 mov_text
        "-metadata:s:s:0", f"language={subtitle_language}"  # 设置字幕语言
    ]
    
    # 4. 处理输出文件覆盖(若需覆盖,添加 -y 参数)
    if overwrite_output:
        cmd.insert(1, "-y")  # 插入到 ffmpeg 后,作为输入前的参数
    
    # 5. 添加输出文件路径
    cmd.append(output_path)
    
    # 6. 执行 FFmpeg 命令(捕获输出,失败时抛异常)
    try:
        result = subprocess.run(
            cmd,
            check=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True  # 输出为字符串(而非字节)
        )
        print(f"字幕合并成功!输出文件: {output_path}")
        print(f"FFmpeg 输出: {result.stdout}")
    except subprocess.CalledProcessError as e:
        error_msg = f"FFmpeg 执行失败!返回码: {e.returncode}\n错误信息: {e.stderr}"
        raise RuntimeError(error_msg) from e


# -------------------------- 示例调用 --------------------------
if __name__ == "__main__":
    # 示例路径(替换为实际文件路径)
    video_file = "input_video.mp4"       # 输入视频(MP4)
    subtitle_file = "subtitle.srt"       # 输入字幕(SRT,需 UTF-8 编码)
    output_file = "output_with_sub.mp4"  # 输出视频
    
    try:
        merge_subtitle_to_video(
            video_path=video_file,
            subtitle_path=subtitle_file,
            output_path=output_file,
            subtitle_language="chi",  # 中文
            overwrite_output=True
        )
    except Exception as e:
        print(f"合并失败: {e}")

五、代码结构解析

1. 文件校验

通过 os.path.isfile 提前检查视频和字幕文件是否存在,避免因路径错误导致的 FFmpeg 报错(更友好的错误提示)。

2. 使用 subprocess.run 的优势

  • 参数列表形式​:避免直接使用字符串命令(如 ffmpeg -i ...),防止 shell 注入攻击;
  • 捕获输出​:通过 stdout=subprocess.PIPEstderr=subprocess.PIPE 捕获 FFmpeg 的输出日志,便于调试;
  • 自动抛异常​:check=True 确保 FFmpeg 执行失败时(返回非 0 码)抛出 CalledProcessError,统一错误处理逻辑。

3. 选择 mov_text 的原因

MP4 容器原生不支持直接嵌入 SRT 字幕,需转换为容器兼容的格式。mov_text 是 MP4 官方推荐的字幕编码格式,支持主流播放器(VLC、QuickTime、手机端播放器等),且体积小、兼容性强。

六、常见问题与解决方案

1. 中文字幕乱码

  • 原因​:SRT 字幕文件编码非 UTF-8(如 GBK、ANSI),或未通过 -sub_charenc UTF-8 强制指定编码。
  • 解决​:
    • 确保 SRT 文件本身为 UTF-8 编码(可用记事本/VS Code 另存为 UTF-8 格式);
    • 代码中必须保留 -sub_charenc UTF-8 参数(关键!)。

2. 输出文件覆盖失败

  • 现象​:目标路径已存在文件,FFmpeg 报错 File exists
  • 解决​:代码中通过 overwrite_output=True 自动添加 -y 参数(FFmpeg 的覆盖标志),或在调用时手动删除旧文件。

3. 字幕语言元数据未生效

  • 现象​:播放器未识别字幕语言(如显示为“未知”)。
  • 解决​:检查 -metadata:s:s:0 language=chi 参数是否正确(s:s:0 表示第一条字幕流,chi 是中文的标准语言代码)。

4. 需要将字幕“烧录”到画面中(硬字幕)​

若需字幕固定在画面中(不可关闭),需使用重新编码视频的方式(速度慢、有损):

# 烧录字幕的 FFmpeg 命令(替换原函数中的 cmd 构建部分)
cmd = [
    "ffmpeg",
    "-i", video_path,
    "-vf", f"subtitles={subtitle_path}:force_style='FontName=SimHei,Fontsize=24,PrimaryColour=&HFFFFFF&'",  # 指定字体(如黑体)
    "-c:a", "copy",  # 音频仍不重新编码
    output_path
]
  • 缺点​:视频会被重新编码(耗时、画质可能下降),字幕无法关闭。

七、总结

本文方案通过 Python 调用 FFmpeg,实现了 MP4 与 SRT 字幕的无损合并,核心优势是速度快(不重新编码)、中文兼容(UTF-8 强制编码)、可复用(封装为函数)​。通过合理的参数配置和错误处理,可稳定应用于日常视频处理场景(如 B 站视频字幕合并、教学视频压制等)。

© 版权声明
THE END
喜欢就点个赞,支持一下吧!
点赞55 分享
评论 抢沙发
头像
欢迎您留下评论!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容