5.1 ffmpeg 基础


5.1 ffmpeg 基础

本节摘要:本节掌握流水线的胶水。ffmpeg 的心智模型一句话:一切皆转码——拼接是转码、缩放是转码、抽帧是转码、烧字幕还是转码,理解了这一点,几十个参数就归了类。四件事的命令族逐个过:拼接用 concat demuxer(同参数片段可流复制零转码,异参数必须先统一再拼);转码定成片档(H.264 加 CRF 是默认答案);分辨率统一用缩放加补边(保宽高比、黑边填齐);时长控制有截断、补齐、变速三招。然后是一张十二行速查表,覆盖流水线高频场景(含第 3.2 节用过的抽尾帧)。最后是 Python 封装的纪律:错误信息不能吞——ffmpeg 的报错都在 stderr 尾部,静默失败会让上游段以为大功告成。

学习目标

  • 用「一切皆转码」的模型归类 ffmpeg 的常用操作。
  • 用 concat demuxer 拼接片段,并判断能否流复制。
  • 为成片选定转码档位,理解 CRF 与 preset 的取舍。
  • 实现分辨率统一(保宽高比加补边)与时长控制三招。
  • 用 Python 封装 ffmpeg 并保留完整错误信息。

一、心智模型:一切皆转码

ffmpeg 的参数surface很大,但流水线里用到的只有一个模型:输入流经过滤镜(改像素)与编解码器(改格式),变成输出流。拼接是「多输入变一输出」,缩放是「滤镜改像素」,抽帧是「视频流变图片流」,烧字幕是「滤镜叠加图层」。带着这个模型读命令,参数不再是一堆咒语,而是「哪段进滤镜、哪段进编码器」的结构选择。

二、拼接:concat demuxer

0.2 节的最小闭环已经用过 concat 清单。流水线版本多一个判断:片段参数一致吗?

# bash —— concat demuxer 拼接(同参数片段可流复制,零转码零损) cd build/04_clips printf "file 'shot_101.mp4'\nfile 'shot_102.mp4'\nfile 'shot_201.mp4'\n" > list.txt ffmpeg -f concat -safe 0 -i list.txt -c copy merged.mp4 ​

-c copy 是流复制:不重新编码,秒级完成、无损。前提是所有片段的编码参数一致(编解码器、分辨率、帧率、像素格式)——第 3.1 节「全片统一」的纪律在这里兑现为速度。参数不一致时流复制会产出时间戳错乱的坏文件,正确做法是先统一转码(下节)再流复制拼接,宁可多花一遍编码时间。

「参数一致吗」不该靠肉眼判断,ffprobe 加十几行 Python 就是一台体检机:

# check_uniform.py —— 拼接前的片段参数体检(纯标准库,调用 ffprobe) import json import subprocess from pathlib import Path KEYS = ("codec_name", "width", "height", "r_frame_rate", "pix_fmt") def probe(path): proc = subprocess.run( ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=" + ",".join(KEYS), "-of", "json", str(path)], capture_output=True, text=True, check=True) return json.loads(proc.stdout)["streams"][0] def check(clips_dir): clips = sorted(Path(clips_dir).glob("shot_*.mp4")) if not clips: raise SystemExit("没有找到片段,先检查目录与生成段状态") base, base_name = probe(clips[0]), clips[0].name for clip in clips[1:]: info = probe(clip) diff = [k for k in KEYS if info.get(k) != base.get(k)] if diff: print("不一致:%s 与 %s 差在 %s" % (clip.name, base_name, "、".join(diff))) print("体检完成:%d 个片段,以 %s 为基准" % (len(clips), base_name)) if __name__ == "__main__": check("build/04_clips") ​

体检机输出的是「谁和谁差在哪」,不是「怎么修」——修复动作统一而简单:全部片段过一遍下一节的成片档转码,再回来流复制拼接。把体检放在拼接之前当门禁,坏消息会在最便宜的位置暴露;拼接之后才发现,返工的就是整条时间线。

三、转码:成片档位

参数 常用值(示意) 含义与取舍
-c:v libx264 通用兼容的默认答案;分发平台基本全收
-crf 18 到 23 质量与体积的旋钮:小更精、大更清;中间值起步
-preset medium 编码速度与压缩率:快preset出大文件
-pix_fmt yuv420p 兼容性像素格式,部分播放器只认它
-c:a 与 -b:a aac 与 192k 音轨档位(后半本音轨章节细化)
# bash —— 统一转码为成片档(异参数片段先过这一遍再拼接) ffmpeg -i input.mov -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p -c:a aac -b:a 192k output.mp4 ​

四、分辨率统一与时长控制

分辨率统一的原则是保宽高比加补边,不做暴力拉伸:先缩到目标内接尺寸,再补黑边到画布。时长控制三招:截断(-t)、补齐(克隆末帧)、变速(改 PTS)。

