要做「新画布贴视频」,顺序是 nullsrc 造布、crop 定尺寸、overlay 贴上去。 overlay 只往已有画面上贴,它不会帮你扩画布。
前面学的 crop 是「从画面里取一块」,这一节要反过来「往画面上加一块」。 这是画中画、九宫格的地基。
一、先造画布,再贴
命令是两段:一段造布,一段贴图。
ffmpeg -f lavfi -i nullsrc=s=320x240:d=1 -i s7.mp4
-filter_complex "[0:v]crop=200:120:60:60[bg];[bg][1:v]overlay=20:20"
nullsrc 造一块什么都没有的画布,crop 把它的尺寸定成想要的大小,
overlay 再把真视频贴上去。三步一条链。
[bg] 是给中间结果起的标签,方括号里的名字随便起,
但前后必须一致——这是 filter_complex 的连接方式。
二、沙箱跑一遍这条链
三个环节各做什么,输出多大:
环节 做了什么 尺寸
nullsrc 造空画布 320×240 crop 抠出中间一块 200×120 overlay 贴到 (20, 20) 200×120
最终画布 200×120,左上角是 [0, 135, 0],贴图区域内是 [1, 1, 1]。
左上角那三个数里绿通道有 135,看着像一层深绿,不是纯黑。
nullsrc 的底本来就不是标准黑,7.4.4 一节专门讲这个坑。
要真黑底,把 nullsrc 换成 color=c=black 就行。
三、为什么非要绕这一圈
因为 overlay 只能把图贴到「已有画面」上,它不会帮你扩画布。 视频只有 160×90,想贴到 200×120 的画布中间,就得先有人把 200×120 那块布造出来。
nullsrc 干的就是这件事:不读任何文件,凭空给一帧。 它不占磁盘、不解码,成本几乎为零,所以被当成「默认画布」。
那 crop 在这里干什么?把 320×240 裁成 200×120。
为什么不直接写 nullsrc=s=200x120?可以直接写,crop 这一环是多余的。
教材这一节的写法串了三环,实际能用更短的写法:
nullsrc=s=200x120:r=25 一句话就把前两环做完了。
串三环的意义在于:crop 还能顺手改取景位置,一步到位。
四、沙箱的限制要说清楚
想做两个输入时,PyAV 这边有个坑:
要两个输入时 结果
两个 add_buffer 接 overlay returned 22 一路 buffer 加一路 movie 能跑
所以上面那段用的是 movie 去读文件补第二路,不是两个内存缓冲。
movie 的参数是文件路径,Windows 路径要把反斜杠换成正斜杠、冒号转义。
命令行里没有这个限制,-i a.mp4 -i b.mp4 就是两路输入,随便接。
这个差别只影响「用 Python 建图」的写法,不影响命令行的效果。
五、动手 Demo:nullsrc 造布、crop 定尺寸、overlay 贴图
下面这段代码把三环串起来跑一次,打印输出尺寸与两个取样点。 第二路用 movie 读文件补上,这是 PyAV 里唯一能跑通的双输入写法。
import av
ESC = u"s7.mp4".replace(u"\\", u"/").replace(u":", u"\\:")
g = av.filter.Graph()
ns = g.add(u"nullsrc", u"size=320x240:rate=25")
cr = g.add(u"crop", u"200:120:60:60")
mv = g.add(u"movie", ESC)
ov = g.add(u"overlay", u"20:20")
sk = g.add(u"buffersink")
ns.link_to(cr)
cr.link_to(ov, 0, 0)
mv.link_to(ov, 0, 1)
ov.link_to(sk)
g.configure()
o = sk.pull()
a = o.to_ndarray(format=u"rgb24")
print(u"输出 %d×%d" % (o.width, o.height))
print(u"左上角 %s" % a[0, 0].tolist())
print(u"贴图内 %s" % a[30, 30].tolist())
实跑输出:
--------------------------------------------------------------------
7.4.3 复杂滤镜 nullsrc、crop、overlay 结合使用
--------------------------------------------------------------------
**① 想做「新画布贴视频」,第一步不是贴,是造一块空画布**
`ffmpeg -f lavfi -i nullsrc=s=320x240:d=1 -i s7.mp4`
`-filter_complex "[0:v]crop=200:120:60:60[bg];[bg][1:v]overlay=20:20"`
**`nullsrc` 造一块什么都没有的画布,`crop` 把它的尺寸定成想要的大小,**
**`overlay` 再把真视频贴上去。三步一条链。**
**② 沙箱里跑一遍这条链**
环节 做了什么 尺寸
-----------------------------------------
nullsrc 造空画布 320×240
crop 抠出中间一块 200×120
overlay 贴到 (20, 20) 200×120
**最终画布 200×120,画布底色(左上角)是 [0, 135, 0],贴图区域里是 [1, 1, 1]。**
**左上角那三个数里绿通道有 135,看着像一层深绿,不是纯黑。**
**nullsrc 的底本来就不是标准黑,后面 7.4.4 一节专门讲这个坑。**
**③ 为什么非要绕这一圈**
**因为 overlay 只能把图贴到「已有画面」上,它不会帮你扩画布。**
**视频只有 160×90,想贴到 200×120 的画布中间,就得先有人把 200×120 那块布造出来。**
**nullsrc 干的就是这件事:不读任何文件,凭空给一帧。**
**④ 沙箱的一个限制要说清楚**
要两个输入时 结果
----------------------------------------------
两个 add_buffer 接 overlay returned 22
一路 buffer 加一路 movie 能跑
**所以上面那段用的是 `movie` 去读文件补第二路,不是两个内存缓冲。**
**命令行里没有这个限制,`-i a.mp4 -i b.mp4` 就是两路输入,随便接。**
三行输出:200×120、左上角 [0, 135, 0]、贴图内 [1, 1, 1]。
中间那一行就是 nullsrc 的底色,绿通道 135 说明它不是纯黑。
ov 的两个输入槽:槽 0 是底(nullsrc 那条链路),槽 1 是要贴的图。
link_to(ov, 0, 0) 与 link_to(ov, 0, 1) 最后那个数字就是槽号。
贴图内取样点给的是 (30, 30),落在贴图区域里—— overlay 从 (20, 20) 开始贴,所以 30 已经进去 10 个像素了。
30 秒实验
- 换位置:把
overlay=20:20改成0:0,看左上角变成什么颜色。 - 换尺寸:把 crop 改成
200:120:0:0,看画布内容变不变。 - 换底色:把 nullsrc 换成
color=c=black:s=320x240:r=25,看左上角是不是 [0, 0, 0]。
常见误区
- 以为 overlay 能扩画布。它只在已有画面上画,画布大小得先定好。
- 两个 add_buffer 接 overlay。PyAV 里直接 returned 22,必须用 movie 补位。
- movie 路径不转义。Windows 路径的冒号要写成
\:,否则解析失败。
自测三问(点开对答案)
1. 为什么需要 nullsrc? —— overlay 只能往已有画面上贴,想贴到更大的画布上就得先造出那块布。 2. `[bg]` 是什么? —— 给中间结果起的标签,供后面用 `[bg]` 引用,是 filter_complex 的连接方式。 3. PyAV 里为什么要用 movie 补第二路? —— 两个 add_buffer 同时接一个双输入滤镜会报 22,一路 buffer 加一路 movie 才行。- ☐ (本节完,下一节 7.4.4 nullsrc 参数说明)