halfsize

halfsize / ffmpeg 指定大小

ffmpeg 压到指定大小

-crf 要的是画质,文件多大它不管。当报名表写着 20 MB 时,你需要的是反过来: 先算码率预算,再两遍编码,最后确认真的塞得下。

为什么 -crf 压不准大小

CRF 是恒定画质,编码器把码率花在画面需要的地方。同样 CRF 下,一个人物访谈和一段烟花视频,文件大小能差四倍。存档时这是对的,面对有硬性上限的上传表单就是错的。

于是大多数人开始「猜一个 CRF、导出、看大小、再导一次」。这个循环能用,但每猜一次就要重编一遍,而且永远不告诉你画质付出了什么代价。

先算码率预算

编码器只认每秒多少比特,文件大小要换算过去:

视频码率 = (目标字节 × 8 ÷ 时长秒数) − 音频码率 − 容器开销

60 秒压到 20 MB(20,000,000 字节),总码率是 2.67 Mbps。减掉 128 kbps 的 AAC 音频,再留大约 1.5% 给 MP4 容器,视频流大约 2.46 Mbps。

剩下都是细节:旋转信息、HDR 标记、可变帧率,以及源音频本来就很低时,重新编码是否反而浪费码率。

两遍编码

单遍的平均码率要在编码时猜后面的画面有多复杂。两遍就不一样:第一遍把整条片子分析完,第二遍把预算花在真正需要的地方。

$ ffmpeg -y -i 输入.mp4 -c:v libx264 -b:v 2460k \
    -pass 1 -passlogfile ffmpeg2pass -an -f mp4 /dev/null
$ ffmpeg -i 输入.mp4 -c:v libx264 -b:v 2460k \
    -pass 2 -passlogfile ffmpeg2pass -c:a aac -b:a 128k \
    -movflags +faststart 输出.mp4

Windows 下把 /dev/null 换成 NUL。两遍跑完记得删掉 ffmpeg2pass* 日志。

为什么还是会差几个百分点

落在上限下方 1%–3% 才是正确结果:19.45 MB 到处能过,20.01 MB 会被同一个表单拒掉。

验证大小和画质

$ ffprobe -v error -show_entries format=duration,size \
    -of default=noprint_wrappers=1 输出.mp4

$ ffmpeg -i 输出.mp4 -i 输入.mp4 -lavfi libvmaf -f null -

VMAF 是 0–100 的感知相似度。90 以上基本「看着一样」,80–90 是「轻微变软」,70–80 能看出损失。只报大小不报 VMAF 的压缩,等于多绕几步的猜。

halfsize 把这些串起来了

$ npx halfsize 输入.mp4 --to 20mb

  target     20.0 MB
  plan       H.264  1280x720  2.46 Mbps  audio 128 kbps  2-pass
  scale      1920x1080 → 1280x720
  output     输入.20mb.mp4

  136 MB  →  19.45 MB   小 7.0 倍   比目标小 2.7%
  VMAF 88.3   SSIM 0.971   轻微变软

实测数据

60 秒 1080p30、源 18 Mbps,默认设置,普通笔记本:

目标输出分辨率视频码率VMAF
20 MB19.45 MB1280×7202.46 Mbps88.3
16 MB15.59 MB960×5401.94 Mbps84.8
10 MB9.77 MB960×5401.24 Mbps82.9
2 MB1.99 MB960×5400.25 Mbps79.9

常见问题

两遍一定比 CRF 好吗?

有硬性大小上限时是的:它是唯一能可预测地花完预算的模式。只在意画质时 CRF 更快也更好,不会把码率浪费在不需要的场景上。

直接用 -fs 20M 不行吗?

-fs 是写到上限就停,可能在高帧组中间截断,文件大小对了、结尾却播不了,而且完全不管画质怎么分配。

H.265 值得吗?

同画质大约省 30%–50%,可以把分辨率留得更高。代价是兼容性:老 Windows、部分浏览器和剪辑软件解不了。要保证到处能播,还是 H.264。

为什么 10 秒的片子更容易超?

片段短,码率控制器能平均的空间小,容器开销占比也更大。多留 5% 余量,或者手动把码率调低一点。

「指定大小」有多准?

一般在上限下方 1%–3%。编码器没法在开始前承诺精确字节数,留一点余量是故意的。

按去向选教程