chore: 初始化仓库,提交 vibevideo 源码

- src/: HyperFrames 封装 CLI(hv)、组件、品牌样式、音频(TTS/混音)工具
- projects/: 各项目 project.yaml 与文案
- interactive/: 井字棋演示页
- .gitignore: 忽略 node_modules、构建产物与 HyperFrames 运行期缓存
This commit is contained in:
2026-10-12 18:06:20 +08:00
commit 9b63424740
42 changed files with 8004 additions and 0 deletions
+76
View File
@@ -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);
}
}