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. 以 1080x1920 啟動 Puppeteer
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 上能即時緩衝。