Paint Frame Reel Renderer 是做什么的?
在无头 Chrome 中渲染像素完美的 1080x1920 竖屏视频帧,构建微时序 FFmpeg concat demuxer 脚本,并编码最终包含 320kbps AAC 音频的 H.264 MP4。
flowchart TD
A[Web Canvas UI 1080x1920] --> B[Puppeteer Headless Capture]
B --> C[Visual Phase Screenshots]
C --> D[Concat Demuxer Script]
D --> E[FFmpeg Encoding libx264 + AAC + faststart]
E --> F[Finished Reel MP4]
为什么选择 Puppeteer + Concat Demuxer?
- 零丢帧:消除屏幕录制中的丢帧和卡顿。
- 微秒级精度:每个单词或帧状态都被赋予精确的浮点时长(秒)。
- 湍流墨水抖动:使用 SVG
feTurbulence和feDisplacementMap配合旋转种子,为静态文本赋予手绘标记的生命力。 - 速度:编码速度为实时录屏的 3–6 倍。
核心渲染脚本结构
const puppeteer = require('puppeteer-core');
const { spawnSync } = require('child_process');
// 1. 启动 Puppeteer 并设置 1080x1920
const browser = await puppeteer.launch({
executablePath: CHROME_PATH,
args: ['--no-sandbox', '--disable-gpu']
});
const page = await browser.newPage();
await page.setViewport({ width: 1080, height: 1920, deviceScaleFactor: 1 });
await page.goto('file:///' + HTML_PATH);
// 2. 捕获单词/行状态
const concatLines = [];
for (const line of linesData) {
// 捕获截图...
concatLines.push(`file '${framePath}'`);
concatLines.push(`duration ${duration.toFixed(3)}`);
}
// 3. Concat Demuxer 要求(重复最后一帧)
concatLines.push(concatLines[concatLines.length - 2]);
fs.writeFileSync('concat.txt', concatLines.join('\n'));
// 4. 使用 FFmpeg 编码
spawnSync(FFMPEG_PATH, [
'-f', 'concat', '-safe', '0', '-i', 'concat.txt',
'-ss', String(audioStartOffset),
'-i', songAudioPath,
'-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-r', '30',
'-c:a', 'aac', '-b:a', '320k',
'-shortest', '-movflags', '+faststart',
'-y', outputMp4Path
]);
关键优化规则
- 快速揭示:快速演唱期间揭示动画不得超过 0.24 秒。
- 设备缩放因子:设置
deviceScaleFactor: 1以确保精确的 1080x1920 分辨率且不增加内存负担。 - +faststart:始终包含
-movflags +faststart,以便 Reels 在 Instagram 和 Telegram 上即时缓冲。