/** * 将 public/banner.mp4(或 banner.source.mp4)压成首屏 Hero 尺寸。 * 用法:pnpm run optimize:banner * 可选:把未压缩母带重命名为 banner.source.mp4,脚本会优先读取它并写回 banner.mp4。 */ import { execFileSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import ffmpegPath from "ffmpeg-static"; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); const publicDir = path.join(root, "public"); const sourceCandidates = [ path.join(publicDir, "banner.source.mp4"), path.join(publicDir, "banner.mp4"), ]; const input = sourceCandidates.find((p) => fs.existsSync(p)); if (!input || !ffmpegPath) { console.error("缺少 banner.mp4 / banner.source.mp4 或 ffmpeg 二进制。"); process.exit(1); } const outMp4 = path.join(publicDir, "banner.mp4"); const outWebm = path.join(publicDir, "banner.webm"); const tmpMp4 = path.join(publicDir, "banner.opt.mp4"); const scale = "scale=1920:-2"; const common = ["-y", "-i", input, "-an", "-vf", scale, "-pix_fmt", "yuv420p"]; console.log(`输入: ${input}`); execFileSync( ffmpegPath, [ ...common, "-c:v", "libx264", "-preset", "slow", "-crf", "28", "-movflags", "+faststart", tmpMp4, ], { stdio: "inherit" }, ); execFileSync( ffmpegPath, [ ...common, "-c:v", "libvpx-vp9", "-crf", "35", "-b:v", "0", "-row-mt", "1", outWebm, ], { stdio: "inherit" }, ); fs.copyFileSync(tmpMp4, outMp4); fs.unlinkSync(tmpMp4); for (const f of [outMp4, outWebm]) { const kb = (fs.statSync(f).size / 1024).toFixed(1); console.log(`${path.basename(f)}: ${kb} KiB`); }