用无头 Chrome 逐屏截图 + ffmpeg 拼竖版视频:1080×1920 全流程与 4 个坑
我最近在维护一个日更的竖版短视频号,内容全是技术干货,一屏一个结论。最开始用剪辑软件做,改一句文案就要重新对齐字幕、重新导出,一条 10 秒的视频能耗掉四十分钟。后来换成「HTML 排版 + 无头 Chrome 截图 + ffmpeg 串帧」,一条视频的返工时间压到了三分钟以内------改文案只改 HTML,重跑两条命令就出片。
这篇文章把整条链路和踩过的坑一次写清楚。环境是 Windows,Chrome + PowerShell + ffmpeg 9.0.2,不需要装任何剪辑软件。
一、先想清楚:为什么不直接用剪辑软件
竖版知识类视频的结构其实非常固定:标题屏 → 三到四个要点屏 → 收尾屏。这种结构本质上是「排版」,不是「剪辑」。用剪辑软件做排版,等于用时间轴去干 CSS 的活。
用 HTML 做有三个直接好处:
- 字号、行高、安全边距全是代码,几屏之间天然一致
- 改一个数字只改一处,不用在时间轴上找关键帧
- 产物是 PNG 序列,出图即终稿,不存在「导出后字体变了」
代价是要写一点命令行。下面就是全部命令。
二、一个 HTML 管多屏:用 location.search 切屏
不要为每一屏写一个 HTML 文件,维护成本会翻倍。正确做法是一个文件里放多个 section,用 URL 参数决定显示哪一屏:
html
<section class="screen" data-idx="1">...</section>
<section class="screen" data-idx="2">...</section>
<section class="screen" data-idx="3">...</section>
<section class="screen" data-idx="4">...</section>
<script>
var idx = new URLSearchParams(location.search).get('s') || '1';
document.querySelectorAll('.screen').forEach(function (el) {
el.style.display = (el.dataset.idx === idx) ? 'flex' : 'none';
});
</script>
页面尺寸直接按 1080×1920 写死,body { margin: 0; width: 1080px; height: 1920px; }。这样截图出来的图不用二次裁剪,也不会出现「渲染尺寸和截图尺寸不一致导致文字偏移」。
三、截图:三个参数决定成败
单屏截图的完整命令(PowerShell):
powershell
$chrome = "C:\Program Files\Google\Chrome\Application\chrome.exe"
$tmp = "$env:TEMP\xkw_video"
Start-Process $chrome -ArgumentList @(
"--headless=old",
"--disable-gpu",
"--disable-extensions",
"--hide-scrollbars",
"--no-first-run",
"--user-data-dir=$tmp\profile_s1",
"--window-size=1080,1920",
"--screenshot=$tmp\s1.png",
"file:///C:/Users/xxx/AppData/Local/Temp/xkw_video/index.html?s=1"
) -Wait
四个参数里,有三个是我踩坑踩出来的。
坑 1:--headless=new 在本机会静默失败。
不是报错,是「进程正常退出、退出码 0、PNG 根本不生成」。第一次遇到时我以为是路径写错了,查了十几分钟才发现是 headless 模式的问题。换回 --headless=old 一次成功。所以如果你的脚本「跑完了但没产物」,先换这个参数,别去怀疑 HTML。
坑 2:不指定独立的 --user-data-dir,会附着到你已经打开的 Chrome。
现象是命令执行完没有任何报错,截图也没落地,或者截出来的是你当前浏览器窗口的内容。原因是 Chrome 对同一份用户数据目录只允许一个实例,第二次启动会把参数转交给已有实例然后自己退出。
对策是每一屏都给一个全新的临时目录(profile_s1、profile_s2......)。用同一个目录连续跑两次,第二次可能命中缓存,出图还是上一版------这一点在改文案后特别容易误判成「改了没生效」。
坑 3:路径里不要有中文。
--screenshot 和 file:/// 后面都走纯 ASCII 路径最稳。我的做法是统一把工作目录放到 %TEMP% 下的英文目录,产物再从那里拷走。中文路径在部分 Chrome 版本上会让截图静默失败,症状和坑 1 一模一样。
四个坑里前两个是「无报错失败」,这类问题最耗时间------所以建议在脚本里加一条产物校验:截图命令跑完立刻判断文件是否存在、大小是否大于 0,不存在就直接抛错,不要继续往下走。
四、串帧:xfade 比 concat 更适合排版型视频
四张 PNG 串成视频,最省事的写法是 concat,但那样是硬切,屏与屏之间会有明显跳变。用 xfade 做 0.3 秒交叉溶解,观感会顺很多:
powershell
ffmpeg -y `
-loop 1 -t 3 -i s1.png `
-loop 1 -t 3 -i s2.png `
-loop 1 -t 3 -i s3.png `
-loop 1 -t 3 -i s4.png `
-filter_complex "[0][1]xfade=transition=fade:duration=0.3:offset=2.7[v01];[v01][2]xfade=transition=fade:duration=0.3:offset=5.4[v02];[v02][3]xfade=transition=fade:duration=0.3:offset=8.1,format=yuv420p[v]" `
-map "[v]" -r 30 -c:v libx264 -crf 23 -movflags +faststart dy_video.mp4
offset 要按「前一段总时长减去过渡时长」递推,写错会报 Input link ... not found 或者出现黑帧。四屏各 3 秒、三次 0.3 秒过渡,成片 10.5 秒、607KB,上传抖音秒过。
还有一个容易忽略的点:本地不要加音轨。竖版知识类视频的背景音乐在平台发布页的曲库里选,既是授权音乐,也避免了自己塞音频后被判「搬运」。加了音轨反而要额外处理版权。
五、发布侧的两个动作别漏
一是自主声明。这类内容有 AI 参与(排版和文案都用了),发布页的「自主声明」必须选「内容由AI生成」,不选的话在抖音这类平台属于未声明。
二是封面别用默认帧。默认取首帧的话,信息流里的缩略图就是标题屏的中间一行字,点击率会很难看。宁可手动传一张标题屏的完整版。
六、跑了十几条之后的数据复盘
我把同一批选题分别做成「单图图文」和「竖版视频」发出去,得到一组很直接的数字:
| 形态 | 播放 | 完播率 | 2 秒跳出率 |
|---|---|---|---|
| 单图图文 | 8 ~ 9 | --- | 60% ~ 67%(划走率) |
| 竖版视频 | 17 → 23 | 6.67% | 53.33% |
结论有两个。视频形态确实比单图好,差不多是两倍;但完播率只有 6.67%,说明问题不在「做不做视频」,而在前两秒没留住人。
于是我把首屏改成了一个更硬的规则:第一屏不放栏目标题、不放「今天聊聊」,直接给数字结论。比如原来首屏写「显存不够怎么办」,改成「4.9GB 溢出到内存,速度掉 8 倍」。改完之后 2 秒跳出率有没有降下来,还要再跑几条才有结论,但方向是明确的------排版工具的收益是「改得快」,而改什么,只能靠数据告诉你。
自查清单
- HTML 是否为 1080×1920,
body是否去掉了默认 margin - 每屏是否用独立
--user-data-dir - 是否使用
--headless=old - 截图路径是否为纯 ASCII,产物是否非空
- xfade 的
offset是否逐段递推,成片有没有黑帧 - 本地是否未加音轨,音乐是否在平台曲库选
- 发布页「自主声明」是否已勾选 AI 生成
- 封面是否手动指定,而非默认首帧
作者:信可维,日常整理软件著作权申请材料的格式规范,也写一些能跑通的自动化小流程。