2021-07-29
你可能只是想干一件很朴素的事: - 把视频转成 `mp4` - 抽一张封面图 - 把音频单独导出来 - 裁一段片头 - 给视频压一压体积
FFmpeg 很像那种一开始看着有点凶、用熟以后又离不开的工具。
结果一打开命令行,参数就像瀑布一样砸下来。于是很多人对 FFmpeg 的印象会卡在两个极端里:
其实它没那么神秘。只要把几个核心概念理顺,FFmpeg 就会从“会背几条命令”变成“我知道该怎么组织媒体处理流程”。
官方文档把 ffmpeg 描述成一个可以处理音视频流的命令行工具,而 ffprobe 则负责把媒体信息用更适合人读或机器读的方式输出出来。ffmpeg Documentation ffprobe Documentation
这一对搭档的感觉很像:
ffprobe 先看清素材ffmpeg 再动手处理素材很多工具是“点一下功能就出结果”,FFmpeg 不是。
它更像一个媒体处理引擎,核心思路大概是:
官方文档里有一句话很关键:每个输入和输出在原则上都可以包含多个不同类型的基础流(elementary streams),比如视频流、音频流、字幕流等。ffmpeg Documentation
这个概念特别重要,因为很多初学者会把“一个文件”误当成“一个视频”。但在 FFmpeg 眼里,文件更像一个容器,里面装着很多流。
所以你后面写命令时,脑子里最好把素材拆成这几层:
一旦你开始这么想,很多命令会突然顺起来。
官方文档对 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。
这几乎是日常使用 FFmpeg 时最值得先想清楚的问题。
如果你只是:
并且原始编码本身就是你能接受的,那优先考虑 -c copy。
ffmpeg -i input.mkv -c copy output.mp4
这类命令的好处很直接:
但它也有前提:目标容器得能装下这些流,且编码本身兼容。
如果你要:
那就要真正进入转码阶段。
一个最常见的例子:
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
这类操作很常见,但要注意:
所以“快”和“精确”不总是同一个方向。
官方的过滤器文档非常长,但有一个核心概念特别值得先吃透:
filtergraph 是一张由过滤器连接起来的有向图。
FFmpeg Filters Documentation
官方文档还给了一个很经典的图式例子:
testsrc,split[L1],hflip[L2];[L1][L2] hstack
它的味道其实非常像数据流管道:
这个思路一旦懂了,你后面看复杂滤镜链就不会只觉得它像乱码。
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 用户都迟早会撞上的坑。
尤其是:
drawtextfilter_complex最实用的建议反而很朴素:
你不需要靠硬扛转义来证明自己会用 FFmpeg。
如果你只是手敲几个命令,命令行已经够用。
但一旦你要做:
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 输出收进来比如:
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"))
这种脚本在内容平台、短视频后台、媒体资源仓库里都非常常见。
不是“我记住了几十条命令”,而是:
ffprobe 看素材copy这套顺序一旦建立起来,后面的命令就不会再像散弹。