滤镜图语法:滤镜链的字符串表示法、分号与逗号的作用、滤镜图的分区与连接

好,咱们进入第二章。上一章我们把滤镜的基本概念捋了一遍,这一章要聊的,是FFmpeg滤镜体系里最核心、也最容易让人懵圈的东西——滤镜图语法。

说白了,就是怎么用一串字符串,把多个滤镜串起来、并联起来,组成一个处理流水线。我当年刚接触这个的时候,看着那一堆分号逗号,心里直犯嘀咕:这玩意儿到底是啥?后来在项目里被坑过几次,才真正搞明白。今天咱们就把这事彻底说透。

2.1 滤镜链:最简单的串联方式

先看最基础的情况。你只有一个滤镜,比如做缩放:

ffmpeg -i input.mp4 -vf "scale=1280:720" output.mp4

这没什么好说的。但如果你要先缩放,再转成灰度呢?

这时候就需要滤镜链了。多个滤镜用逗号 , 连接,前一个的输出自动成为后一个的输入。

ffmpeg -i input.mp4 -vf "scale=1280:720,format=gray" output.mp4

注意这个逗号。它的作用就是串联——数据从左往右流。scale处理完,交给format。

我的习惯:写滤镜链的时候,我会在脑子里画一条流水线。每个滤镜是一个工位,逗号就是传送带。这样不容易搞乱顺序。

再复杂一点,三个滤镜串联:

ffmpeg -i input.mp4 -vf "scale=1280:720,format=gray,eq=brightness=0.1" output.mp4

顺序是:缩放 → 转灰度 → 调亮度。每个滤镜只处理上一级的结果。

2.2 滤镜图:分号带来的并联世界

单条链子好理解。但实际场景里,你经常需要同时处理多个输入,或者把一个输出分给多个滤镜。这时候逗号就不够用了。

分号 ; 登场。它的作用是分隔不同的滤镜链,或者说,分隔不同的处理分支。

看个经典例子——画中画(PIP):

ffmpeg -i main.mp4 -i overlay.mp4 -filter_complex \
"[0:v]scale=1280:720[main]; \
 [1:v]scale=320:240[overlay]; \
 [main][overlay]overlay=10:10[out]" \
-map "[out]" output.mp4

这里有三条链,用分号隔开:

  • 第一条:处理主视频,缩放后存为标签 [main]
  • 第二条:处理叠加视频,缩放后存为标签 [overlay]
  • 第三条:把两个标签喂给 overlay 滤镜,合成最终结果

分号的作用,就是让这些链并行存在,互不干扰。每条链可以有自己的输入源、自己的处理逻辑。

核心记忆法:
  • ,(逗号)= 串联,数据流在同一链内传递
  • ;(分号)= 并联,分隔不同的链或分支

2.3 标签系统:给滤镜链起名字

刚才的例子里出现了 [main]、[overlay]、[out] 这样的东西。这就是标签(label)。

标签的作用是给某个处理阶段的输出命名,方便后面引用。没有标签,后面的滤镜就不知道从哪取数据。

标签的语法很简单:[名字]。名字随便起,但建议有意义。

看这个例子:

ffmpeg -i input.mp4 -filter_complex \
"[0:v]split[base][enhanced]; \
 [base]eq=brightness=0.0[base_out]; \
 [enhanced]eq=brightness=0.2,unsharp=5:5:1.0[enhanced_out]; \
 [base_out][enhanced_out]hstack[out]" \
-map "[out]" output.mp4

这里用到了 split 滤镜,它把一路视频分成两路。两路分别做不同的处理,最后用 hstack 并排显示。

标签就是管道上的阀门,你可以在任意位置接出来,也可以往任意位置送进去。

我曾经踩过的坑:标签名不能包含空格和特殊符号。我试过用 [my filter],结果FFmpeg直接报错。老老实实用下划线或驼峰:[my_filter] 或 [myFilter]。

2.4 输入输出索引:0:v 是什么意思?

刚才的例子里有 [0:v] 和 [1:v]。这是FFmpeg引用输入流的方式。

  • 0 表示第一个输入文件
  • 1 表示第二个输入文件
  • v 表示视频流(video)
  • a 表示音频流(audio)

所以 [0:v] 就是「第一个输入文件的视频流」。

