码到成功

FFmpeg——从命令到工作流


`FFmpeg` 很像那种一开始看着有点凶、用熟以后又离不开的工具。

结果一打开命令行,参数就像瀑布一样砸下来。于是很多人对 `FFmpeg` 的印象会卡在两个极端里:

- “它特别强”
- “但我总记不住怎么写”

其实它没那么神秘。只要把几个核心概念理顺,`FFmpeg` 就会从“会背几条命令”变成“我知道该怎么组织媒体处理流程”。

官方文档把 `ffmpeg` 描述成一个可以处理音视频流的命令行工具,而 `ffprobe` 则负责把媒体信息用更适合人读或机器读的方式输出出来。[ffmpeg Documentation](https://ffmpeg.org/ffmpeg.html) [ffprobe Documentation](https://ffmpeg.org/ffprobe.html)

这一对搭档的感觉很像:

- `ffprobe` 先看清素材
- `ffmpeg` 再动手处理素材

![工作流总览](https://zoytown-sg.oss-ap-southeast-1.aliyuncs.com/blogs/ffmpeg-workflow-ptxm.svg)

## 先把 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。

![滤镜和流](https://zoytown-sg.oss-ap-southeast-1.aliyuncs.com/blogs/ffmpeg-filters-ptxm.svg)

## 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 工作流图](https://zoytown-sg.oss-ap-southeast-1.aliyuncs.com/blogs/ffmpeg-python-ptxm.svg) 

### 再用 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 包成稳定流程

这套顺序一旦建立起来,后面的命令就不会再像散弹。