码到成功
FFmpeg——从命令到工作流
`FFmpeg` 很像那种一开始看着有点凶、用熟以后又离不开的工具。
结果一打开命令行,参数就像瀑布一样砸下来。于是很多人对 `FFmpeg` 的印象会卡在两个极端里:
- “它特别强”
- “但我总记不住怎么写”
其实它没那么神秘。只要把几个核心概念理顺,`FFmpeg` 就会从“会背几条命令”变成“我知道该怎么组织媒体处理流程”。
官方文档把 `ffmpeg` 描述成一个可以处理音视频流的命令行工具,而 `ffprobe` 则负责把媒体信息用更适合人读或机器读的方式输出出来。[ffmpeg Documentation](https://ffmpeg.org/ffmpeg.html) [ffprobe Documentation](https://ffmpeg.org/ffprobe.html)
这一对搭档的感觉很像:
- `ffprobe` 先看清素材
- `ffmpeg` 再动手处理素材

## 先把 FFmpeg 的基本气质看明白
很多工具是“点一下功能就出结果”,`FFmpeg` 不是。
它更像一个媒体处理引擎,核心思路大概是:
1. 读输入
2. 识别里面有哪些流
3. 决定哪些流要保留、丢弃、复制、转码
4. 必要时挂上滤镜
5. 输出到新的容器或文件
官方文档里有一句话很关键:每个输入和输出在原则上都可以包含多个不同类型的基础流(elementary streams),比如视频流、音频流、字幕流等。[ffmpeg Documentation](https://ffmpeg.org/ffmpeg.html)
这个概念特别重要,因为很多初学者会把“一个文件”误当成“一个视频”。但在 `FFmpeg` 眼里,文件更像一个容器,里面装着很多流。
所以你后面写命令时,脑子里最好把素材拆成这几层:
- 容器
- 视频流
- 音频流
- 字幕流
- 元数据
一旦你开始这么想,很多命令会突然顺起来。
## 先别急着转码,先让 ffprobe 说话
官方文档对 `ffprobe` 的描述很直接:它会收集多媒体流信息,并把结果以人类可读和机器可读的方式打印出来。[ffprobe Documentation](https://ffmpeg.org/ffprobe.html)
这一步非常值钱,因为很多“FFmpeg 命令写错”的根源,不是命令不会写,而是素材根本没看清。
先看一个最基础的命令:
```bash
ffprobe input.mp4
```
如果你想把输出变得更适合机器处理,官方文档也明确说明了它支持不同 writer,比如 `json`、`csv`、`xml` 等。[ffprobe Documentation](https://ffmpeg.org/ffprobe.html)
一个非常常用的形式:
```bash
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
```
这个输出特别适合做下面几件事:
- 看视频分辨率
- 看音频编码格式
- 看总时长
- 看码率
- 看流索引
而这些信息,后面会直接决定你到底该不该转码、转哪一路流、是不是能直接 copy。
## 最重要的一个分界线:到底是 copy 还是转码
这几乎是日常使用 `FFmpeg` 时最值得先想清楚的问题。
### stream copy:能不重编码,就先别重编码
如果你只是:
- 换容器
- 抽某一路流
- 简单裁剪
并且原始编码本身就是你能接受的,那优先考虑 `-c copy`。
```bash
ffmpeg -i input.mkv -c copy output.mp4
```
这类命令的好处很直接:
- 快
- 不重新编码
- 没有额外画质损耗
但它也有前提:目标容器得能装下这些流,且编码本身兼容。
### 转码:当你真的要改编码、改尺寸、改码率时再动手
如果你要:
- 降低体积
- 改成 H.264 / H.265
- 改采样率
- 改分辨率
- 给移动端做兼容输出
那就要真正进入转码阶段。
一个最常见的例子:
```bash
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 和转码是两条完全不同的路”,已经能避开一大半混乱了。
## 抽流、抽帧、裁剪:这些命令其实特别生活化
### 把音频单独导出来
```bash
ffmpeg -i input.mp4 -vn -c:a copy output.aac
```
这里:
- `-vn` 表示不要视频
- `-c:a copy` 表示音频流直接复制
### 抽一张封面图
```bash
ffmpeg -i input.mp4 -ss 00:00:03 -frames:v 1 cover.jpg
```
这个命令特别适合:
- 视频封面生成
- 运营后台截图
- 内容审核取样
### 裁剪一段片头或片段
```bash
ffmpeg -ss 00:00:10 -i input.mp4 -t 00:00:08 -c copy clip.mp4
```
这类操作很常见,但要注意:
- 想要快,常常会尽量用 copy
- 想要边界更准,有时要配合重新编码
所以“快”和“精确”不总是同一个方向。
## 过滤器才是 FFmpeg 真正的魔术区
官方的过滤器文档非常长,但有一个核心概念特别值得先吃透:
**filtergraph 是一张由过滤器连接起来的有向图。**
[FFmpeg Filters Documentation](https://ffmpeg.org/ffmpeg-filters.html)
官方文档还给了一个很经典的图式例子:
```text
testsrc,split[L1],hflip[L2];[L1][L2] hstack
```
它的味道其实非常像数据流管道:
- 一个输入源进来
- 中途 split 成两路
- 其中一路做翻转
- 最后两路再拼起来
这个思路一旦懂了,你后面看复杂滤镜链就不会只觉得它像乱码。
### 最常用的几个视频滤镜
#### 缩放
```bash
ffmpeg -i input.mp4 -vf "scale=1280:720" output.mp4
```
#### 裁剪
```bash
ffmpeg -i input.mp4 -vf "crop=1280:720:0:0" output.mp4
```
#### 叠字
```bash
ffmpeg -i input.mp4 -vf "drawtext=text='hello':x=50:y=50:fontsize=36:fontcolor=white" output.mp4
```
#### 多个视频横向拼接
```bash
ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v][1:v]hstack=inputs=2[v]" -map "[v]" output.mp4
```
### 音频滤镜也很值得认识
比如调音量:
```bash
ffmpeg -i input.mp3 -af "volume=1.5" louder.mp3
```
或者淡入淡出:
```bash
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](https://ffmpeg.org/ffmpeg-filters.html)
这几乎是所有 FFmpeg 用户都迟早会撞上的坑。
尤其是:
- `drawtext`
- 包含冒号、逗号、引号的参数
- 多层 `filter_complex`
最实用的建议反而很朴素:
- 过滤链简单时,直接命令行写
- 一旦变复杂,尽量拆变量、拆脚本
- 文本内容能放文件就别硬塞命令行
你不需要靠硬扛转义来证明自己会用 FFmpeg。

## ffprobe + Python:这是做自动化最顺的一段
如果你只是手敲几个命令,命令行已经够用。
但一旦你要做:
- 批量转码
- 批量抽图
- 自动生成封面
- 批量检测分辨率、码率、时长
Python 就会非常舒服。
### 先用 Python 跑 ffprobe
```python
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
```python
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 语法,而是你开始把媒体处理组织成函数了。
这意味着后面你可以很自然地补:
- 批量处理
- 日志记录
- 失败重试
- 输入规则校验
### 一个更像实战的小工具:批量抽封面
```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 用顺以后,思路会变成这样
不是“我记住了几十条命令”,而是:
1. 先用 `ffprobe` 看素材
2. 判断是否能 `copy`
3. 确定要不要转码
4. 判断是否需要滤镜
5. 必要时用 Python 包成稳定流程
这套顺序一旦建立起来,后面的命令就不会再像散弹。