一条视频三个平台:批量改尺寸、抽帧和文案
用 FFmpeg 和一段 Node 脚本,把一条横屏视频批量转成抖音、小红书、视频号需要的尺寸,同时抽出封面帧并生成三版文案。
30 秒速查
- 源视频放
input/,三平台参数在脚本顶部- 转码:
node scripts/convert.mjs input/demo.mp4- 抽帧:
node scripts/batch-media/extract-covers.mjs input/demo.mp4- 发布前查:时长、字幕、封面帧
这篇教程解决什么问题
同一条视频,抖音要 9:16、小红书常用 3:4、视频号又是一套尺寸。手动剪三遍,一小时就没了。
这篇的做法是:保留一份原始素材,用脚本一次生成所有平台版本。你只需要在发布前检查画面和文案,不再重复导出。
准备环境
需要两个东西:
- Node.js 20 或更高版本,用来跑批量脚本。
- FFmpeg,负责真正的转码和抽帧。
macOS 可以用 Homebrew 安装:
brew install ffmpeg
Windows 建议用 winget:
winget install Gyan.FFmpeg
安装完成后确认命令可用:
ffmpeg -version
node -v
目录结构
先在项目里建一个固定目录,后面所有脚本都围绕它工作:
batch-media/
├── input/
│ └── demo.mp4
├── output/
├── copy.json
└── scripts/
└── convert.mjs
原则只有一条:input 只读,output 只写。这样脚本跑错也不会破坏原素材。
三个平台的参数
尺寸会随平台规则变化,下面是写脚本时的起点,发布前仍需以平台最新要求为准:
| 平台 | 画面比例 | 输出尺寸 | 适合的场景 |
|---|---|---|---|
| 抖音 | 9:16 | 1080 × 1920 | 竖屏口播、剧情、教程 |
| 小红书 | 3:4 | 1080 × 1440 | 图文感较强、封面重要的内容 |
| 视频号 | 1:1 | 1080 × 1080 | 信息流里更稳,兼容横竖屏 |
两种适配方式
方式一:留边,不裁掉任何内容
ffmpeg -i input/demo.mp4 \
-vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black" \
-c:v libx264 -crf 20 -preset veryfast \
-c:a aac -b:a 128k -movflags +faststart \
output/douyin.mp4
force_original_aspect_ratio=decrease 会先把画面缩到能放进目标尺寸,再用 pad 补黑边。人物和字幕都不会被切掉。
方式二:裁切,填满画面
ffmpeg -i input/demo.mp4 \
-vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
-c:v libx264 -crf 20 -preset veryfast \
-c:a aac -b:a 128k -movflags +faststart \
output/douyin.mp4
increase 会先把画面放大到能盖住目标尺寸,再居中裁切。画面更满,但左右或上下会损失一部分。
批量脚本
把下面这段保存为 scripts/convert.mjs——就是仓库里 resources/batch-media/convert.mjs 的全文,需要和同目录的 tools.mjs 放在一起。它用 execFile 调用 FFmpeg,避免把文件名拼进 shell 命令里。
import { execFile } from "node:child_process";
import { constants } from "node:fs";
import { access, mkdir } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { promisify } from "node:util";
import { resolveFfmpeg } from "./tools.mjs";
const run = promisify(execFile);
const ROOT = import.meta.dirname;
export const PROFILES = [
{ name: "douyin", width: 1080, height: 1920, mode: "pad" },
{ name: "xiaohongshu", width: 1080, height: 1440, mode: "pad" },
{ name: "shipinhao", width: 1080, height: 1080, mode: "pad" },
];
export function buildFilter({ width, height, mode }) {
if (mode === "crop") {
return `scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height}`;
}
return `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:color=black`;
}
export function buildConvertArgs({ input, output, profile }) {
return [
"-y",
"-i", input,
"-vf", buildFilter(profile),
"-c:v", "libx264",
"-crf", "20",
"-preset", "veryfast",
"-pix_fmt", "yuv420p",
"-c:a", "aac",
"-b:a", "128k",
"-movflags", "+faststart",
output,
];
}
export async function convertAll({
input,
outputDir,
profiles = PROFILES,
ffmpeg = resolveFfmpeg(),
}) {
await access(input, constants.R_OK);
await mkdir(outputDir, { recursive: true });
const outputs = [];
for (const profile of profiles) {
const output = path.join(outputDir, `${profile.name}.mp4`);
await run(ffmpeg, buildConvertArgs({ input, output, profile }), {
maxBuffer: 10 * 1024 * 1024,
});
outputs.push(output);
}
return outputs;
}
async function main() {
const arg = process.argv[2];
const input = arg ? path.resolve(process.cwd(), arg) : path.join(ROOT, "input/demo.mp4");
const outputs = await convertAll({ input, outputDir: path.join(ROOT, "output") });
for (const output of outputs) console.log(`done: ${output}`);
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
await main();
}
运行:
node scripts/convert.mjs input/demo.mp4
跑完检查尺寸:
ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 output/douyin.mp4
自动抽封面帧
封面不要靠平台随机截。用 FFmpeg 在视频的 20%、50%、80% 位置各抽一帧:
ffmpeg -ss 00:00:02 -i input/demo.mp4 -frames:v 1 -q:v 2 output/cover-20.jpg
ffmpeg -ss 00:00:05 -i input/demo.mp4 -frames:v 1 -q:v 2 output/cover-50.jpg
ffmpeg -ss 00:00:08 -i input/demo.mp4 -frames:v 1 -q:v 2 output/cover-80.jpg
时间点要按视频长度改。更稳的做法是先取时长,再按比例计算:
ffprobe -v error -show_entries format=duration -of csv=p=0 input/demo.mp4
仓库里的 scripts/batch-media/extract-covers.mjs 已经把“取时长 → 按 20%/50%/80% 抽帧”封装好了:
node scripts/batch-media/extract-covers.mjs input/demo.mp4
三版文案怎么改
同一个内容,三个平台的文案结构不一样。建议只维护一份 copy.json,再用下面的结构改写成三版:
{
"topic": "一条视频多平台分发",
"points": ["不用剪三遍", "尺寸自动适配", "封面自动抽帧"],
"audience": "做自媒体但不想重复劳动的人",
"cta": "需要脚本的评论区说一声"
}
改写规则:
- 抖音:前三秒给结果,第一句直接说“别再手动剪三个版本了”。
- 小红书:标题写具体收益,正文分点,结尾给可执行的第一步。
- 视频号:语气更平,先讲场景,再讲方法,少用夸张词。
验证清单
发布前至少检查这四件事:
- 三个版本的时长和原片一致,没有音画不同步。
- 字幕没有被裁切或压在黑边上。
- 封面帧里人物表情正常,没有闭眼或运动模糊。
- 文件能在手机上正常播放,尤其是从 iPhone 拍的 HDR/HEVC 素材。
如果遇到某些设备播放黑屏,通常是编码兼容问题。把 -c:v libx264 和 -pix_fmt yuv420p 固定下来,兼容性最好。
成本与边界
这个方案适合同一条内容的多平台适配,不适合把长视频自动剪成多条短视频。后者需要理解内容结构,脚本只能做机械切割,效果通常不好。
转码本身消耗 CPU。十分钟的 1080p 素材在普通笔记本上大概需要几分钟,建议一次批量跑完,不要边剪边等。
下一步读什么
第一次装 ffmpeg 或换电脑,先跑一遍ffmpeg 不是内部或外部命令里的检查,确认 PATH 没问题。
转完视频,配图可以接着批量压缩图片,上传前把体积压下来。
常见问题
没有编程基础能跟着做吗?
可以。你只需要会复制命令、安装 FFmpeg 和 Node.js。脚本里的路径和尺寸都集中在文件顶部,改两个值就能跑。
会不会覆盖或损坏原视频?
不会。脚本只读取 input 目录,所有结果写到 output 目录,源文件不动。第一次跑建议用一条十几秒的短视频测试。
应该用留黑边还是裁切?
看画面主体。人物、字幕靠近边缘时用留边更安全;画面主体居中、边缘是空背景时用裁切更好看。脚本里两种模式都有。
为什么转出来的视频比原来大?
默认用 CRF 20 保证画质。想减小体积可以把 CRF 调到 23 到 26,或者把 preset 改成 slow,代价是转码更慢。
每周一张复习表
每周分享一张实用复习表,不收钱不打扰,想走随时走
邮箱存在 Cloudflare KV 里,只用来发每周更新;任何时候写信到hello@genwozuo.com说「退订」就删掉。详见隐私政策。