加了个班, 现已支持 Windows
0x0
印象中, 在某榴社区/杏某社区, 查看技术文章的时候, 经常看到一些根据视频制作的宫格缩略图, 如下图. 感觉一张图就能把整个资料一览无遗. 很强大.
大概查了下, 包括很多软件都是基于 ffmpeg 做的二次开发,
所以, 今天把 ffmpeg 下载下来 研究了下这个东西是如何制作的.
0x1 依赖环境
要使用这个工具,您需要在系统上安装ffmpeg
ffmpeg 是一个用于处理音频和视频文件的命令行工具。
ffmpeg 支持广泛的视频和音频格式、编解码器和容器,可以使用各种选项和过滤器进行定制,以达到特定的任务。
ffmpeg 可在 Windows、Mac 和 Linux 上使用,您可以按照官方网站上的说明进行安装。
还有许多第三方应用程序和库使用 ffmpeg 作为后端执行音频和视频处理任务。
0x2 How it works?
经过几天研究, 得出结论 , 要想实现这个宫格效果图,我们需要分三步:
- 0x0. 根据宫格数量, 将视频拆成 n 个片, 每个片截取一张图最后 合并成一张大图. 顺便将 缩略图当前的时间写入到图片中. 最后输出 大图 pic_x_y
- 0x1. 生成一张空白大图,获取视频信息(视频大小/长度/文件名) 写如到图片中, 输出 pic_info
- 0x2. 将 pic_x_y pic_info 俩张图 合并到一块
如果各位大佬有更优解,请指出 ~ 谢谢 !!
0x2.0x0
首先利用 ffprobe 计算视频总时长
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "one-piece.E0162.mkv"
1423.700000
假设 我们要切成 3x4, 那么就是一共12个图 ,
1423.700000/(3x4+1) ~= 109.51
我们根据 chunk 数量 计算平均时间, 这里 + 1 是因为 切片不是从0开始的, 是从第一个片的结束时间开始的. 如果不加1, 很可能最后只能切够 11 个图
之后用 ffmpeg 按 109秒的时间循环截取图片,并且合并成 3x4 的 大图
ffmpeg -i "/Users/voidm/Downloads/one-piece.E0162.mkv" -frames:v 1 -update 1 -vf "select=(gte(t\,109.51))*(isnan(prev_selected_t)+gte(t-prev_selected_t\,109.51)),drawtext=text='%{pts\:hms}':fontsize=h/15:fontcolor=white:x=w/20:y=h/20, scale=636:-1,tile=3x4:padding=20:margin=50:color=gray, " -fps_mode auto "/Users/voidm/Downloads/one-piece.E0162.mkv_time_2.png"
参数介绍:
select=(gte(t\,109.51))*(isnan(prev_selected_t)+gte(t-prev_selected_t\,109.51
tile=3x4:padding=20:margin=50:color=gray
- tile=3x4: 指定网格的行列数,这里是 3 行 4 列。
- padding=20: 指定每个帧之间的间距,这里是 20 像素。
- margin=50: 指定大图像的边距,这里是 50 像素。
- color=gray: 指定间距的颜色,这里是灰色。
drawtext=text='%{pts\:hms}':fontsize=h/15:fontcolor=white:x=w/20:y=h/20
- text='%{pts\:hms}': 指定要添加的文本内容,这里是显示视频时间戳的小时、分钟和秒数。
- fontsize=h/15: 指定文本字体的大小,这里是视频高度的 1/15。
- fontcolor=white: 指定文本的颜色,这里是白色。
- x=w/20: 指定文本的水平位置,这里是视频宽度的 1/20。
- y=h/20: 指定文本的垂直位置,这里是视频高度的 1/20。
scale=$tile_img_width:-1
scale 是输出分辨率, 用法有俩种. 放到tile前面是代表 tile 每一个切片的大小, 放到后面则是汇总图的大小.
如果在tile之后放置scale,则参数控制最终合成图像的分辨率。并且tile中的padding/margin属性可能无法正确计算。。
所以我们这离要放到 tile 前面。
此时我们需要根据最终输出的大图的width , 以及 padding,margin 计算出小图的 width
公式如下:
tile_img_width=$(echo "($composite_img_width - ($padding * ($x-1)) - ($margin * 2)) / $x" | bc)
假设: composite_img_width =2048,margin=50,padding=20 , 则 tile_img_width = 636
scale= 636:-1
执行完后就有了下图:
0x2.0x1
然后我们将视频信息写入到一张单独的小图中
info=$(ffprobe -v quiet -print_format json -show_format -show_streams "$abs_video_file")
filename=$(echo "$info" | jq -r '.format.filename')
size=$(echo "$info" | jq -r '.format.size')
duration=$(echo "$info" | jq -r '.format.duration')
width=$(echo "$info" | jq -r '.streams[0].width')
height=$(echo "$info" | jq -r '.streams[0].height')
cat >$text_tile <<EOF
Filename: $video_name
Size: $size
Resolution: ${width}x${height}
duration: ${duration}
EOF
先用 ffprobe 获取视频信息, 得到一个 json串,之后 用固定的 jsonpath 获取到视频的时长/大小/分辨率..
然后将这些视频先写入到一个 txt 文件中去, 之后在调用 ffmpeg 生成一张大图, 然后把 txt 写入到大图中
ffmpeg -f lavfi -i color=gray:s=2048x130:d=1 -update 1 -filter:v "drawtext=textfile='/Users/voidm/Downloads/one-piece.E0162.mkv_info.txt':fontsize=24:fontcolor=white:x=50/2:y=h/4" "/Users/voidm/Downloads/one-piece.E0162.mkv_info.png"
参数介绍 :
- lavfi -i color=gray:s=2048x130:d=1: 指定一个灰色的背景,大小为 2048x130 像素
- drawtext=textfile='/Users/voidm/Downloads/one-piece.E0162.mkv_info.txt':指定要在视频中绘制的文本内容
- fontsize=24 :指定字体大小为 24。
- fontcolor=white: 指定字体颜色为白色。
得到了下图:
0x2.0x2
最后 将 0x2.0x0 得到的图片 向上拉伸 130px 的位置,把 0x2.0x1 得到的图片 填充进来
ffmpeg -i "/Users/voidm/Downloads/one-piece.E0162.mkv_time_2.png" -i "/Users/voidm/Downloads/one-piece.E0162.mkv_info.png" -update 1 -frames:v 1 -filter_complex "[0:v]pad=iw:ih+130:0:130:color=white[top]; [top][1:v]overlay=0:0" "/Users/voidm/Downloads/one-piece.E0162.mkv_merge.png"
参数介绍:
- filter_complex: 指定要应用的复杂滤镜,可以在输入流之间进行多个滤镜操作。
- pad=iw:ih+130:0:130:color=white[top]: 使用 pad 滤镜将第一个输入流的视频流进行填充,使其高度增加 130 像素,并将填充后的视频流保存为 top。
- iw: 表示填充后的视频宽度与输入视频宽度相同。
- ih+130: 表示填充后的视频高度比输入视频高度增加了 130 像素。
- 0:130: 表示在垂直方向上向上填充 130 像素。
- color=white: 表示填充的颜色为白色。
- [top][1:v]overlay=0:0: 使用 overlay 滤镜将填充后的视频流 top 和第二个输入流中的视频流进行叠加,将第二个输入流的视频流放置在填充后的视频流的左上角。
- 0:0: 表示在水平方向和垂直方向上都不向右或向下偏移。
注意这些数字都是计算出来的,比如这里的填充 130 其实就是 信息小图的高度 , 而且 要合并的俩张图的宽度也得是一致的
最终得到成品图 :
0x3 一部到胃
为了方便, 我封装制作了一键shell 脚本..
Usage:
# Mac/Linux
sh thumbnail_maker.sh sample/video.mp4 4 5
# Windows
.\thumbnail_maker.bat sample\video.mp4 3 4
Github:
https://github.com/marlkiller/thumbnail_maker↗.
0x3 支持的平台
注:若转载请注明来源(本贴地址)与作者信息。