chore: 初始化仓库,提交 vibevideo 源码
- src/: HyperFrames 封装 CLI(hv)、组件、品牌样式、音频(TTS/混音)工具 - projects/: 各项目 project.yaml 与文案 - interactive/: 井字棋演示页 - .gitignore: 忽略 node_modules、构建产物与 HyperFrames 运行期缓存
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
/* ==========================================================================
|
||||
预览帧:hv shot
|
||||
--------------------------------------------------------------------------
|
||||
改完组件先抽几帧看看,比直接渲染整条快一个数量级。
|
||||
底层用的是渲染引擎自带的 snapshot,它按时间点精确抽帧,
|
||||
还会顺便输出一张 contact sheet 拼图方便一次看完。
|
||||
|
||||
用法:
|
||||
hv 看帧 <项目名> [--时间 1,3,5] [--数 5] [--缩放 .editor] [--输出 目录]
|
||||
========================================================================== */
|
||||
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
|
||||
export async function shotCLI(argv) {
|
||||
const arg = (k) => {
|
||||
const i = argv.indexOf(k);
|
||||
return i >= 0 ? argv[i + 1] : undefined;
|
||||
};
|
||||
const name = argv[0];
|
||||
if (!name) {
|
||||
console.log("用法: hv 看帧 <项目名> [--时间 1,3,5] [--数 5] [--缩放 .editor] [--输出 目录]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const dir = path.join(ROOT, "projects", name);
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.error(`✗ 项目不存在:${name}`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (!fs.existsSync(path.join(dir, "index.html"))) {
|
||||
console.error("✗ 还没有 index.html,先执行 hv build");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const at = arg("--时间") || arg("--at");
|
||||
const count = arg("--数") || arg("--frames") || "5";
|
||||
const zoom = arg("--缩放") || arg("--zoom");
|
||||
const outDir = arg("--输出") || arg("--output") || path.join(dir, "snapshots");
|
||||
|
||||
const bin = path.join(ROOT, "node_modules/.bin/hyperframes");
|
||||
const argvHf = ["snapshot", dir, "--output", outDir];
|
||||
if (at) argvHf.push("--at", at, "--no-end");
|
||||
else argvHf.push("--frames", count);
|
||||
if (zoom) argvHf.push("--zoom", zoom);
|
||||
|
||||
console.log(`正在抽帧:${name} ${at ? `时间点 ${at}s` : `${count} 帧`}`);
|
||||
try {
|
||||
const out = execFileSync(fs.existsSync(bin) ? bin : "npx", argvHf, {
|
||||
encoding: "utf8",
|
||||
maxBuffer: 32 * 1024 * 1024,
|
||||
});
|
||||
process.stdout.write(out);
|
||||
} catch (e) {
|
||||
if (e.stdout) process.stdout.write(e.stdout);
|
||||
if (e.stderr) process.stderr.write(e.stderr);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`\n看图目录:${outDir.replace(ROOT + "/", "")}`);
|
||||
console.log(`${C("contact-sheet.jpg")} 是所有帧的拼图,一张看完整条时间轴。\n`);
|
||||
}
|
||||
const C = (s) => `\x1b[36m${s}\x1b[0m`;
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
try {
|
||||
await shotCLI(process.argv.slice(2));
|
||||
} catch (e) {
|
||||
console.error("✗ " + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user