| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- // 只跑【文字 + 音频】两模块(不调 Remotion、不需要 Chrome),把 VideoDocument 契约
- // 导出成 JSON,并打印一份人读摘要。用于快速验证重构后的契约与口播/卡片/字幕。
- //
- // 用法: node scripts/dump-video-document.mjs
- // TEMPLATE=knowledge INPUT=test/fixtures/sample-knowledge.json node scripts/dump-video-document.mjs
- // NO_TTS=1 node scripts/dump-video-document.mjs # 跳过真实 TTS(用静音估算时长)
- //
- // 需要的:.env 里的 LLM key(除非用 INPUT 直接给合法 JSON + skipLlm)。TTS key(除非 NO_TTS)。
- import { readFile, writeFile, mkdir } from "node:fs/promises";
- import { existsSync } from "node:fs";
- import { fileURLToPath, pathToFileURL } from "node:url";
- import { createRequire } from "node:module";
- import { resolve, join } from "node:path";
- const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
- process.chdir(root);
- // --- .env (填充未设置的 key) ---
- const envPath = join(root, ".env");
- if (existsSync(envPath)) {
- for (const line of (await readFile(envPath, "utf8")).split("\n")) {
- const t = line.trim();
- if (!t || t.startsWith("#")) continue;
- const eq = t.indexOf("=");
- if (eq < 0) continue;
- const k = t.slice(0, eq).trim();
- if (k && process.env[k] === undefined) process.env[k] = t.slice(eq + 1).trim();
- }
- }
- // --- config/default.yaml (yaml 通过 createRequire 从 shared 的依赖上下文解析出确切 dist 路径) ---
- const sharedRequire = createRequire(join(root, "packages/shared/package.json"));
- const yamlPath = sharedRequire.resolve("yaml");
- const yamlMod = await import(pathToFileURL(yamlPath).href);
- const config = yamlMod.parse(await readFile(join(root, "config/default.yaml"), "utf8"));
- // --- 模块(从编译好的 dist 导入) ---
- const { generateDocument } = await import(join(root, "packages/text/dist/index.js"));
- const { generateAudio } = await import(join(root, "packages/audio/dist/index.js"));
- const { resolveOutputDir } = await import(join(root, "packages/shared/dist/node.js"));
- const template = process.env.TEMPLATE ?? "github-trending";
- const source = process.env.INPUT ? undefined : (process.env.SOURCE ?? "github-trending");
- const providerName = process.env.TTS_PROVIDER ?? config.tts?.provider ?? "openai-tts";
- const providerConfig = config.tts?.[providerName];
- const outputDir = resolveOutputDir(config.output?.dir);
- const workDir = join(outputDir, "tmp", "dump");
- await mkdir(workDir, { recursive: true });
- const text = process.env.INPUT
- ? await readFile(resolve(process.env.INPUT), "utf8")
- : undefined;
- console.log(`\n=== 文字模块 (template=${template}, source=${source ?? "(input file)"}) ===`);
- const { doc: textDoc, publish } = await generateDocument({
- template,
- text,
- source,
- sourceArgs: undefined,
- collectorConfig: source ? config.collect?.[source] : undefined,
- llm: { model: process.env.LLM_MODEL ?? config.llm?.model ?? "glm-5.1" },
- skipLlm: process.env.SKIP_LLM === "1",
- });
- let doc = textDoc;
- await writeFile(join(workDir, "doc-after-text.json"), JSON.stringify(doc, null, 2));
- console.log(` ✓ 产出了 ${doc.data.length} 个段(cover=${doc.data.find(s => s.kind === "cover") ? "有" : "无"})。已写到 ${join(workDir, "doc-after-text.json")}`);
- if (publish) console.log(` publish (独立产物,不在 VideoDocument 内): title="${publish.title}" tags=[${(publish.tags ?? []).join(",")}]`);
- console.log(`\n=== 音频模块 (provider=${providerName}${process.env.NO_TTS === "1" ? ", 静音" : ""}) ===`);
- doc = await generateAudio(doc, {
- workDir,
- provider: providerName,
- voiceId: providerConfig?.defaultVoice,
- model: providerConfig?.model,
- format: config.tts?.format,
- speed: config.tts?.speed,
- skip: process.env.NO_TTS === "1",
- alignment: config.tts?.alignment,
- });
- await writeFile(join(workDir, "doc-after-audio.json"), JSON.stringify(doc, null, 2));
- // --- 人读摘要 ---
- const totalDur = doc.data.reduce((a, s) => a + (s.duration ?? 0), 0);
- console.log(`\n================ VideoDocument 摘要 ================`);
- console.log(`template=${doc.template} 段数=${doc.data.length} 总时长≈${totalDur.toFixed(1)}s`);
- console.log(`meta: title="${doc.meta?.title ?? ""}" subtitle="${doc.meta?.subtitle ?? ""}" trendSummary="${doc.meta?.trendSummary ?? ""}"`);
- if (publish) console.log(`publish (独立): title="${publish.title}" tags=[${(publish.tags ?? []).join(",")}]`);
- console.log(`----------------------------------------------------`);
- for (const s of doc.data) {
- const cards = s.cardList?.length ?? 0;
- const caps = s.caption?.length ?? 0;
- const origin = (s.captionOrigin ?? "").replace(/\s+/g, " ").slice(0, 48);
- console.log(` [${s.id}] kind=${s.kind} dur=${(s.duration ?? 0).toFixed(1)}s cards=${cards} captions=${caps}`);
- if (origin) console.log(` 口播: ${origin}${(s.captionOrigin ?? "").length > 48 ? "…" : ""}`);
- if (s.extension?.type === "github-trending") {
- const g = s.extension;
- console.log(` repo: ${g.repo.fullName} ★${g.repo.stars ?? "?"} 今日+${g.repo.todayStars ?? "?"} (extension)`);
- }
- }
- console.log(`====================================================`);
- console.log(`完整 JSON: ${join(workDir, "doc-after-audio.json")}`);
- console.log(`\n下一步: 确认口播/卡片/字幕无误后,用 ./scripts/verify-refactor.sh 跑完整渲染。`);
|