🎬 第六章 FFmpeg命令行实现图片水印及文字跑马灯
📄 一句话概括

滤镜是「一个输入、一个输出、只干一件事」的小盒,串起来就是链。 这一章要用的三个滤镜在这台机器上缺了,原因在编译期,不在用法。

📝 本节和前面几节的关系

前五章一直在调「编码参数」,画面本身没被改过。 从这一章起才开始动画面里的像素——加水印、写字、抹掉东西。

一、滤镜是流水线上的工位

一个滤镜只做一件事:翻转、缩放、画框、叠层。 它有一个输入口、一个输出口,进出都是「一帧画面」,不碰编码码流。

正因为接口这么窄,它们才能任意拼接—— 前一个的输出喂给后一个,谁也不必知道上下游是谁。

这台机器上注册了 468 个滤镜。找功能时按动词搜前缀最快:

关键字 数量 这一族里有谁


draw 4 adrawgraph、drawbox、drawgraph、drawgrid scale 6 scale、scale2ref、scale_cuda… fade 4 fade、afade、xfade、acrossfade overlay 3 overlay、overlay_cuda、overlay_qsv

注意 draw 那一行:本该排在最前面的 drawtext 并不在里面。 缺滤镜就是这么缺的——不是报错,是清单里干脆没有它。

二、链的顺序就是数据的顺序

先画一个红框,再把画面水平翻转:

取样点 亮度 说明


x=30 y=15 102 翻转前是框内,翻转后成了空处 x=140 y=15 81 翻转前是空处,翻转后成了框内 x=70 y=5 102 两个位置都在框外

框原本占 x 从 10 到 49,翻转后跑到 110 到 149,位置正好对称。 这说明「先画框、后翻转」——框也跟着一起翻了。

把顺序倒过来,先翻后画,框就老老实实待在左边。 命令行里 -vf drawbox=...,hflip 的逗号顺序,就是数据流过的顺序。

这一点在长链上尤其要紧:一条十级的链,中间某一级放错位置, 画面能出来,只是结果不对,而且不报任何错。

三、本机缺了哪三个,为什么缺

这一章要用的三个滤镜,这台机器上一个都没有:

滤镜 本领 本机


movie 把文件当源读进来 有 drawbox 画方框 有 overlay 叠图层 有 drawtext 写字 没有 subtitles 烧字幕 没有 delogo 去水印 没有

原因不在 FFmpeg,而在这份构建编译时没接上 freetype 与 libass。 滤镜是「有条件才编进去」的:依赖库不在,这个滤镜干脆不注册。

所以「本机没有这个滤镜」和「命令写错了」是两件完全不同的事。 先用清单确认滤镜在不在,再去看参数——顺序反了,会白查很久。

四、动手 Demo:数清单、搭链、量像素

先数一遍滤镜总数,再看这一章要用的几个在不在。 然后搭一条 drawbox → hflip 的链,真喂一帧进去,量两个位置的亮度。

import av, numpy as np
from fractions import Fraction

fs = sorted(av.filter.filters_available)
print(u"滤镜总数 %d" % len(fs))
for n in (u"movie", u"drawbox", u"drawtext", u"delogo"):
    print(u"%-10s %s" % (n, n in fs))
print(u"draw 族 %s" % u"、".join([x for x in fs if u"draw" in x]))

g = av.filter.Graph()
a = g.add_buffer(width=160, height=90, format=u"yuv420p",
                 time_base=Fraction(1, 25))
b1 = g.add(u"drawbox", u"x=10:y=10:w=40:h=20:color=red:t=fill")
b2 = g.add(u"hflip")
s = g.add(u"buffersink")
a.link_to(b1)
b1.link_to(b2)
b2.link_to(s)
g.configure()

fr = av.VideoFrame.from_ndarray(np.full((90, 160), 100, dtype=np.uint8),
                                format=u"gray")
fr = fr.reformat(format=u"yuv420p")
fr.pts = 0
a.push(fr)
y = s.pull().to_ndarray()
print(u"x=30 处 %d  x=140 处 %d  x=70 处 %d"
      % (y[15][30], y[15][140], y[5][70]))

实跑输出:

--------------------------------------------------------------------
6.1 FFmpeg 的滤镜技术——清单、链、以及本机缺了谁
--------------------------------------------------------------------

**① 先数一遍滤镜,再看这一章要用的几个在不在**

