min 封顶、max 保底,两个套起来就是夹逼。 本机实测:逗号不用转义也能跑,转义是命令行那一层的事。
上一节的变量让命令能适配不同分辨率。这一节再加一层条件: 不仅要跟着输入变,还要落在某个范围内。
一、两个函数,一个封顶一个保底
min 取两个数里小的,max 取大的。就这两句,没有别的。
scale=min(iw,320):-1——宽度取「原本宽度」与 320 里小的那个。
效果是:原本不到 320 的素材原样不动,超过 320 的缩到 320。 一个参数就实现了「大图缩小、小图不动」。
二、四组实测
同一个 160 宽的素材,四种写法四种结果:
写法 想干的事 实际输出
min(iw,320):-1 宽最多 320 160×90 max(iw,320):-1 宽至少 320 320×180 min(iw,80):-1 宽最多 80 80×45 max(iw,80):-1 宽至少 80 160×90
第一行原样不变:输入本来只有 160 宽,min 在两数里取了 160。 第二行放大到 320:max 挑大的,320 赢。
第三行缩到 80,第四行不动——同一个素材,四种结果。 决定结果的不是素材,是你用了哪个函数、跟哪个数比。
三、转义是命令行那一层的事
网上的命令几乎都写成 min(iw\,320),逗号前面多个反斜杠。
本机把四种写法都试了一遍,全部跑通,包括一个反斜杠都不加的:
写法 本机结果
min(iw,320):-1 160×90 min(iw\,320):-1 160×90 min(max(iw,80),320):-1 160×90 min(max(iw\,80)\,320):-1 160×90
因为这段代码直接调接口,参数串已经是一个整体,中间没有「先按逗号切」这一步。
写成 -vf scale=min(iw,320):-1 就不同了。
命令行里那一长串要先切成滤镜、再切成参数,逗号在两层都是分隔符。 所以那边必须转义,不转义就会被截成两段。
跟第六章 movie 的路径引号是同一件事: 转义是给「解析那一层」加的,不是滤镜自己的要求。
四、两个函数套起来就是夹逼
先保底、再封顶:
scale=min(max(iw,80),320):-1
读法是从里往外:先把宽度抬到至少 80,再压到最多 320。 结果是不管输入多大多小,宽度都落在 80 到 320 之间。
批量处理尺寸不一的素材时,这一句比写一堆 if 省事得多。 视频墙、缩略图批处理、用户上传的任意素材,都是这个场景。
顺序不能反:max(min(iw,320),80) 是「先封顶再保底」,
对 160 这种本来就在区间里的值结果一样,但对极小的输入就不同了。
五、两个方向都夹会变形
min 与 max 只管写进去的那一边,另一边还得靠 -1 或 -2 去算。 如果宽高各写一个 min:
scale=min(iw,320):min(ih,180)
160×90 过一遍还是 160×90,因为两个数都没超。 但换一段 640×480 的素材,宽被压到 320、高被压到 180, 比例从 4:3 变成 16:9——画面就被横向拉扁了。
两个方向各自独立地夹,等于各改各的比例。 要保住比例,只能夹一个方向,另一个交给 -2。
六、动手 Demo:四种写法、一次夹逼,再验一遍转义
下面这段代码把四种写法、夹逼、以及转义与否都试一遍。 最后两行是为了说明:转义在这条路上根本不需要。
import av
from fractions import Fraction
TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
base = next(c0.decode(video=0)).to_ndarray()
c0.close()
def run(expr):
g = av.filter.Graph()
p = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
sc = g.add(u"scale", expr)
sk = g.add(u"buffersink")
p.link_to(sc)
sc.link_to(sk)
g.configure()
vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
p.push(vf)
o = sk.pull().to_ndarray(format=u"rgb24")
return u"%d×%d" % (o.shape[1], o.shape[0])
for expr in (u"min(iw,320):-1", u"max(iw,320):-1",
u"min(iw,80):-1", u"max(iw,80):-1",
u"min(max(iw,80),320):-1"):
print(u"%-26s -> %s" % (expr, run(expr)))
print(u"--- 带反斜杠的写法")
for expr in (u"min(iw\\,320):-1", u"min(max(iw\\,80)\\,320):-1"):
print(u"%-26s -> %s" % (expr, run(expr)))
print(u"--- 两个方向都夹")
for expr in (u"min(iw,320):min(ih,180)", u"min(iw,80):min(ih,45)"):
print(u"%-26s -> %s" % (expr, run(expr)))
实跑输出:
--------------------------------------------------------------------
7.2.4 使用 min 或 max 函数进行缩放
--------------------------------------------------------------------
**① 想让「大图缩小、小图不动」,就得有条件判断,min 与 max 就是最简的那种。**
`scale=min(iw,320):-1`——宽度取「原本宽度」与 320 里小的那个。
**② 四组实测**
写法 想干的事 实际输出
----------------------------------------------
min(iw,320):-1 宽最多 320 160×90
max(iw,320):-1 宽至少 320 320×180
min(iw,80):-1 宽最多 80 80×45
max(iw,80):-1 宽至少 80 160×90
**第一行原样不变:输入本来就只有 160 宽,min 取到 160。**
**第二行放大到 320:max 在两数里挑大的,320 赢。**
**第三行缩到 80,第四行不动——同一个素材,四种结果,全看要用哪个函数。**
**③ 转义是命令行那一层的事**
**网上的命令几乎都写成 `min(iw\,320)`,逗号前面多个反斜杠。**
**但本机四种写法全部跑通,包括一个反斜杠都不加的:**
写法 本机结果
-----------------------------------------
min(iw,320):-1 160×90
min(iw\,320):-1 160×90
min(max(iw,80),320):-1 160×90
min(max(iw\,80)\,320):-1 160×90
**因为这段代码直接调接口,参数串已经是一个整体,中间没有「先按逗号切」这一步。**
**写成 `-vf scale=min(iw,320):-1` 就不同了:那一长串要先切成滤镜、再切成参数,**
**逗号在那两层都是分隔符,所以必须转义。**
**跟第六章 movie 的路径引号是同一件事:转义是为「解析那一层」加的,不是滤镜自己的要求。**
**④ 两个函数套起来用**
`scale=min(max(iw,80),320):-1`——先保底 80,再封顶 320。
写法 实际输出
------------------------------------
min(max(iw,80),320) 160×90
**这句就是「夹逼」:不管输入多大多小,宽度都落在 80 到 320 之间。**
**批量处理尺寸不一的素材时,这一句比写一堆 if 省事得多。**
**⑤ min 与 max 只管数,不管比例**
**它们只对写进去的那一边生效,另一边还得靠 -1 或 -2 去算。**
**如果两个方向都写 min,画面就会被拉变形——因为两个方向各自独立地夹。**
五行结果跟上一张表逐条对上,夹逼那一行落在 160×90,因为它在区间内。 带反斜杠的两行结果一模一样,证明转义对结果毫无影响。
最后两行是「两个方向都夹」: 第一行两数都没超,所以原样;第二行两个都被压,比例就变了。
为什么第二行会变比例?因为 160 压到 80 是砍一半,90 压到 45 也是砍一半, 这次正好比例相同所以没看出来。换一段 4:3 素材就会明显拉扁。
30 秒实验
- 换数值:把夹逼的上下限改成 100 与 200,看输出落在哪。
- 换素材:拿一段 4:3 的素材跑「两个方向都夹」,量比例差多少。
- 去掉 -1:写作
min(iw,320):ih,看与-1有没有区别。
常见误区
- 以为 min 与 max 会自动保持比例。它们只改一个数,不管另一个。
- 在直接调接口时也加反斜杠。多转义一层反而可能出错。
- 两个方向都写 min。比例保不住,画面会被拉扁。
自测三问(点开对答案)
1. min 与 max 分别干什么? —— min 取小的(封顶),max 取大的(保底)。 2. 为什么命令行里要写 `min(iw\,320)`? —— 逗号在命令行那层是参数分隔符,必须转义;直接调接口时不需要。 3. 两个方向都写 min 会怎样? —— 两个方向各自被夹,比例可能改变,画面变形。- ☐ (本节完,下一节 7.2.5 使用 force_original_aspect_ratio 进行缩放)