🎬 第二章 FFmpeg命令行初体验
📄 一句话概括

抽一帧就是「解到那一刻,存一张图」,命令只要三个参数。 难点不在命令,而在「取第几秒」和「取什么格式」这两个选择上。

📝 本节和前面几节的关系

2.2 把整段流转过去了,这一节只取其中一帧。 2.4 反过来,把一堆图拼成视频——两条路正好互为逆操作。

一、三个参数决定一张图

把一帧存成图片,命令短到一行,但有三个参数必须知道:

参数 作用 写错的后果


-ss 跳到指定时刻 放在 -i 后会先解一遍前面 -vframes 1 只要一帧就停 不写会一直抽到片尾 -q:v JPEG 的质量档 对 PNG 无效,写了也不生效

第三行是个容易白忙一场的点:PNG 是无损格式,没有「质量」这个旋钮。 所以 -q:v 只对 JPEG 有效——写进 PNG 命令里,ffmpeg 不会报错,就是没作用。

抽缩略图一般用 JPEG:文件小、质量可调、肉眼几乎看不出差别。 要留原始画质(比如给算法做输入)才用 PNG。

二、大小取决于画面复杂度,不取决于时间点

这是个容易想错的地方:很多人以为「抽得越晚文件越大」,其实没关系。 下面 Demo 在同一段素材上抽了五个时刻,五张图的字节数几乎一样。

原因是这一段素材本身的画面复杂度在每个时刻都一样——纯色渐变加一个小方块。 压缩算法看到的东西一样,压出来的大小自然一样。

换成真实拍摄的素材,差异会明显得多:画面里有树叶、有水波的那些帧, 压缩后就是比纯色墙面大。所以这个规律要记成「看内容,不看时间」。

三、要一批图,两种思路

做封面只要一张,做时间轴预览要很多张,两条路不一样:

思路 做法 适合什么


按间隔抽 -vf fps=1/10 整片的均匀预览 按时刻抽 循环调用 -ss 只取几个关键点

第一行一条命令搞定全部帧,省事;fps=1/10 的意思是「每十秒一张」。 第二行每抽一张都要重新打开文件、重新 seek,麻烦但落点可控。

选哪个看用途:做「十秒一张」的时间轴预览选第一种, 做视频封面(要精确到某一帧)选第二种。

四、动手 Demo:抽帧的两种取法

在同一份素材的第 0、1、2、3、4 秒各抽一帧存成 PNG,比较五张图的字节数。 目的是验证一件事:图片大小到底由什么决定。

import os
import av

src = u"src.mp4"          # 5 秒、125 帧、160x90
sizes = []

for sec in (0, 1, 2, 3, 4):
    p = u"th%d.png" % sec
    c = av.open(src)
    c.seek(int(sec * 1000000))     # 相当于 -ss
    f = None
    for x in c.decode(video=0):
        f = x
        break                      # 相当于 -vframes 1
    if f is None:                  # 跳过头了就回到开头
        c.seek(0)
        for x in c.decode(video=0):
            f = x
            break
    f.to_image().save(p)
    c.close()
    sizes.append(os.path.getsize(p))

for i, sz in enumerate(sizes):
    print(u"第 %d 秒  %d 字节" % (i, sz))
print(u"最大最小差 %d 字节" % (max(sizes) - min(sizes)))

实跑输出:

--------------------------------------------------------------------
视频缩略图——抽帧的两种取法
--------------------------------------------------------------------

**① 抽一帧就是「解到那一刻,存一张图」**

  取第几秒     PNG 字节     与第 0 秒比
  -----------------------------------------
  第 0 秒      289          —(基准)
  第 1 秒      289          +0
  第 2 秒      291          +2
  第 3 秒      291          +2
  第 4 秒      291          +2

**五张图几乎一样大,最多差 2 字节。**
**这一段素材本身是「纯色渐变 + 一个小方块」,五个时刻的画面复杂度相同,**
**所以 PNG 压出来的大小也接近。换一段真实素材,这个差异会大得多。**
**结论只有一条:PNG 的大小取决于画面复杂度,不取决于时间点。**

**② 抽帧命令的两个关键参数**

  参数           作用                 放错位置的后果
  --------------------------------------------------------------
  -ss            跳到指定时刻         放后面就要解一遍前面的
  -vframes 1     只要一帧就停         不写会一直抽到结尾
  -q:v           输出 JPEG 的质量     对 PNG 无效

**注意第三行:-q:v 只对 JPEG 有效,PNG 是无损的,没有「质量」这个旋钮。**
**所以抽缩略图一般用 JPEG:文件小、质量可调,肉眼看不出差别。**

**③ 一次抽多张,有两种思路**

  思路         做法             适合
  ------------------------------------------------
  按间隔抽     -vf fps=1/10     整片的均匀预览
  按时刻抽     循环调用 -ss     只取几个关键点

**第一行更省事,一条命令搞定;第二行更可控,但每抽一张都要重新打开文件。**
**做封面图选第二行,做「十秒一张」的时间轴预览选第一行。**

c.seek(微秒) 对应命令行的 -ss,break 对应 -vframes 1。 这两个动作合起来就是「跳到那一刻,只要一帧」,和命令行一一对上。

那句 if f is None 是补丁:如果目标时刻已经超过片长,seek 之后可能一帧都解不出来。 命令行里 ffmpeg 遇到这种情况会直接报错退出,这里回到开头是让实验能跑完。

五个时刻的字节数差得很小,说明这段素材的画面复杂度在每个时刻都差不多。 这不是「时间点不重要」的证据,恰恰是「内容没变」的证据。

想亲眼看到大小差异,拿一段有镜头切换的素材来抽:黑场那几帧会小得离谱。 那是因为黑场几乎没有高频细节,压缩算法几下就描述完了。

30 秒实验

  1. 抽一张:ffmpeg -i 输入.mp4 -ss 00:00:05 -vframes 1 -q:v 3 out.jpg,把 -q:v 从 2 改到 30 看大小变化。
  2. 抽一批:ffmpeg -i 输入.mp4 -vf fps=1/10 out_%03d.jpg,一条命令出整片预览图。
  3. 比格式:同一帧分别存 PNG 和 JPEG,比大小、放大看边缘,体会「无损」和「质量档」的差别。

常见误区

  1. 以为抽得越晚文件越大。大小由画面复杂度决定,跟时间点没关系。纯色画面的第 100 秒也可能比第 1 秒小。
  2. 给 PNG 加 -q:v。PNG 无损,没有质量档。写了不报错,但一点作用都没有,白折腾。
  3. 忘记 -vframes 1。不加这一句,ffmpeg 会把整片每一帧都存成图片,一个 5 秒的片子能塞出 125 个文件。
自测三问(点开对答案) 1. `-vf fps=1/10` 是什么意思? —— 每 10 秒抽一张。`fps` 滤镜按目标帧率抽帧,写成 1/10 就是「每秒 0.1 帧」,换算下来正好十秒一张。 2. 为什么同一段素材抽五张,字节数几乎一样? —— 因为这段素材的画面复杂度在每个时刻都一样(纯色渐变加小方块)。压缩后的体积由画面内容决定,不由时间点决定。 3. 抽缩略图该用 JPEG 还是 PNG? —— 一般用 JPEG:体积小、质量可调、肉眼够用。要留原始画质给算法用才用 PNG,代价是文件大好几倍。

相关笔记