时间轴预览图实现#
简单看一下 bilibili 的实现。在鼠标首次移动到时间轴上时,获取到一个 avif 格式的雪碧图,它包含了鼠标所指位置附近的预览画面。在鼠标沿着时间滑动的时候,会根据判断是否需要请求新的雪碧图
时长 23:40 秒的视频,拉取的雪碧图一共两张,每张是 10x10 的布局。应该有一个数据是让前端拿到视频的预览信息,可以定位到预览图在哪个 URL 的几行几列,后端则负责雪碧图的生成
FFmpeg 生成雪碧图#
我们来看一下通过 FFmpeg 如何在后端生成雪碧图。
测试视频还是使用 https://www.h264info.com/clips.html 中的 “The Simpsons Movie – 720p Trailer”,下载后重命名为 input.mp4
按照时间切割#
通过指定 fps=1
,可以每秒取一帧。tile=10x10
告诉 FFmpeg 我们的雪碧图格式是什么样子的。-q:v 20
来控制生成图的质量,这个参数越小质量则越高。我们可以发现 bilibili 的雪碧图 URL 后面有一个 50q 的参数,这个应该是质量 loss 50% 的意思。如果用 OSS 的话,一般是支持动态拿到不同质量的图的
$ ffmpeg -i input.mp4 -vf "fps=1,scale=320:-1,tile=10x10" -q:v 20 time-linear-%02d.jpg
一共生成了 136 张图,效果如下
因为 1 秒一个图,一张雪碧图的时间范围是 1 分 40 秒,这种对于短视频来说还好。但对于长视频来说就太多了
关键帧#
关键帧可以有效的避免生成雪碧图数量过多的问题
$ ffmpeg -skip_frame nokey -i input.mp4 -vsync vfr -vf "scale=320:-1,tile=10x10" -q:v 20 key-frame-%02d.jpg
效果如下
这里一共生成了 48 张图,最后是一张黑色的。如果使用这种,那么还需要告诉前端时间的信息,这点可以通过加入 showinfo
来 grep 到 FFmpeg 截取日志
$ ffmpeg -skip_frame nokey -i input.mp4 -vsync vfr -vf "showinfo,scale=320:-1,tile=10x10" -q:v 20 key-frame-%02d.jpg 2>&1 | grep "showinfo" | grep "duration_time"
部分日志如下
[Parsed_showinfo_0 @ 0x7e2d0c003d40] n: 0 pts: 1001 pts_time:0.0417083 duration: 1001 duration_time:0.0417083 fmt:yuv420p cl:left sar:0/1 s:1280x544 i:P iskey:1 type:I checksum:C627E3A8 plane_checksum:[1A3FA0BC B3F301D6 734F4116] mean:[93 108 91] stdev:[26.4 4.2 7.7]
[Parsed_showinfo_0 @ 0x7e2d0c003d40] n: 1 pts: 141141 pts_time:5.880875 duration: 1001 duration_time:0.0417083 fmt:yuv420p cl:left sar:0/1 s:1280x544 i:P iskey:1 type:I checksum:2AB765A2 plane_checksum:[10FCA9F3 0A0E6B5A 6F755046] mean:[91 121 132] stdev:[75.5 8.3 8.6]
[Parsed_showinfo_0 @ 0x7e2d0c003d40] n: 2 pts: 256256 pts_time:10.677333 duration: 1001 duration_time:0.0417083 fmt:yuv420p cl:left sar:0/1 s:1280x544 i:P iskey:1 type:I checksum:0D78B354 plane_checksum:[CFC05B20 A4B5EE5F C5ED69C6] mean:[64 123 133] stdev:[28.0 5.4 5.7]
这里的 pts_time
就是时间戳信息,n
则是小图的索引。有了两个数据就可以确定时间轴上应该显示的图片了
转场#
转场则是另一个手段
$ ffmpeg -i input.mp4 -filter:v "select='gt(scene,0.2)',showinfo,scale=320:-1,tile=10x10" -vsync vfr -q:v 20 -y scene-%02d.jpg
通过 select
滤镜来筛选场景变化。gt(scene,0.25)
表示筛选出场景变化程度大于 0.2 的帧。这个参数可以自己进行调整
时间信息还是通过 showinfo
在日志中提取即可
遇到的一些问题#
雪碧图数量不对#
举个例子,使用下面的命令生成视频
$ ffmpeg -f lavfi -i testsrc=duration=2.3:size=1280x720:rate=30 -c:v libx264 -pix_fmt yuv420p testsrc.mp4
我们能够看到 0 1 2 的计数器
如果我们按照 fps=1 来 1 秒生成一张图,那么理论上应该出现三张图片
$ ffmpeg -i testsrc.mp4 -vf "fps=1,scale=320:-1,tile=10x10" -q:v 20 time-linear-%02d.jpg
但是现在只有两张
当我们使用固定时间间隔来生成雪碧图的时候,可能需要设置这个 round
参数,参考文档 https://ffmpeg.org/ffmpeg-filters.html#fps-1
round
参数表示时间戳(PTS)的舍入方式。
可选值有:
zero
:向 0 舍入inf
:向远离 0 的方向舍入down
:向负无穷方向舍入up
:向正无穷方向舍入near
:舍入到最接近的值
更新后的命令如下
$ ffmpeg -i testsrc.mp4 -vf "fps=1:round=up,scale=320:-1,tile=10x10" -q:v 20 time-linear-with-round-%02d.jpg