# bash —— 分辨率统一:缩放加补边到 1920 by 1080 画布(不拉伸) ffmpeg -i input.mp4 -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" out.mp4 # bash —— 时长控制三招:截断、克隆末帧补齐、轻微变速 ffmpeg -i in.mp4 -t 4.0 cut.mp4 ffmpeg -i in.mp4 -vf "tpad=stop_mode=clone:stop_duration=1.5" padded.mp4 ffmpeg -i in.mp4 -filter_complex "[0:v]setpts=0.8*PTS[v]" -map "[v]" faster.mp4 ​

补齐比截断更常用:生成段给出的片段常常比分镜时长短零点几秒(模型时长档离散),克隆末帧把尾巴补到分镜要求值,剪辑段的时间线才对得上账。

对账本身也要有工具动作:分镜时长的合计是预期值,成片的实际时长用 ffprobe 读出来,两者差值超出容差(如 0.5 秒,示意值)就说明有片段短缺没补齐、或有转场交叠吃掉了时长:

# bash —— 读成片实际时长(秒),与分镜时长合计对账 ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 merged.mp4 ​

对账不过关的处置顺序:先查各片段时长是否达到分镜值(短缺走 tpad 补齐),再查转场交叠是否重复扣减了时长——两类原因的修法完全不同,混在一起查会绕远路。

五、命令族速查表

场景 命令要点
拼接(同参数) -f concat -safe 0 -i list.txt -c copy
统一转码 -c:v libx264 -crf 20 -pix_fmt yuv420p
分辨率统一 scale 加 pad,force_original_aspect_ratio
截断时长 -t 秒数
补齐时长 tpad 滤镜,stop_mode 等于 clone
变速 setpts 乘系数
抽指定时间帧 -ss 时间 -frames:v 1 输出 png(第 3.2 节抽尾帧)
烧字幕 -vf subtitles 等字幕文件(0.2 节用过)
查看元信息 ffprobe 查流参数(拼接前检查参数一致性)
提取音轨 -vn -c:a copy(后半本音轨章用)
帧序列合成 -framerate 30 -i 帧模板(第 5.3 节用)
静音音轨 -f lavfi -i anullsrc(占位音轨,保容器声轨结构)

六、Python 封装:错误信息不能吞

# ff.py —— subprocess 封装 ffmpeg(失败必须带出 stderr 尾部) import subprocess def run_ffmpeg(args, cwd=None): cmd = ["ffmpeg", "-y"] + args proc = subprocess.run(cmd, capture_output=True, text=True, cwd=cwd) if proc.returncode != 0: raise RuntimeError("ffmpeg 失败:\n%s" % proc.stderr[-1200:]) return proc run_ffmpeg(["-i", "shot_101.mp4", "-c:v", "libx264", "-crf", "20", "shot_101_h264.mp4"], cwd="build/04_clips") ​

封装的要点只有一个:失败时把 stderr 的尾部带出来。ffmpeg 的报错几乎全在 stderr 的最后几十行,check=True 抛出的 CalledProcessError 默认不带输出,静默吞掉会让上游段误判成功——这是流水线里最难排查的一类假绿。参数一致性检查(拼接前 ffprobe 逐片段比对)建议一并封进去,1.1 节的失败隔离纪律在剪辑段的落点。

七、常见问题与排查

症状 根因 处置
流复制拼接后播放错乱、时长不对 片段参数不一致还强行复制 先统一转码再拼;拼接前跑参数体检
拼接后总时长与分镜对不上 片段短缺,或转场交叠吃掉时长 按对账流程分两头查:先补齐、再查交叠
烧出的字幕是乱码或方框 字幕文件编码不对,或滤镜未指定中文字体 字幕存 UTF-8,subtitles 滤镜带字体参数
画面绿屏、花屏 像素格式不是 yuv420p,或源片段损坏 转码统一像素格式;损坏片段回生成段重跑
Python 封装显示成功但没有产物 输出路径写错,或没检查返回码 核对路径;失败必须抛出 stderr 尾部
命令长时间不结束 输出被接到管道上无人读取 用 capture_output 收走 stdout 与 stderr

ffmpeg 的报错信息量很大,但几乎全部走 stderr——排查任何问题的第一步,都是先确认自己真的看得到 stderr。

本节要点回顾

  • 心智模型一句话:一切皆转码;滤镜改像素,编解码改格式。
  • 拼接优先流复制,前提是参数一致;异参数先统一再拼。
  • 成片档默认 libx264 加 CRF 18 到 23 加 yuv420p。
  • 分辨率统一保宽高比加补边;时长三招:截断、克隆补齐、变速。
  • Python 封装的核心纪律:stderr 尾部必须带出来,假绿比报错可怕。

命令行胶水在手,下一节上编排层——moviepy 读分镜 JSON,把片段、转场、字幕条自动排成时间线。

作者与出处
原作者:灏天文库
整理人: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U