75 lines
1.7 KiB
JavaScript
75 lines
1.7 KiB
JavaScript
/**
|
||
* 将 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`);
|
||
}
|