如果你有多个视频流,还可以用 [0:v:0]、[0:v:1] 这样更精确的写法。不过日常用 [0:v] 就够了。

写法 含义
[0:v] 第1个输入文件的视频流
[1:a] 第2个输入文件的音频流
[0:v:0] 第1个输入文件的第1个视频流
[0:v:1] 第1个输入文件的第2个视频流

2.5 滤镜图的分区:把复杂逻辑拆开看

当滤镜图变得很复杂时,我习惯把它分区理解。每个分区完成一个独立的功能。

举个例子,一个常见的视频处理流程:

ffmpeg -i input.mp4 -filter_complex \
"[0:v]split=3[bg][fg1][fg2]; \
 [bg]boxblur=10:5[bg_blur]; \
 [fg1]crop=iw/2:ih:0:0,scale=640:480[fg1_scaled]; \
 [fg2]crop=iw/2:ih:iw/2:0,scale=640:480[fg2_scaled]; \
 [bg_blur][fg1_scaled]overlay=0:0[tmp]; \
 [tmp][fg2_scaled]overlay=640:0[out]" \
-map "[out]" output.mp4

这个图可以分成三个区:

  • 输入分区:split 把原视频分成三路
  • 处理分区:一路做模糊背景,两路分别裁剪并缩放
  • 合成分区:用两次 overlay 把两路前景叠到背景上

分区的好处是:出了问题,你能快速定位是哪一段的锅。我在项目里调试复杂滤镜图时,经常先跑通每个分区,再连起来。

2.6 核心知识图谱

下面这张图,把滤镜图语法的核心关系画出来了。建议你多看两眼,后面写复杂滤镜图的时候,脑子里有这个结构就不会乱。

滤镜图语法核心结构 输入流 [0:v] 输入流 [1:v] 滤镜链1: scale,format 滤镜链2: crop,scale 标签 [main] 标签 [overlay] 合成滤镜: overlay 输出 [out] 图例: 输入流 滤镜链(逗号串联) 标签 合成滤镜 输出 分号 ; 分隔不同链,逗号 , 串联链内滤镜,标签 [name] 命名中间结果

2.7 实战:从简单到复杂的写法演进

咱们用同一个需求,看看写法怎么一步步变复杂。

需求:把视频缩放成1280x720,加上文字水印「Hello」,再调亮一点。

版本1:一条链搞定

ffmpeg -i input.mp4 -vf "scale=1280:720,drawtext=text='Hello':fontsize=48,eq=brightness=0.1" output.mp4

三个滤镜用逗号串起来,简单直接。但有个问题:如果我想让文字水印在缩放之前加上去,效果会不一样(文字会被缩放影响)。

版本2:用标签控制顺序

ffmpeg -i input.mp4 -filter_complex \
"[0:v]drawtext=text='Hello':fontsize=48[texted]; \
 [texted]scale=1280:720,eq=brightness=0.1[out]" \
-map "[out]" output.mp4

先加文字,再缩放。文字不会被缩放影响,保持清晰。

版本3:多输入场景

ffmpeg -i bg.mp4 -i logo.png -filter_complex \
"[0:v]scale=1280:720[bg]; \
 [1:v]scale=200:100[logo]; \
 [bg][logo]overlay=W-w-10:10[out]" \
-map "[out]" output.mp4

两个输入,一个背景视频,一个Logo图片。各自处理完再合成。

我建议:刚开始写复杂滤镜图时,先用版本1的简单链。等确认每个滤镜参数都对,再拆成多链加标签。一步到位容易出bug,而且不好查。

2.8 常见错误与避坑

最后聊几个我实际踩过的坑,你遇到了能少走弯路。

坑1:分号位置搞错
分号是链之间的分隔符,不是滤镜之间的。写 scale=1280:720;format=gray 会报错,因为分号不能代替逗号。
坑2:标签名重复
同一个滤镜图里,标签名必须唯一。我试过两个链都输出 [out],结果后面的覆盖了前面的,合成时拿到的数据不对。
坑3:忘记 -map 输出标签
用了 -filter_complex 之后,输出必须用 -map "[标签名]" 指定。我刚开始经常忘了写,结果FFmpeg报「Output file does not contain any stream」。