码到成功
FFmpeg——从命令到工作流
FFmpeg 很像那种一开始看着有点凶、用熟以后又离不开的工具。
结果一打开命令行,参数就像瀑布一样砸下来。于是很多人对 FFmpeg 的印象会卡在两个极端里:
- “它特别强”
- “但我总记不住怎么写”
其实它没那么神秘。只要把几个核心概念理顺,FFmpeg 就会从“会背几条命令”变成“我知道该怎么组织媒体处理流程”。
官方文档把 ffmpeg 描述成一个可以处理音视频流的命令行工具,而 ffprobe 则负责把媒体信息用更适合人读或机器读的方式输出出来。ffmpeg Documentation ffprobe Documentation
这一对搭档的感觉很像:
ffprobe先看清素材ffmpeg再动手处理素材
先把 FFmpeg 的基本气质看明白
很多工具是“点一下功能就出结果”,FFmpeg 不是。
它更像一个媒体处理引擎,核心思路大概是:
- 读输入
- 识别里面有哪些流
- 决定哪些流要保留、丢弃、复制、转码
- 必要时挂上滤镜
- 输出到新的容器或文件
官方文档里有一句话很关键:每个输入和输出在原则上都可以包含多个不同类型的基础流(elementary streams),比如视频流、音频流、字幕流等。ffmpeg Documentation
这个概念特别重要,因为很多初学者会把“一个文件”误当成“一个视频”。但在 FFmpeg 眼里,文件更像一个容器,里面装着很多流。
所以你后面写命令时,脑子里最好把素材拆成这几层:
- 容器
- 视频流
- 音频流
- 字幕流
- 元数据
一旦你开始这么想,很多命令会突然顺起来。
先别急着转码,先让 ffprobe 说话
官方文档对 ffprobe 的描述很直接:它会收集多媒体流信息,并把结果以人类可读和机器可读的方式打印出来。ffprobe Documentation
这一步非常值钱,因为很多“FFmpeg 命令写错”的根源,不是命令不会写,而是素材根本没看清。
先看一个最基础的命令:
ffprobe input.mp4
如果你想把输出变得更适合机器处理,官方文档也明确说明了它支持不同 writer,比如 json、csv、xml 等。ffprobe Documentation
一个非常常用的形式:
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
这个输出特别适合做下面几件事:
- 看视频分辨率
- 看音频编码格式
- 看总时长
- 看码率
- 看流索引
而这些信息,后面会直接决定你到底该不该转码、转哪一路流、是不是能直接 copy。
最重要的一个分界线:到底是 copy 还是转码
这几乎是日常使用 FFmpeg 时最值得先想清楚的问题。
stream copy:能不重编码,就先别重编码
如果你只是:
- 换容器
- 抽某一路流
- 简单裁剪
并且原始编码本身就是你能接受的,那优先考虑 -c copy。
ffmpeg -i input.mkv -c copy output.mp4
这类命令的好处很直接:
- 快
- 不重新编码
- 没有额外画质损耗
但它也有前提:目标容器得能装下这些流,且编码本身兼容。
转码:当你真的要改编码、改尺寸、改码率时再动手
如果你要:
- 降低体积
- 改成 H.264 / H.265
- 改采样率
- 改分辨率
- 给移动端做兼容输出
那就要真正进入转码阶段。
一个最常见的例子:
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output.mp4
这里面最常见的几个参数可以先这样理解:
-c:v libx264:视频编码器-crf 23:画质和体积的平衡杆-preset medium:编码速度与压缩效率的平衡-c:a aac:音频编码器-b:a 128k:音频码率
不需要一次把所有编码器参数背熟,先知道“copy 和转码是两条完全不同的路”,已经能避开一大半混乱了。
抽流、抽帧、裁剪:这些命令其实特别生活化
把音频单独导出来
ffmpeg -i input.mp4 -vn -c:a copy output.aac
这里:
-vn表示不要视频-c:a copy表示音频流直接复制
抽一张封面图
ffmpeg -i input.mp4 -ss 00:00:03 -frames:v 1 cover.jpg
这个命令特别适合:
- 视频封面生成
- 运营后台截图
- 内容审核取样
裁剪一段片头或片段
ffmpeg -ss 00:00:10 -i input.mp4 -t 00:00:08 -c copy clip.mp4
这类操作很常见,但要注意:
- 想要快,常常会尽量用 copy
- 想要边界更准,有时要配合重新编码
所以“快”和“精确”不总是同一个方向。
过滤器才是 FFmpeg 真正的魔术区
官方的过滤器文档非常长,但有一个核心概念特别值得先吃透:
filtergraph 是一张由过滤器连接起来的有向图。
FFmpeg Filters Documentation
官方文档还给了一个很经典的图式例子:
testsrc,split[L1],hflip[L2];[L1][L2] hstack
它的味道其实非常像数据流管道:
- 一个输入源进来
- 中途 split 成两路
- 其中一路做翻转
- 最后两路再拼起来
这个思路一旦懂了,你后面看复杂滤镜链就不会只觉得它像乱码。
最常用的几个视频滤镜
缩放
ffmpeg -i input.mp4 -vf "scale=1280:720" output.mp4
裁剪
ffmpeg -i input.mp4 -vf "crop=1280:720:0:0" output.mp4
叠字
ffmpeg -i input.mp4 -vf "drawtext=text='hello':x=50:y=50:fontsize=36:fontcolor=white" output.mp4
多个视频横向拼接
ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v][1:v]hstack=inputs=2[v]" -map "[v]" output.mp4
音频滤镜也很值得认识
比如调音量:
ffmpeg -i input.mp3 -af "volume=1.5" louder.mp3
或者淡入淡出:
ffmpeg -i input.mp3 -af "afade=t=in:ss=0:d=2,afade=t=out:st=28:d=2" output.mp3
你会发现,-vf 和 -af 的感觉就像:
-vf处理视频流-af处理音频流
复杂一点时再上 -filter_complex。
滤镜链一复杂,最难的反而不是滤镜本身
官方过滤器文档里有一大段在讲转义和 escaping,而且说得非常直白:滤镜描述在命令行里可能会遇到多层转义问题,甚至会变得很痛苦。FFmpeg Filters Documentation
这几乎是所有 FFmpeg 用户都迟早会撞上的坑。
尤其是:
drawtext- 包含冒号、逗号、引号的参数
- 多层
filter_complex
最实用的建议反而很朴素:
- 过滤链简单时,直接命令行写
- 一旦变复杂,尽量拆变量、拆脚本
- 文本内容能放文件就别硬塞命令行
你不需要靠硬扛转义来证明自己会用 FFmpeg。
ffprobe + Python:这是做自动化最顺的一段
如果你只是手敲几个命令,命令行已经够用。
但一旦你要做:
- 批量转码
- 批量抽图
- 自动生成封面
- 批量检测分辨率、码率、时长
Python 就会非常舒服。
先用 Python 跑 ffprobe
from __future__ import annotations
import json
import subprocess
from pathlib import Path
def probe_media(path: str | Path) -> dict:
cmd = [
"ffprobe",
"-v", "quiet",
"-print_format", "json",
"-show_format",
"-show_streams",
str(path),
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return json.loads(result.stdout)
meta = probe_media("input.mp4")
print(meta["format"]["format_name"])
print(meta["format"]["duration"])
print(meta["streams"][0]["codec_name"])
这段代码做的事很简单,但很有工程感:
- 把
ffprobe的 JSON 输出收进来 - 后面就可以按字段做判断
比如:
- 视频是否超过 1080p
- 时长是否太长
- 封装格式是否符合要求
再用 Python 调 ffmpeg
from __future__ import annotations
import subprocess
from pathlib import Path
def transcode_to_h264(src: str | Path, dst: str | Path) -> None:
cmd = [
"ffmpeg",
"-y",
"-i", str(src),
"-c:v", "libx264",
"-crf", "23",
"-preset", "medium",
"-c:a", "aac",
"-b:a", "128k",
str(dst),
]
subprocess.run(cmd, check=True)
transcode_to_h264("input.mov", "output.mp4")
这里最关键的不是 Python 语法,而是你开始把媒体处理组织成函数了。
这意味着后面你可以很自然地补:
- 批量处理
- 日志记录
- 失败重试
- 输入规则校验
一个更像实战的小工具:批量抽封面
from __future__ import annotations
import subprocess
from pathlib import Path
def extract_cover(video_path: Path, output_dir: Path) -> None:
output_dir.mkdir(parents=True, exist_ok=True)
target = output_dir / f"{video_path.stem}.jpg"
cmd = [
"ffmpeg",
"-y",
"-i", str(video_path),
"-ss", "00:00:02",
"-frames:v", "1",
str(target),
]
subprocess.run(cmd, check=True)
for video in Path("./videos").glob("*.mp4"):
extract_cover(video, Path("./covers"))
这种脚本在内容平台、短视频后台、媒体资源仓库里都非常常见。
真正把 FFmpeg 用顺以后,思路会变成这样
不是“我记住了几十条命令”,而是:
- 先用
ffprobe看素材 - 判断是否能
copy - 确定要不要转码
- 判断是否需要滤镜
- 必要时用 Python 包成稳定流程
这套顺序一旦建立起来,后面的命令就不会再像散弹。