**这台机器上的 libavfilter 一共注册了 468 个滤镜。**

  滤镜          在本机
  ------------------------
  movie         有
  drawbox       有
  overlay       有
  scale         有
  crop          有
  drawtext      没有
  subtitles     没有
  delogo        没有

**后三个全是「没有」:drawtext、subtitles、delogo。**
**它们是这一章的三根支柱,drawtext 加文字、delogo 去水印、subtitles 烧字幕。**
**原因不在 FFmpeg 本身,而在这份构建编译时没接 freetype 与 libass 两个库——**
**滤镜是「有条件才编进去」的:依赖库不在,这个滤镜干脆就不注册。**

**② 名字里的规律:滤镜按「动作」命名**

  关键字      数量     这一族里有谁
  ------------------------------------------------------------------
  draw        4        adrawgraph、drawbox、drawgraph、drawgrid
  scale       6        scale、scale2ref、scale_cuda、scale_d3d11
  fade        4        acrossfade、afade、fade、xfade
  overlay     3        overlay、overlay_cuda、overlay_qsv

**注意 draw 那一行:只剩 adrawgraph、drawbox、drawgraph、drawgrid 四个,**
**本该排在最前面的 drawtext 并不在里面——缺滤镜就是这么缺的。**

**所以要找某个功能,先想它的英文动词,再去清单里搜前缀——**
**比翻文档快,也比问人可靠。找到之后先数一眼同类里少没少东西,**
**少了的那几个,往往正是你要用的。**

**③ 滤镜链:上一级的输出直接喂下一级**

**画一个红框,再把画面水平翻转:**

  取样点         亮度     说明
  --------------------------------------------------------
  x=30 y=15      102      翻转前是框内,翻转后成了空处
  x=140 y=15     81       翻转前是空处,翻转后成了框内
  x=70 y=5       102      两个位置都在框外

**框原本占 x 从 10 到 49,翻转后跑到 110 到 149,位置正好对称。**
**这条链在命令行里就是 `-vf drawbox=...,hflip`——**
**逗号隔开的顺序就是数据流过的顺序,写反了结果就完全不同。**

**④ 本章的写法:两条腿走路**

  小节内容       命令行怎么写                本机怎么验
  -------------------------------------------------------------------
  加图片水印     -vf movie 读图              能真跑
  加文字水印     -vf drawtext                缺滤镜,改用等价实验
  去水印         -vf delogo                  缺滤镜,改用等价实验
  叠加图层       -filter_complex overlay     建图失败,改手算

**凡是本机跑不了的,都会写清「命令行怎么写」和「为什么这台机器跑不了」,**
**再用一段能手算的等价实验把原理验证掉——**
**这样命令行照抄就能用,原理也不靠猜。**

time_base 必须是 Fraction,给元组 (1, 25) 会报 returned 22。 这是 PyAV 的类型检查,不是 FFmpeg 的要求,但踩一次要查很久。

亮度 102 而不是 100,是因为灰度转 yuv420p 时做了取整。 量像素实验里,个位数的偏差不用管,看的是「框内 81 与框外 102」这个台阶。

链的中间结果不能跳过——b1.link_to(b2) 这一行才是「串」的动作。 少了它,两个滤镜各接一个 buffer,链是断的,配流就会失败。

30 秒实验

  1. 查清单:ffmpeg -filters | findstr draw,看本机到底有哪些 draw 族。
  2. 换顺序:把 drawbox,hflip 改成 hflip,drawbox,重看两个取样点的亮度。
  3. 加一级:中间插一个 crop=w=80:h=90,看框还在不在画面里。

常见误区

  1. 把「缺滤镜」当成「参数写错」。先查清单,再看参数,顺序不能反。
  2. 以为滤镜能改码流。滤镜动的是解码后的像素,压缩参数是编码器的事。
  3. 链里少写 link。两个滤镜各自成链,配流直接失败,报错却很不直观。
自测三问(点开对答案) 1. 滤镜的输入输出是什么? —— 都是解码后的画面帧(或音频帧),不碰压缩码流。 2. 为什么这台机器上没有 drawtext? —— 编译时没接 freetype,属「有条件才编进去」的可选滤镜。 3. `-vf a,b` 里谁先执行? —— a 先,b 后。逗号顺序就是数据流过的顺序。

相关笔记