@gitruck/cli 0.2.19 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4196,7 +4196,7 @@ var {
4196
4196
  } = import__.default;
4197
4197
 
4198
4198
  // src/index.ts
4199
- import { readFileSync as readFileSync5 } from "node:fs";
4199
+ import { readFileSync as readFileSync6 } from "node:fs";
4200
4200
  import { join as join30 } from "node:path";
4201
4201
 
4202
4202
  // src/lib/paths.ts
@@ -4557,6 +4557,7 @@ function writeUserConfig(patch) {
4557
4557
  // src/lib/jianying.ts
4558
4558
  import { join as join4, resolve as resolve2 } from "node:path";
4559
4559
  import { existsSync as existsSync4 } from "node:fs";
4560
+ import { cp, mkdir, readdir } from "node:fs/promises";
4560
4561
  function probeJianyingDraftDir() {
4561
4562
  const local = process.env.LOCALAPPDATA;
4562
4563
  if (!local)
@@ -4575,6 +4576,31 @@ function resolveJianyingDraftDir(opt) {
4575
4576
  return saved;
4576
4577
  return probeJianyingDraftDir();
4577
4578
  }
4579
+ var JIANYING_DRAFT_FILES = ["draft_content.json", "draft_meta_info.json"];
4580
+ async function copyJianyingDraft(srcDir, destDir) {
4581
+ const entries = (await readdir(srcDir, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
4582
+ const chosen = new Map;
4583
+ for (const fixed of JIANYING_DRAFT_FILES) {
4584
+ const cands = entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith(fixed));
4585
+ const pick = cands.find((e) => e.name.toLowerCase() === fixed) ?? cands[0];
4586
+ if (pick)
4587
+ chosen.set(pick.name, fixed);
4588
+ }
4589
+ await mkdir(destDir, { recursive: true });
4590
+ const landed = {};
4591
+ const passthrough = [];
4592
+ for (const e of entries) {
4593
+ const fixed = chosen.get(e.name);
4594
+ const dest = join4(destDir, fixed ?? e.name);
4595
+ await cp(join4(srcDir, e.name), dest, { recursive: true });
4596
+ if (fixed)
4597
+ landed[fixed] = dest;
4598
+ else
4599
+ passthrough.push(e.name);
4600
+ }
4601
+ const missing = JIANYING_DRAFT_FILES.filter((f) => !landed[f]);
4602
+ return { landed, missing, complete: missing.length === 0, passthrough };
4603
+ }
4578
4604
 
4579
4605
  // src/lib/open.ts
4580
4606
  import { spawn } from "node:child_process";
@@ -6175,7 +6201,7 @@ function registerInstall(program2) {
6175
6201
 
6176
6202
  // src/commands/oralcut.ts
6177
6203
  import { resolve as resolve4, join as join15, dirname as dirname3, basename as basename6, extname as extname2 } from "node:path";
6178
- import { mkdir as mkdir4, writeFile as writeFile5, readFile as readFile4 } from "node:fs/promises";
6204
+ import { mkdir as mkdir5, writeFile as writeFile5, readFile as readFile4 } from "node:fs/promises";
6179
6205
  import { existsSync as existsSync13 } from "node:fs";
6180
6206
 
6181
6207
  // src/lib/config.ts
@@ -6330,7 +6356,7 @@ async function download(url, dest) {
6330
6356
 
6331
6357
  // src/lib/upload-cache.ts
6332
6358
  import { join as join11 } from "node:path";
6333
- import { stat as stat3, mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
6359
+ import { stat as stat3, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
6334
6360
  import { existsSync as existsSync10 } from "node:fs";
6335
6361
 
6336
6362
  // src/lib/chunk-upload.ts
@@ -6581,7 +6607,7 @@ async function load() {
6581
6607
  }
6582
6608
  }
6583
6609
  async function save(cache) {
6584
- await mkdir(CACHE_DIR, { recursive: true });
6610
+ await mkdir2(CACHE_DIR, { recursive: true });
6585
6611
  await writeFile2(CACHE_FILE, JSON.stringify(cache, null, 2));
6586
6612
  }
6587
6613
  var defaultUploadCacheDeps = {
@@ -6608,7 +6634,7 @@ async function loadSessions() {
6608
6634
  }
6609
6635
  }
6610
6636
  async function saveSessions(sessions) {
6611
- await mkdir(CACHE_DIR, { recursive: true });
6637
+ await mkdir2(CACHE_DIR, { recursive: true });
6612
6638
  await writeFile2(SESSION_FILE, JSON.stringify(sessions, null, 2));
6613
6639
  }
6614
6640
  var fileSessionStore = {
@@ -6696,7 +6722,7 @@ async function uploadAndSubmitTask(cfg, path, taskType, buildPayload, options =
6696
6722
  }
6697
6723
 
6698
6724
  // src/lib/media.ts
6699
- import { mkdir as mkdir2, stat as stat4 } from "node:fs/promises";
6725
+ import { mkdir as mkdir3, stat as stat4 } from "node:fs/promises";
6700
6726
  import { existsSync as existsSync11 } from "node:fs";
6701
6727
  import { basename as basename4, extname, join as join12 } from "node:path";
6702
6728
  function parseFps(rate) {
@@ -6747,7 +6773,7 @@ function probeDuration(path, ffmpegPath) {
6747
6773
  async function artifactPath(inputAbs, ext) {
6748
6774
  const s = await stat4(inputAbs);
6749
6775
  const base = basename4(inputAbs, extname(inputAbs));
6750
- await mkdir2(audioCacheDir(), { recursive: true });
6776
+ await mkdir3(audioCacheDir(), { recursive: true });
6751
6777
  return join12(audioCacheDir(), `${base}.${s.size}_${Math.round(s.mtimeMs)}.${ext}`);
6752
6778
  }
6753
6779
  async function extractAudio(inputAbs, ffmpegPath) {
@@ -6810,7 +6836,7 @@ function assertDurationConsistent(originalDuration, artifactAbs, ffmpegPath, tol
6810
6836
 
6811
6837
  // src/lib/materialize.ts
6812
6838
  import { join as join14, basename as basename5 } from "node:path";
6813
- import { mkdir as mkdir3, cp, writeFile as writeFile4 } from "node:fs/promises";
6839
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
6814
6840
 
6815
6841
  // src/lib/render.ts
6816
6842
  import { writeFile as writeFile3, unlink, readFile as readFile3 } from "node:fs/promises";
@@ -7037,7 +7063,7 @@ async function materializeResult(opts) {
7037
7063
  if (!files.length)
7038
7064
  throw new Error("任务无工程文件产物(检查 project_formats / 任务是否产出)");
7039
7065
  const errors = { ...output.errors ?? {} };
7040
- await mkdir3(outDir, { recursive: true });
7066
+ await mkdir4(outDir, { recursive: true });
7041
7067
  const resultPath = join14(outDir, "result.json");
7042
7068
  const writeResult = async (extra) => {
7043
7069
  const r = {
@@ -7061,7 +7087,7 @@ async function materializeResult(opts) {
7061
7087
  for (const f of files) {
7062
7088
  const base = baseFormat(f.format);
7063
7089
  const fmtDir = join14(outDir, base);
7064
- await mkdir3(fmtDir, { recursive: true });
7090
+ await mkdir4(fmtDir, { recursive: true });
7065
7091
  const dest = join14(fmtDir, f.filename);
7066
7092
  try {
7067
7093
  await dl(f.download_url, dest);
@@ -7078,13 +7104,17 @@ async function materializeResult(opts) {
7078
7104
  }
7079
7105
  let jianyingDraftPath = null;
7080
7106
  if (byFormat.jianying && opts.draftDir) {
7107
+ const dest = join14(opts.draftDir, basename5(outDir));
7081
7108
  try {
7082
- jianyingDraftPath = join14(opts.draftDir, basename5(outDir));
7083
- await mkdir3(jianyingDraftPath, { recursive: true });
7084
- await cp(join14(outDir, "jianying"), jianyingDraftPath, { recursive: true });
7085
- log.info(`剪映草稿已落到:${jianyingDraftPath}`);
7109
+ const landing = await copyJianyingDraft(join14(outDir, "jianying"), dest);
7110
+ if (landing.complete) {
7111
+ jianyingDraftPath = dest;
7112
+ log.info(`剪映草稿已落到:${dest}`);
7113
+ } else {
7114
+ errors["jianying:draft"] = `草稿两件套不全(缺 ${landing.missing.join("、")}),剪映列表里不会显示:${dest}`;
7115
+ log.warn(errors["jianying:draft"]);
7116
+ }
7086
7117
  } catch (e) {
7087
- jianyingDraftPath = null;
7088
7118
  errors["jianying:draft"] = e instanceof Error ? e.message : String(e);
7089
7119
  log.warn(`剪映草稿落盘失败:${errors["jianying:draft"]}`);
7090
7120
  }
@@ -7259,7 +7289,7 @@ async function runOralCut(input, opts) {
7259
7289
  const { taskId } = submitted;
7260
7290
  const up = { fileId: submitted.fileId, cached: submitted.cached };
7261
7291
  log.info(`task_id = ${taskId}`);
7262
- await mkdir4(outDir, { recursive: true });
7292
+ await mkdir5(outDir, { recursive: true });
7263
7293
  await writeFile5(join15(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE, fileId: up.fileId, source: inputAbs, formats, createdAt: new Date().toISOString() }, null, 2));
7264
7294
  log.step("④ 云端处理中(每 5s 轮询)…");
7265
7295
  const result = await pollTask(cfg, TASK_TYPE, taskId, (status, progress) => {
@@ -7285,13 +7315,141 @@ async function runOralCut(input, opts) {
7285
7315
 
7286
7316
  // src/commands/long2short.ts
7287
7317
  import { resolve as resolve6, join as join17, dirname as dirname5, basename as basename8, extname as extname5 } from "node:path";
7288
- import { mkdir as mkdir6, writeFile as writeFile7, cp as cp2 } from "node:fs/promises";
7289
- import { existsSync as existsSync15 } from "node:fs";
7318
+ import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
7319
+ import { existsSync as existsSync16 } from "node:fs";
7320
+
7321
+ // src/lib/clip-brief.ts
7322
+ var str = (v) => {
7323
+ const s = typeof v === "string" ? v.trim() : "";
7324
+ return s ? s : undefined;
7325
+ };
7326
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
7327
+ var list = (v) => Array.isArray(v) ? v.map((x) => str(x)).filter((x) => !!x) : [];
7328
+ function fmtTime(ms) {
7329
+ const total = Math.max(0, Math.round((num(ms) ?? 0) / 1000));
7330
+ const s = total % 60;
7331
+ const m = Math.floor(total / 60) % 60;
7332
+ const h = Math.floor(total / 3600);
7333
+ const mm = h ? String(m).padStart(2, "0") : String(m);
7334
+ return `${h ? `${h}:` : ""}${mm}:${String(s).padStart(2, "0")}`;
7335
+ }
7336
+ var baseName = (p) => p.split(/[\\/]/).pop() || p;
7337
+ var cell = (s) => s.replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ");
7338
+ function jumpcutText(note) {
7339
+ if (note === "jumpcut_off")
7340
+ return "未启用跳剪(`--no-jump-cut`),片段按原样保留。";
7341
+ if (note === "too_short")
7342
+ return "片段过短,跳剪跳过(保留全部内容)。";
7343
+ if (note === "degraded")
7344
+ return "跳剪降级:本条未压缩,保留全部内容。";
7345
+ return note;
7346
+ }
7347
+ function clipDurationMs(clip) {
7348
+ return num(clip.total_duration_ms) ?? num(clip.duration_ms);
7349
+ }
7350
+ function clipSegmentCount(clip) {
7351
+ return num(clip.segment_count) ?? (Array.isArray(clip.segments) ? clip.segments.length : undefined);
7352
+ }
7353
+ function sourceSpan(clip) {
7354
+ const segs = Array.isArray(clip.segments) ? clip.segments : [];
7355
+ if (segs.length) {
7356
+ const b2 = num(segs[0]?.begin_time);
7357
+ const e2 = num(segs[segs.length - 1]?.end_time);
7358
+ if (b2 != null && e2 != null)
7359
+ return [b2, e2];
7360
+ }
7361
+ const b = num(clip.begin_time);
7362
+ const e = num(clip.end_time);
7363
+ return b != null && e != null ? [b, e] : undefined;
7364
+ }
7365
+ function renderClipBrief(clip, index, files = {}) {
7366
+ const title = str(clip.title);
7367
+ const out = [`# clip${index}${title ? `「${title}」` : ""}`, ""];
7368
+ const bits = [];
7369
+ const dur = clipDurationMs(clip);
7370
+ if (dur != null)
7371
+ bits.push(`时长 ${fmtTime(dur)}`);
7372
+ const segCount = clipSegmentCount(clip);
7373
+ if (segCount != null)
7374
+ bits.push(`${segCount} 个保留片段`);
7375
+ const span = sourceSpan(clip);
7376
+ if (span)
7377
+ bits.push(`源片 ${fmtTime(span[0])}–${fmtTime(span[1])}`);
7378
+ if (bits.length)
7379
+ out.push(`- ${bits.join(" · ")}`, "");
7380
+ const score = num(clip.score);
7381
+ const reason = str(clip.score_reason);
7382
+ if (score != null || reason) {
7383
+ out.push("## 入选理由", "");
7384
+ out.push([score != null ? `**评分 ${score}**` : null, reason].filter(Boolean).join(" — "), "");
7385
+ }
7386
+ const summary = str(clip.summary);
7387
+ if (summary)
7388
+ out.push("## 简介", "", summary, "");
7389
+ const note = str(clip.jumpcut_note);
7390
+ if (note)
7391
+ out.push("## 跳剪", "", jumpcutText(note), "");
7392
+ const cats = [
7393
+ ["主题", list(clip.themes)],
7394
+ ["标签", list(clip.tags)],
7395
+ ["类型", list(clip.genres)],
7396
+ ["调性", list(clip.moods)]
7397
+ ];
7398
+ const shown = cats.filter(([, v]) => v.length);
7399
+ if (shown.length) {
7400
+ out.push("## 分类与调性", "");
7401
+ for (const [k, v] of shown)
7402
+ out.push(`- ${k}:${v.join("、")}`);
7403
+ out.push("");
7404
+ }
7405
+ const hl = Array.isArray(clip.highlight_words) ? clip.highlight_words : [];
7406
+ const hlRows = hl.map((h) => ({ text: str(h?.text), at: num(h?.begin_time) })).filter((h) => h.text);
7407
+ if (hlRows.length) {
7408
+ out.push("## 高光词(源片时码)", "");
7409
+ for (const h of hlRows)
7410
+ out.push(`- ${h.at != null ? `${fmtTime(h.at)} ` : ""}「${h.text}」`);
7411
+ out.push("");
7412
+ }
7413
+ const formats = Object.keys(files).filter((f) => files[f]?.length);
7414
+ if (formats.length) {
7415
+ out.push("## 本条工程文件", "");
7416
+ for (const f of formats)
7417
+ out.push(`- ${f}:${files[f].map(baseName).join("、")}`);
7418
+ out.push("");
7419
+ }
7420
+ return `${out.join(`
7421
+ `).replace(/\n{3,}/g, `
7422
+
7423
+ `).trimEnd()}
7424
+ `;
7425
+ }
7426
+ function renderClipsOverview(clips, ctx) {
7427
+ const name = ctx.source.split(/[\\/]/).pop() || ctx.source;
7428
+ const out = [`# ${name} · 长剪短总览`, ""];
7429
+ out.push(`- 源片:\`${ctx.source}\``);
7430
+ out.push(`- 切片:${clips.length} 条`);
7431
+ out.push(`- 跳剪:${ctx.jumpCut ? "开" : "关"}`);
7432
+ if (ctx.splitMaterials?.total)
7433
+ out.push(`- 分屏素材:${ctx.splitMaterials.landed}/${ctx.splitMaterials.total} 条已落地`);
7434
+ if (ctx.taskId)
7435
+ out.push(`- task_id:\`${ctx.taskId}\``);
7436
+ out.push("");
7437
+ out.push("| # | 标题 | 时长 | 评分 | 简介 |", "| --- | --- | --- | --- | --- |");
7438
+ for (const [i, clip] of clips.entries()) {
7439
+ const dur = clipDurationMs(clip);
7440
+ const score = num(clip.score);
7441
+ out.push(`| clip${i} | ${cell(str(clip.title) ?? "—")} | ${dur != null ? fmtTime(dur) : "—"} | ${score ?? "—"} | ${cell(str(clip.summary) ?? "—")} |`);
7442
+ }
7443
+ out.push("", "> 逐条入选理由、跳剪说明、高光词见各 `clip{i}/clip.md`。");
7444
+ return `${out.join(`
7445
+ `)}
7446
+ `;
7447
+ }
7290
7448
 
7291
7449
  // src/lib/tool-runner.ts
7292
7450
  import { resolve as resolve5, join as join16, dirname as dirname4, basename as basename7, extname as extname4 } from "node:path";
7293
- import { mkdir as mkdir5, writeFile as writeFile6, stat as stat5 } from "node:fs/promises";
7294
- import { createWriteStream, existsSync as existsSync14 } from "node:fs";
7451
+ import { mkdir as mkdir6, writeFile as writeFile6, stat as stat5 } from "node:fs/promises";
7452
+ import { createWriteStream, existsSync as existsSync15 } from "node:fs";
7295
7453
  import { Readable as Readable2 } from "node:stream";
7296
7454
  import { pipeline } from "node:stream/promises";
7297
7455
 
@@ -7375,7 +7533,8 @@ async function resolveToolPricing(priceKey, pricingContext, fetchFn = fetch) {
7375
7533
  }
7376
7534
 
7377
7535
  // src/lib/tool-descriptors.ts
7378
- import { extname as extname3 } from "node:path";
7536
+ import { readFileSync as readFileSync4 } from "node:fs";
7537
+ import { extname as extname3, resolve as resolvePath } from "node:path";
7379
7538
  var MULTI_INPUT_KINDS = new Set(["images", "videos"]);
7380
7539
  var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif", ".tif", ".tiff", ".heic", ".heif", ".avif"];
7381
7540
  var VIDEO_EXTS = [".mp4", ".mov", ".mkv", ".webm", ".avi", ".m4v", ".flv", ".wmv", ".mpg", ".mpeg", ".ts", ".m2ts"];
@@ -8191,7 +8350,7 @@ var audioStretch = {
8191
8350
  taskType: "audio_stretch",
8192
8351
  options: [
8193
8352
  { flag: "--semitones <n>", desc: "变调半音(升正降负;未传则不变调)" },
8194
- { flag: "--speed <n>", desc: "变速倍率(必须 > 0;未传则不变速)" }
8353
+ { flag: "--speed <n>", desc: "变速/语速倍率(各工具范围与缺省见各自说明)" }
8195
8354
  ],
8196
8355
  buildPayload(fileId, ctx) {
8197
8356
  const payload = { file_id: fileId };
@@ -8549,6 +8708,66 @@ var videoSplitScreen = {
8549
8708
  return url ? [{ url, filename: `${ctx.baseName}-splitscreen${extFromUrl(url, ".mp4")}` }] : [];
8550
8709
  }
8551
8710
  };
8711
+ var audioTtsClone = {
8712
+ name: "audio_tts_clone",
8713
+ title: "声音克隆配音",
8714
+ description: "输入一段文字,用指定音色合成配音音频(--text 短文本或 --text-file 文本文件二选一,≤2000 字)。按文本字数折算分钟计费。",
8715
+ kind: "cloud",
8716
+ input: { kind: "none" },
8717
+ priceKey: "audio_tts_clone",
8718
+ outputHint: "配音音频(wav/mp3)",
8719
+ enabled: true,
8720
+ taskType: "audio_tts_clone",
8721
+ options: [
8722
+ { flag: "--text <text>", desc: "待合成的短文本(与 --text-file 二选一)" },
8723
+ { flag: "--text-file <file>", desc: "待合成的文本文件(UTF-8;与 --text 二选一)" },
8724
+ { flag: "--speaker <code>", desc: "音色代号(必填;可用列表见官网文档「可用音色」,传错时报错会列出全部可用项)" },
8725
+ { flag: "--text-lang <lang>", desc: "文本语言(未传则服务端默认 zh)" },
8726
+ { flag: "--output-format <fmt>", desc: "输出音频格式 wav/mp3(未传则服务端默认 wav)" },
8727
+ { flag: "--split-method <m>", desc: "长文切分法 cut0~cut5(未传则跟随该音色调好的参数)" },
8728
+ { flag: "--batch-size <n>", desc: "分段并发批大小 1~16(未传则服务端默认)" }
8729
+ ],
8730
+ buildPayloadNone(ctx) {
8731
+ const o = ctx.opts;
8732
+ const hasText = typeof o.text === "string" && o.text.trim() !== "";
8733
+ const hasFile = typeof o.textFile === "string" && o.textFile.trim() !== "";
8734
+ if (hasText && hasFile)
8735
+ throw new Error("--text 与 --text-file 只能给其一");
8736
+ if (!hasText && !hasFile)
8737
+ throw new Error("需要 --text <短文本> 或 --text-file <文本文件>(UTF-8)之一");
8738
+ const text = hasText ? o.text : readFileSync4(resolvePath(o.textFile), "utf8");
8739
+ if (!text.trim())
8740
+ throw new Error("文本内容为空");
8741
+ if (!(typeof o.speaker === "string" && o.speaker.trim())) {
8742
+ throw new Error("audio_tts_clone 需要 --speaker <音色代号>(可用列表见官网文档;传错时服务端报错会列出可用项)");
8743
+ }
8744
+ const p = { text, speaker: o.speaker };
8745
+ if (typeof o.textLang === "string")
8746
+ p.text_lang = o.textLang;
8747
+ if (typeof o.outputFormat === "string")
8748
+ p.output_format = o.outputFormat;
8749
+ const speed = parseNumFlag(o.speed, "--speed");
8750
+ if (speed != null)
8751
+ p.speed = speed;
8752
+ if (typeof o.splitMethod === "string")
8753
+ p.text_split_method = o.splitMethod;
8754
+ const bs = parseNumFlag(o.batchSize, "--batch-size");
8755
+ if (bs != null) {
8756
+ if (!Number.isInteger(bs))
8757
+ throw new Error(`--batch-size 需要整数,拿到「${String(o.batchSize)}」`);
8758
+ p.batch_size = bs;
8759
+ }
8760
+ return p;
8761
+ },
8762
+ mapOutputs(out, ctx) {
8763
+ const url = pickUrl(out, ["download_url"]);
8764
+ if (!url)
8765
+ return [];
8766
+ const fallback = typeof ctx.opts.outputFormat === "string" ? `.${ctx.opts.outputFormat}` : ".wav";
8767
+ const speaker = typeof ctx.opts.speaker === "string" && ctx.opts.speaker.trim() ? ctx.opts.speaker : "voice";
8768
+ return [{ url, filename: `tts-${speaker}${extFromUrl(url, fallback)}` }];
8769
+ }
8770
+ };
8552
8771
  var mad = {
8553
8772
  name: "mad",
8554
8773
  title: "一键剪 MAD",
@@ -8599,6 +8818,7 @@ var TOOL_REGISTRY = [
8599
8818
  imageClassicTemplate,
8600
8819
  imageVerticalStitch,
8601
8820
  videoSplitScreen,
8821
+ audioTtsClone,
8602
8822
  mad
8603
8823
  ];
8604
8824
  function findTool(name, registry = TOOL_REGISTRY) {
@@ -8635,6 +8855,19 @@ function validateRegistry(registry = TOOL_REGISTRY) {
8635
8855
  } else if (d.buildPayloadMulti) {
8636
8856
  throw new Error(`非多文件工具不得声明 buildPayloadMulti:「${d.name}」`);
8637
8857
  }
8858
+ if (d.buildPayloadNone) {
8859
+ if (d.input.kind !== "none")
8860
+ throw new Error(`非 none 工具不得声明 buildPayloadNone:「${d.name}」`);
8861
+ if (d.buildPayload || d.buildPayloadMulti) {
8862
+ throw new Error(`buildPayloadNone 与其他 payload 钩子互斥:「${d.name}」`);
8863
+ }
8864
+ }
8865
+ if (d.kind === "cloud" && d.input.kind === "none") {
8866
+ const paths = (d.preprocess ? 1 : 0) + (d.buildPayloadNone ? 1 : 0);
8867
+ if (paths !== 1) {
8868
+ throw new Error(`none 工具须声明 preprocess 或 buildPayloadNone 恰一:「${d.name}」`);
8869
+ }
8870
+ }
8638
8871
  }
8639
8872
  }
8640
8873
 
@@ -8685,7 +8918,7 @@ function validateToolInput(descriptor, inputAbs) {
8685
8918
  return;
8686
8919
  if (!inputAbs)
8687
8920
  throw new Error(`${descriptor.name} 需要输入${spec.kind === "directory" ? "目录" : "文件"}`);
8688
- if (!existsSync14(inputAbs))
8921
+ if (!existsSync15(inputAbs))
8689
8922
  throw new Error(`输入不存在:${inputAbs}`);
8690
8923
  if (spec.kind === "directory")
8691
8924
  return;
@@ -8702,7 +8935,7 @@ function validateToolInputs(descriptor, inputAbsList) {
8702
8935
  throw new Error(`${descriptor.name} 需要至少一个输入文件(可传多个,顺序即拼装顺序)`);
8703
8936
  const exts = descriptor.input.exts ?? defaultExtsFor(descriptor.input.kind);
8704
8937
  for (const p of inputAbsList) {
8705
- if (!existsSync14(p))
8938
+ if (!existsSync15(p))
8706
8939
  throw new Error(`输入不存在:${p}`);
8707
8940
  if (exts && exts.length) {
8708
8941
  const e = extname4(p).toLowerCase();
@@ -8827,7 +9060,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
8827
9060
  const isMulti = MULTI_INPUT_KINDS.has(descriptor.input.kind);
8828
9061
  const inputList = isMulti ? (Array.isArray(inputArg) ? inputArg : inputArg != null ? [inputArg] : []).map((p) => resolve5(p)) : undefined;
8829
9062
  const inputAbs = isMulti ? inputList[0] : typeof inputArg === "string" ? resolve5(inputArg) : undefined;
8830
- const baseName = inputAbs ? basename7(inputAbs, extname4(inputAbs)) : descriptor.name;
9063
+ const baseName2 = inputAbs ? basename7(inputAbs, extname4(inputAbs)) : descriptor.name;
8831
9064
  if (isMulti) {
8832
9065
  validateToolInputs(descriptor, inputList);
8833
9066
  } else {
@@ -8839,7 +9072,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
8839
9072
  const ctx = {
8840
9073
  inputAbs,
8841
9074
  ...inputList ? { inputAbsList: inputList } : {},
8842
- baseName,
9075
+ baseName: baseName2,
8843
9076
  ffmpegPath: opts.ffmpegPath,
8844
9077
  opts,
8845
9078
  extraParams,
@@ -8849,11 +9082,13 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
8849
9082
  const outDir = resolveOutDir(descriptor, inputAbs, opts.out);
8850
9083
  if (isMulti)
8851
9084
  descriptor.buildPayloadMulti(inputList.map(() => "__dry_run__"), ctx);
9085
+ const isNoneDirect = descriptor.input.kind === "none" && !!descriptor.buildPayloadNone;
8852
9086
  let uploadPath = inputAbs;
8853
9087
  if (!isMulti && descriptor.preprocess)
8854
9088
  uploadPath = await descriptor.preprocess(ctx);
8855
- if (!uploadPath)
8856
- throw new Error(`${descriptor.name} 缺上传物(input=none 的 cloud 型工具需 preprocess 产上传物)`);
9089
+ if (!uploadPath && !isNoneDirect) {
9090
+ throw new Error(`${descriptor.name} 缺上传物(input=none 的 cloud 型工具需 preprocess 产上传物或声明 buildPayloadNone)`);
9091
+ }
8857
9092
  let billingHint;
8858
9093
  try {
8859
9094
  billingHint = (await (deps.resolvePricing ?? resolveToolPricing)(descriptor.priceKey, descriptor.pricingContext)).billingHint;
@@ -8865,7 +9100,11 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
8865
9100
  let taskId;
8866
9101
  let fileId;
8867
9102
  let fileIds;
8868
- if (isMulti) {
9103
+ if (isNoneDirect) {
9104
+ const p = descriptor.buildPayloadNone(ctx);
9105
+ mergeParams(p, extraParams);
9106
+ taskId = await deps.submitTask(deps.cfg, taskType, p);
9107
+ } else if (isMulti) {
8869
9108
  const buildMulti = (fids) => {
8870
9109
  const p = descriptor.buildPayloadMulti(fids, ctx);
8871
9110
  mergeParams(p, extraParams);
@@ -8890,7 +9129,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
8890
9129
  taskId = submitted.taskId;
8891
9130
  fileId = submitted.fileId;
8892
9131
  }
8893
- await mkdir5(outDir, { recursive: true });
9132
+ await mkdir6(outDir, { recursive: true });
8894
9133
  const fingerprint2 = inputList ? await Promise.all(inputList.map((p) => safeFingerprint(p))) : inputAbs ? await safeFingerprint(inputAbs) : undefined;
8895
9134
  await writeFile6(join16(outDir, "task.json"), JSON.stringify({
8896
9135
  tool: descriptor.name,
@@ -8996,6 +9235,34 @@ function buildLong2ShortPayload(fileId, opts, geo, inputAbs, formats, draftTarge
8996
9235
  mergeParams(p, parseExtraParams2(opts.param ?? [], opts.paramsJson));
8997
9236
  return p;
8998
9237
  }
9238
+ async function copyClipDraftsToRoot(clipDirs, draftTarget, errors) {
9239
+ const paths = [];
9240
+ let complete = 0;
9241
+ let attempted = 0;
9242
+ for (const [i, dir] of clipDirs.entries()) {
9243
+ const src = join17(dir, "jianying");
9244
+ if (!existsSync16(src)) {
9245
+ paths.push(null);
9246
+ continue;
9247
+ }
9248
+ attempted++;
9249
+ const dest = `${draftTarget}_clip${i}`;
9250
+ try {
9251
+ const landing = await copyJianyingDraft(src, dest);
9252
+ if (landing.complete) {
9253
+ complete++;
9254
+ paths.push(dest);
9255
+ } else {
9256
+ paths.push(null);
9257
+ errors[`clip${i}:jianying:draft`] = `草稿两件套不全(缺 ${landing.missing.join("、")}),剪映列表里不会显示:${dest}`;
9258
+ }
9259
+ } catch (e) {
9260
+ paths.push(null);
9261
+ errors[`clip${i}:jianying:draft`] = e instanceof Error ? e.message : String(e);
9262
+ }
9263
+ }
9264
+ return { paths, complete, attempted };
9265
+ }
8999
9266
  function registerLong2Short(program2) {
9000
9267
  program2.command("long2short <input>").description("长剪短闭环:本地抽音频/720p 代理 → 只传抽出物 → 云端选段跳剪(可选分屏)→ 逐 clip 拉回 gtrk/剪映/PR 三方工程").option("--language <code>", "源语种(必填,如 zh-CN;取值以服务端支持列表为准)").option("--split-screen", "开启智能分屏:本地改传 720p 代理,云端在代理上检测多人同框并烤 720p 分屏素材(素材落毛片旁 split_screen/)").option("--split-orientation <o>", "分屏方向 auto|lr|tb(缺省服务端 auto=按内容随机)").option("--main-topic <text>", "主题引导(影响选段偏好)").option("--duration-pref <p>", "成片时长偏好(缺省服务端 auto;成片条数由内容语义决定、不可指定)").option("--max-clip-sec <n>", "单条成片时长安全上限(秒;缺省服务端默认)").option("--no-jump-cut", "关闭跳剪(默认开:片内去水词/冗余,只删不重排)").option("--output-size <s>", "输出画布 9:16|16:9|1:1 或自定义 WxH(缺省服务端 9:16)").option("-f, --formats <list>", "三方格式(逗号分隔,云端逐 clip 直产)", "gtrk,jianying,xml").option("--jianying-draft-dir <dir>", "剪映草稿根目录;传路径或 auto(默认读 gtrk init 配置 / 自动探测)").option("-o, --out <dir>", "产物根目录(缺省 = <毛片同目录>/<毛片名>-long2short)").option("--ffmpeg-path <dir>", "指定 ffmpeg/ffprobe 所在目录(缺省 ~/.gitruck/ffmpeg → 系统 PATH)").option("--param <k=v>", "透传任意云端参数(标量、可重复)", collectParam2, []).option("--params-json <json>", `透传任意云端参数(JSON 对象、支持嵌套;如 '{"split_screen":{"prefer_mode":"..."}}')`).option("--reupload", "强制重新上传,忽略本地上传缓存").option("--no-open", "完成后不自动打开产物根目录(默认会自动打开一次)").option("--json", "机读模式:人读日志转 stderr,stdout 只输出根级结果 JSON").action(async (input, opts) => {
9001
9268
  await runLong2Short(input, opts);
@@ -9006,7 +9273,7 @@ async function runLong2Short(input, opts) {
9006
9273
  routeLogsToStderr();
9007
9274
  const cfg = loadConfig();
9008
9275
  const inputAbs = resolve6(input);
9009
- if (!existsSync15(inputAbs))
9276
+ if (!existsSync16(inputAbs))
9010
9277
  throw new Error(`毛片不存在:${inputAbs}`);
9011
9278
  const projName = basename8(inputAbs, extname5(inputAbs));
9012
9279
  const formats = opts.formats.split(",").map((s) => s.trim()).filter(Boolean);
@@ -9043,7 +9310,7 @@ async function runLong2Short(input, opts) {
9043
9310
  });
9044
9311
  const { taskId, fileId } = { taskId: submitted.taskId, fileId: submitted.fileId };
9045
9312
  log.info(`task_id = ${taskId}`);
9046
- await mkdir6(outDir, { recursive: true });
9313
+ await mkdir7(outDir, { recursive: true });
9047
9314
  await writeFile7(join17(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE2, fileId, source: inputAbs, formats, createdAt: new Date().toISOString() }, null, 2));
9048
9315
  log.step("④ 云端处理中(选段/跳剪" + (opts.splitScreen ? "/分屏" : "") + ",每 5s 轮询)…");
9049
9316
  const output = await pollToolTask(cfg, TASK_TYPE2, taskId, {
@@ -9069,7 +9336,7 @@ async function runLong2Short(input, opts) {
9069
9336
  if (!/^(?:[A-Za-z]:[\\/]|[\\/])/.test(dest))
9070
9337
  dest = join17(dirname5(inputAbs), dest);
9071
9338
  try {
9072
- await mkdir6(dirname5(dest), { recursive: true });
9339
+ await mkdir7(dirname5(dest), { recursive: true });
9073
9340
  await download(url, dest);
9074
9341
  splitLanded++;
9075
9342
  } catch (e) {
@@ -9105,20 +9372,40 @@ async function runLong2Short(input, opts) {
9105
9372
  log.warn(`clip${i} 落地失败(不连坐其余):${errors[`clip${i}`]}`);
9106
9373
  }
9107
9374
  }
9108
- if (draftRoot) {
9109
- for (const [i, cr] of clipResults.entries()) {
9110
- const src = join17(cr.dir, "jianying");
9111
- if (!existsSync15(src))
9112
- continue;
9113
- const dest = `${draftTarget}_clip${i}`;
9114
- try {
9115
- await mkdir6(dest, { recursive: true });
9116
- await cp2(src, dest, { recursive: true });
9117
- } catch (e) {
9118
- errors[`clip${i}:jianying:draft`] = e instanceof Error ? e.message : String(e);
9119
- }
9375
+ if (draftRoot && draftTarget) {
9376
+ const landing = await copyClipDraftsToRoot(clipResults.map((c3) => c3.dir), draftTarget, errors);
9377
+ landing.paths.forEach((p, i) => {
9378
+ clipResults[i].jianyingDraftPath = p;
9379
+ });
9380
+ const { complete, attempted } = landing;
9381
+ if (!attempted)
9382
+ log.warn("没有任何 clip 产出剪映草稿,草稿根未落地");
9383
+ else if (complete === attempted)
9384
+ log.info(`剪映草稿:<草稿根>/${outName}_clip{i} —— ${complete}/${attempted} 条两件套齐全(剪映里直接可见)`);
9385
+ else if (complete) {
9386
+ log.info(`剪映草稿:<草稿根>/${outName}_clip{i} —— ${complete}/${attempted} 条两件套齐全(剪映里可见)`);
9387
+ log.warn(`另有 ${attempted - complete} 条草稿两件套不全,剪映里不会显示(详见 result.json errors)`);
9388
+ } else
9389
+ log.warn(`${attempted} 条草稿两件套均不全,剪映里不会显示(详见 result.json errors)`);
9390
+ }
9391
+ for (const [i, cr] of clipResults.entries()) {
9392
+ try {
9393
+ await mkdir7(cr.dir, { recursive: true });
9394
+ await writeFile7(join17(cr.dir, "clip.md"), renderClipBrief(clips[i], i, cr.files));
9395
+ } catch (e) {
9396
+ errors[`clip${i}:brief`] = e instanceof Error ? e.message : String(e);
9120
9397
  }
9121
- log.info(`剪映草稿:<草稿根>/${outName}_clip{i}(剪映里直接可见)`);
9398
+ }
9399
+ try {
9400
+ await writeFile7(join17(outDir, "clips.md"), renderClipsOverview(clips, {
9401
+ source: inputAbs,
9402
+ jumpCut: opts.jumpCut !== false,
9403
+ splitMaterials: { landed: splitLanded, total: manifest.length },
9404
+ taskId
9405
+ }));
9406
+ log.info(`总览已生成:clips.md(逐条见 clip{i}/clip.md)`);
9407
+ } catch (e) {
9408
+ errors["clips.md"] = e instanceof Error ? e.message : String(e);
9122
9409
  }
9123
9410
  await writeFile7(join17(outDir, "report.json"), JSON.stringify(output.report ?? {}, null, 2));
9124
9411
  const ok = Object.keys(errors).length === 0 && clipResults.some((c3) => c3.ok);
@@ -9245,13 +9532,13 @@ function registerUpgrade(program2) {
9245
9532
 
9246
9533
  // src/commands/render.ts
9247
9534
  import { resolve as resolve8, dirname as dirname6, join as join19, basename as basename9, extname as extname6 } from "node:path";
9248
- import { existsSync as existsSync16 } from "node:fs";
9535
+ import { existsSync as existsSync17 } from "node:fs";
9249
9536
  function registerRender(program2) {
9250
9537
  program2.command("render <gtrk>").description("本地渲染:gtrk 工程按 EDL 用本地 ffmpeg 渲染成片 mp4(素材取原片本地路径)").option("-o, --out <file>", "输出 mp4 路径(缺省 = <gtrk 同目录>/<gtrk 名>.mp4)").option("--crf <n>", "视频质量 CRF 14-28(越小越清晰/文件越大,默认 18)").option("--codec <c>", "视频编码(默认 h264)").option("--ffmpeg-path <dir>", "指定 ffmpeg/ffprobe 所在目录(缺省 ~/.gitruck/ffmpeg → 系统)").option("--no-open", "完成后不自动打开产物目录").option("--json", "机读模式:人读日志转 stderr,stdout 只输出结果 JSON").action(async (gtrk, opts) => {
9251
9538
  if (opts.json)
9252
9539
  routeLogsToStderr();
9253
9540
  const gtrkAbs = resolve8(gtrk);
9254
- if (!existsSync16(gtrkAbs))
9541
+ if (!existsSync17(gtrkAbs))
9255
9542
  throw new Error(`gtrk 工程不存在:${gtrkAbs}`);
9256
9543
  const outMp4 = resolve8(opts.out ?? join19(dirname6(gtrkAbs), `${basename9(gtrkAbs, extname6(gtrkAbs))}.mp4`));
9257
9544
  log.step(`▶ 本地渲染:${basename9(gtrkAbs)} → ${basename9(outMp4)}`);
@@ -9278,16 +9565,16 @@ function registerRender(program2) {
9278
9565
 
9279
9566
  // src/commands/split.ts
9280
9567
  import { resolve as resolve9, join as join21, dirname as dirname8, basename as basename11 } from "node:path";
9281
- import { existsSync as existsSync17 } from "node:fs";
9282
- import { readFile as readFile5, writeFile as writeFile8, mkdir as mkdir7 } from "node:fs/promises";
9568
+ import { existsSync as existsSync18 } from "node:fs";
9569
+ import { readFile as readFile5, writeFile as writeFile8, mkdir as mkdir8 } from "node:fs/promises";
9283
9570
  import { createHash } from "node:crypto";
9284
9571
 
9285
9572
  // src/lib/gtrk-writeback.ts
9286
- import { readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "node:fs";
9573
+ import { readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "node:fs";
9287
9574
  import { dirname as dirname7, join as join20, basename as basename10 } from "node:path";
9288
9575
  import { randomBytes as randomBytes2 } from "node:crypto";
9289
9576
  function readGtrk(path) {
9290
- const raw = readFileSync4(path, "utf8");
9577
+ const raw = readFileSync5(path, "utf8");
9291
9578
  let gtrk;
9292
9579
  try {
9293
9580
  gtrk = JSON.parse(raw);
@@ -9347,7 +9634,7 @@ function registerSplit(program2) {
9347
9634
  });
9348
9635
  }
9349
9636
  function firstExisting(cands) {
9350
- return cands.find((p) => existsSync17(p));
9637
+ return cands.find((p) => existsSync18(p));
9351
9638
  }
9352
9639
  function resolvePaths(opts) {
9353
9640
  const project = opts.project ? resolve9(opts.project) : undefined;
@@ -9359,7 +9646,7 @@ function resolvePaths(opts) {
9359
9646
  } else {
9360
9647
  throw new Error("需 --project <目录> 或显式 --gtrk <path>");
9361
9648
  }
9362
- if (!existsSync17(gtrkPath))
9649
+ if (!existsSync18(gtrkPath))
9363
9650
  throw new Error(`找不到工程文件:${gtrkPath}`);
9364
9651
  let transcriptPath;
9365
9652
  if (opts.transcript)
@@ -9393,14 +9680,14 @@ async function runSplit(splitdoc, opts) {
9393
9680
  return splitdoc ? runLand(resolve9(splitdoc), baseDir, gtrkPath, transcriptPath, opts) : runView(baseDir, gtrkPath, transcriptPath, opts);
9394
9681
  }
9395
9682
  async function runView(baseDir, gtrkPath, transcriptPath, opts) {
9396
- if (!transcriptPath || !existsSync17(transcriptPath))
9683
+ if (!transcriptPath || !existsSync18(transcriptPath))
9397
9684
  throw new Error(TRANSCRIPT_MISSING);
9398
9685
  log.step("▶ 导出投影视图(transcript × 当刻 .gtrk)…");
9399
9686
  const transcript = await loadTranscript(transcriptPath);
9400
9687
  const { gtrk } = readGtrk(gtrkPath);
9401
9688
  const view = projectTranscript(transcript, gtrk, { words: opts.words });
9402
9689
  const splitDir = join21(baseDir, "split");
9403
- await mkdir7(splitDir, { recursive: true });
9690
+ await mkdir8(splitDir, { recursive: true });
9404
9691
  const viewPath = join21(splitDir, "view.json");
9405
9692
  await writeFile8(viewPath, JSON.stringify(view, null, 2));
9406
9693
  const dropped = view.utterances.filter((u) => u.dropped).length;
@@ -9419,9 +9706,9 @@ async function runView(baseDir, gtrkPath, transcriptPath, opts) {
9419
9706
  return result;
9420
9707
  }
9421
9708
  async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
9422
- if (!existsSync17(splitdocPath))
9709
+ if (!existsSync18(splitdocPath))
9423
9710
  throw new Error(`找不到拆分稿:${splitdocPath}`);
9424
- if (!transcriptPath || !existsSync17(transcriptPath))
9711
+ if (!transcriptPath || !existsSync18(transcriptPath))
9425
9712
  throw new Error(TRANSCRIPT_MISSING);
9426
9713
  log.step("▶ 校验拆分稿并落地…");
9427
9714
  const doc = JSON.parse(await readFile5(splitdocPath, "utf8"));
@@ -9459,7 +9746,7 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
9459
9746
  });
9460
9747
  writeStructMetaSplit(gtrkPath, gtrk, landing.split, mtimeMs);
9461
9748
  const splitDir = join21(baseDir, "split");
9462
- await mkdir7(splitDir, { recursive: true });
9749
+ await mkdir8(splitDir, { recursive: true });
9463
9750
  const dispatchPath = join21(splitDir, "dispatch.json");
9464
9751
  await writeFile8(dispatchPath, JSON.stringify(landing.dispatch, null, 2));
9465
9752
  let mdPath = null;
@@ -9497,8 +9784,8 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
9497
9784
 
9498
9785
  // src/commands/matrix.ts
9499
9786
  import { resolve as resolve11, join as join22, dirname as dirname9, basename as basename12 } from "node:path";
9500
- import { existsSync as existsSync19 } from "node:fs";
9501
- import { readFile as readFile6, writeFile as writeFile9, mkdir as mkdir8, rename } from "node:fs/promises";
9787
+ import { existsSync as existsSync20 } from "node:fs";
9788
+ import { readFile as readFile6, writeFile as writeFile9, mkdir as mkdir9, rename } from "node:fs/promises";
9502
9789
 
9503
9790
  // src/lib/solid-png.ts
9504
9791
  import { deflateSync } from "node:zlib";
@@ -10234,7 +10521,7 @@ function layBrollTracks(opts) {
10234
10521
  }
10235
10522
 
10236
10523
  // src/lib/material-integrity.ts
10237
- import { existsSync as existsSync18 } from "node:fs";
10524
+ import { existsSync as existsSync19 } from "node:fs";
10238
10525
  import { resolve as resolve10 } from "node:path";
10239
10526
  var INTEGRITY_LIST_CAP = 10;
10240
10527
  var r35 = (n) => Math.round(n * 1000) / 1000;
@@ -10278,8 +10565,8 @@ function collectMaterialRefs(gtrk) {
10278
10565
  const id = c3[key];
10279
10566
  if (typeof id !== "string" || id === "")
10280
10567
  continue;
10281
- const list = out.get(id) ?? [];
10282
- list.push({
10568
+ const list2 = out.get(id) ?? [];
10569
+ list2.push({
10283
10570
  track: group,
10284
10571
  track_index: trackIndex,
10285
10572
  clip_id: typeof c3.clip_id === "string" ? c3.clip_id : null,
@@ -10287,7 +10574,7 @@ function collectMaterialRefs(gtrk) {
10287
10574
  track_ed: clipTrackEd(c3),
10288
10575
  key
10289
10576
  });
10290
- out.set(id, list);
10577
+ out.set(id, list2);
10291
10578
  }
10292
10579
  }
10293
10580
  }
@@ -10295,7 +10582,7 @@ function collectMaterialRefs(gtrk) {
10295
10582
  return out;
10296
10583
  }
10297
10584
  function checkMaterialIntegrity(opts) {
10298
- const exists = opts.exists ?? ((p) => existsSync18(p));
10585
+ const exists = opts.exists ?? ((p) => existsSync19(p));
10299
10586
  const materials = Array.isArray(opts.gtrk.materials) ? opts.gtrk.materials : [];
10300
10587
  const refs = collectMaterialRefs(opts.gtrk);
10301
10588
  const counts = { relative: 0, absolute: 0, remote: 0, noPath: 0 };
@@ -10329,15 +10616,15 @@ function checkMaterialIntegrity(opts) {
10329
10616
  }
10330
10617
  if (present)
10331
10618
  continue;
10332
- const list = refs.get(id) ?? [];
10619
+ const list2 = refs.get(id) ?? [];
10333
10620
  const entry = {
10334
10621
  id,
10335
10622
  path,
10336
10623
  kind,
10337
10624
  resolved,
10338
- referenced: list.length > 0,
10339
- refCount: list.length,
10340
- refs: list
10625
+ referenced: list2.length > 0,
10626
+ refCount: list2.length,
10627
+ refs: list2
10341
10628
  };
10342
10629
  (kind === "relative" ? dangling : external).push(entry);
10343
10630
  }
@@ -10486,9 +10773,9 @@ function dedupeBeatQueries(queries) {
10486
10773
  const best = bestByClip.get(r.clip_id);
10487
10774
  if (best.qi === qi && best.r === r)
10488
10775
  return true;
10489
- const list = best.r.also_matched_queries ??= [];
10490
- if (!list.includes(q.query))
10491
- list.push(q.query);
10776
+ const list2 = best.r.also_matched_queries ??= [];
10777
+ if (!list2.includes(q.query))
10778
+ list2.push(q.query);
10492
10779
  return false;
10493
10780
  });
10494
10781
  }
@@ -10645,7 +10932,7 @@ async function runPlanMode(cfg, tier, broll, overrides, columnId, opts) {
10645
10932
  } else {
10646
10933
  throw new Error('需 --project <目录> 或显式 --dispatch <path>(ad-hoc 检索用:gtrk matrix search "<query>")');
10647
10934
  }
10648
- if (!existsSync19(dispatchPath))
10935
+ if (!existsSync20(dispatchPath))
10649
10936
  throw new Error(`找不到派单清单:${dispatchPath}(先跑 gtrk split <拆分稿> 落地派单)`);
10650
10937
  const dispatch = JSON.parse(await readFile6(dispatchPath, "utf8"));
10651
10938
  const rawQueue = Array.isArray(dispatch.film_broll) ? dispatch.film_broll : [];
@@ -10711,7 +10998,7 @@ async function runPlanMode(cfg, tier, broll, overrides, columnId, opts) {
10711
10998
  beats
10712
10999
  });
10713
11000
  const splitDir = join22(baseDir, "split");
10714
- await mkdir8(splitDir, { recursive: true });
11001
+ await mkdir9(splitDir, { recursive: true });
10715
11002
  const planPath = join22(splitDir, "broll-plan.json");
10716
11003
  await writeFile9(planPath, JSON.stringify(plan, null, 2));
10717
11004
  log.ok(`候选清单已生成:${planPath}(${beats.length} beat · ${okCount}/${totalQueries} query 成功 · ${resultCount} 条候选)`);
@@ -10761,7 +11048,7 @@ function parseScoreFloor(raw) {
10761
11048
  }
10762
11049
  function locateGtrk(baseDir) {
10763
11050
  const cands = [join22(baseDir, "gtrk", "project.gtrk"), join22(baseDir, "project.gtrk")];
10764
- return cands.find((p) => existsSync19(p));
11051
+ return cands.find((p) => existsSync20(p));
10765
11052
  }
10766
11053
  async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRelay, reproj) {
10767
11054
  const gtrkPath = locateGtrk(baseDir);
@@ -10776,7 +11063,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10776
11063
  log.step(`▶ 候选铺轨(${layN} 轨 · 平铺 ${slotCount} 槽位 · ${clipIds.size} 个 clip,preview 代理)…`);
10777
11064
  const gtrkDir = dirname9(gtrkPath);
10778
11065
  const previewDir = join22(gtrkDir, ...BROLL_PREVIEW_DIR.split("/"));
10779
- await mkdir8(previewDir, { recursive: true });
11066
+ await mkdir9(previewDir, { recursive: true });
10780
11067
  const prevSource = new Map;
10781
11068
  const prevBroll = gtrk.struct_meta?.broll;
10782
11069
  for (const b of prevBroll?.beats ?? []) {
@@ -10799,7 +11086,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10799
11086
  continue;
10800
11087
  const rel = `${BROLL_PREVIEW_DIR}/${clipId}.mp4`;
10801
11088
  const abs = join22(gtrkDir, ...rel.split("/"));
10802
- if (existsSync19(abs)) {
11089
+ if (existsSync20(abs)) {
10803
11090
  const prev = prevSource.get(clipId);
10804
11091
  if (prev !== "raw") {
10805
11092
  downloads.set(clipId, { rel, source: prev ?? "preview" });
@@ -10837,8 +11124,8 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10837
11124
  forceRelay
10838
11125
  });
10839
11126
  if (summary.refused) {
10840
- const list = summary.keptEditedTracks;
10841
- log.err(`拒绝铺轨:${list.length} 条候选轨已被你在客户端编辑过(track_index ${list.join("/") || "-"})——` + "本次不剥它们、也不铺新轨,工程文件零改动。");
11127
+ const list2 = summary.keptEditedTracks;
11128
+ log.err(`拒绝铺轨:${list2.length} 条候选轨已被你在客户端编辑过(track_index ${list2.join("/") || "-"})——` + "本次不剥它们、也不铺新轨,工程文件零改动。");
10842
11129
  for (const w of warnings)
10843
11130
  log.warn(w);
10844
11131
  log.warn("下一步二选一:① 在客户端处置那条轨(删掉 / 移走 / 改用别的轨)后重跑本命令;" + "② 确知要丢弃那条轨上的编辑 → 加 --force-relay 强制剥离重铺" + "(会删掉已确认原片的 broll-raw-* 素材登记,盘上原片文件成孤儿,不可恢复)。");
@@ -10846,7 +11133,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10846
11133
  return {
10847
11134
  lay: {
10848
11135
  refused: true,
10849
- keptEditedTracks: list,
11136
+ keptEditedTracks: list2,
10850
11137
  laidTracks: [],
10851
11138
  laidClips: 0,
10852
11139
  removedTracks: [],
@@ -10863,8 +11150,8 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10863
11150
  const rel = solidRelPath(spec);
10864
11151
  const abs = join22(gtrkDir, ...rel.split("/"));
10865
11152
  try {
10866
- if (!existsSync19(abs)) {
10867
- await mkdir8(dirname9(abs), { recursive: true });
11153
+ if (!existsSync20(abs)) {
11154
+ await mkdir9(dirname9(abs), { recursive: true });
10868
11155
  const tmp = `${abs}.tmp-${process.pid}`;
10869
11156
  await writeFile9(tmp, encodeSolidPng(spec));
10870
11157
  await rename(tmp, abs);
@@ -10980,8 +11267,8 @@ function slugify3(name) {
10980
11267
 
10981
11268
  // src/commands/mg.ts
10982
11269
  import { resolve as resolve12, join as join23, dirname as dirname10, basename as basename13 } from "node:path";
10983
- import { existsSync as existsSync20 } from "node:fs";
10984
- import { readFile as readFile7, mkdir as mkdir9, copyFile } from "node:fs/promises";
11270
+ import { existsSync as existsSync21 } from "node:fs";
11271
+ import { readFile as readFile7, mkdir as mkdir10, copyFile } from "node:fs/promises";
10985
11272
 
10986
11273
  // src/lib/mg-lint.ts
10987
11274
  var CDN_OK = [/lib\.baomitu\.com/i, /cdnjs\.cloudflare\.com/i, /unpkg\.com/i];
@@ -11845,9 +12132,9 @@ function detectPrimitiveLoops(html) {
11845
12132
  }
11846
12133
  const callsByFunction = new Map;
11847
12134
  const pushCall = (fn, at) => {
11848
- const list = callsByFunction.get(fn.declStart) ?? [];
11849
- list.push({ at, loops: executionLoopsAt(at) });
11850
- callsByFunction.set(fn.declStart, list);
12135
+ const list2 = callsByFunction.get(fn.declStart) ?? [];
12136
+ list2.push({ at, loops: executionLoopsAt(at) });
12137
+ callsByFunction.set(fn.declStart, list2);
11851
12138
  };
11852
12139
  const functionNames = new Set(functions.map((fn) => fn.name).filter((name) => name !== null));
11853
12140
  for (const call of code.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
@@ -12048,21 +12335,27 @@ function filterDecls(styleDecls) {
12048
12335
  out.push(m[1].trim());
12049
12336
  return out;
12050
12337
  }
12051
- function styleBlockOwnDecls(html) {
12338
+ function styleBlockRules(html) {
12052
12339
  const out = [];
12053
12340
  for (const sm of html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)) {
12054
12341
  const css = sm[1].replace(/\/\*[\s\S]*?\*\//g, " ");
12055
12342
  const stack = [];
12343
+ const selStack = [];
12344
+ let segStart = 0;
12056
12345
  for (let i = 0;i < css.length; i++) {
12057
12346
  const c3 = css[i];
12058
12347
  if (c3 === '"' || c3 === "'") {
12059
12348
  i = skipString(css, i);
12060
12349
  continue;
12061
12350
  }
12062
- if (c3 === "{")
12351
+ if (c3 === "{") {
12063
12352
  stack.push(i);
12064
- else if (c3 === "}") {
12353
+ selStack.push(css.slice(segStart, i));
12354
+ segStart = i + 1;
12355
+ } else if (c3 === "}") {
12065
12356
  const open2 = stack.pop();
12357
+ const sel = selStack.pop() ?? "";
12358
+ segStart = i + 1;
12066
12359
  if (open2 === undefined)
12067
12360
  continue;
12068
12361
  let own = css.slice(open2 + 1, i);
@@ -12070,8 +12363,9 @@ function styleBlockOwnDecls(html) {
12070
12363
  prev = own;
12071
12364
  own = own.replace(/\{[^{}]*\}/g, " ");
12072
12365
  }
12073
- out.push(own);
12074
- }
12366
+ out.push({ selector: sel.trim(), own });
12367
+ } else if (c3 === ";")
12368
+ segStart = i + 1;
12075
12369
  }
12076
12370
  }
12077
12371
  return out;
@@ -12131,21 +12425,138 @@ function tweenVarObjects(js) {
12131
12425
  }
12132
12426
  return out;
12133
12427
  }
12428
+ var GSAP_TRANSFORM_KEY = /^(?:transform|x|y|z|xPercent|yPercent|scale|scaleX|scaleY|scaleZ|rotate|rotation|rotationX|rotationY|rotationZ|skew|skewX|skewY|translateX|translateY|translateZ)$/;
12429
+ var TWEENING_METHOD = /^(?:to|from|fromTo)$/;
12430
+ function selectorSubjectKeys(selector) {
12431
+ const keys = [];
12432
+ for (const alt of selector.split(",")) {
12433
+ const subject = alt.trim().split(/[\s>+~]+/).filter(Boolean).pop();
12434
+ if (!subject)
12435
+ continue;
12436
+ const core = subject.split(/[:\[]/)[0];
12437
+ for (const m of core.matchAll(/([.#])([A-Za-z_][\w-]*)/g))
12438
+ keys.push(`${m[1] === "#" ? "id" : "class"}:${m[2]}`);
12439
+ }
12440
+ return keys;
12441
+ }
12442
+ function tagIdentityKeys(tag) {
12443
+ const keys = [];
12444
+ const id = attr(tag, "id");
12445
+ if (id)
12446
+ keys.push(`id:${id.trim()}`);
12447
+ for (const cls of (attr(tag, "class") ?? "").trim().split(/\s+/))
12448
+ if (cls)
12449
+ keys.push(`class:${cls}`);
12450
+ return keys;
12451
+ }
12452
+ function tweenCalls(js) {
12453
+ const out = [];
12454
+ const re = /\.\s*(to|from|fromTo|set)\s*\(/g;
12455
+ let m;
12456
+ while (m = re.exec(js)) {
12457
+ const open2 = m.index + m[0].length - 1;
12458
+ const args = parenArg(js, open2);
12459
+ re.lastIndex = open2 + 1;
12460
+ if (args === null)
12461
+ continue;
12462
+ const target = readObjValue(args, 0);
12463
+ const bodies = [];
12464
+ for (let i = 0;i < args.length; i++) {
12465
+ const c3 = args[i];
12466
+ if (c3 === '"' || c3 === "'" || c3 === "`") {
12467
+ i = skipString(args, i);
12468
+ continue;
12469
+ }
12470
+ if (c3 !== "{")
12471
+ continue;
12472
+ const body = braceBlock(args, i);
12473
+ bodies.push(body);
12474
+ i += body.length + 1;
12475
+ }
12476
+ out.push({ method: m[1], target, bodies });
12477
+ }
12478
+ return out;
12479
+ }
12480
+ function selectorBindings(js) {
12481
+ const out = new Map;
12482
+ const re = /([A-Za-z_$][\w$]*)\s*=\s*(?:[A-Za-z_$][\w$]*\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\(\s*(["'])(.*?)\3\s*\)/g;
12483
+ for (const m of js.matchAll(re)) {
12484
+ const [, name, fn, , raw] = m;
12485
+ const keys = fn === "getElementById" ? [`id:${raw.trim()}`] : /^\s*[.#]/.test(raw) ? selectorSubjectKeys(raw) : [];
12486
+ if (!keys.length)
12487
+ continue;
12488
+ const prev = out.get(name) ?? [];
12489
+ out.set(name, [...new Set([...prev, ...keys])]);
12490
+ }
12491
+ return out;
12492
+ }
12493
+ function tweenTargetKeys(target, bindings) {
12494
+ const t = target.trim();
12495
+ const lit = stringLiteral(t);
12496
+ if (lit !== null)
12497
+ return /^\s*[.#]/.test(lit) ? selectorSubjectKeys(lit) : [];
12498
+ if (/^[A-Za-z_$][\w$]*$/.test(t))
12499
+ return bindings.get(t) ?? [];
12500
+ return [];
12501
+ }
12502
+ function topLevelObjKeys(body) {
12503
+ const keys = [];
12504
+ let depth = 0;
12505
+ let expectKey = true;
12506
+ for (let i = 0;i < body.length; i++) {
12507
+ const c3 = body[i];
12508
+ if (c3 === '"' || c3 === "'" || c3 === "`") {
12509
+ if (depth === 0 && expectKey) {
12510
+ const end = skipString(body, i);
12511
+ const name = body.slice(i + 1, end);
12512
+ const rest = body.slice(end + 1);
12513
+ if (/^\s*:/.test(rest)) {
12514
+ keys.push(name);
12515
+ expectKey = false;
12516
+ }
12517
+ }
12518
+ i = skipString(body, i);
12519
+ continue;
12520
+ }
12521
+ if (c3 === "(" || c3 === "[" || c3 === "{")
12522
+ depth++;
12523
+ else if (c3 === ")" || c3 === "]" || c3 === "}")
12524
+ depth = Math.max(0, depth - 1);
12525
+ else if (c3 === "," && depth === 0)
12526
+ expectKey = true;
12527
+ else if (depth === 0 && expectKey && /[A-Za-z_$]/.test(c3)) {
12528
+ const m = /^([A-Za-z_$][\w$]*)\s*:/.exec(body.slice(i));
12529
+ if (m) {
12530
+ keys.push(m[1]);
12531
+ expectKey = false;
12532
+ i += m[1].length;
12533
+ }
12534
+ }
12535
+ }
12536
+ return keys;
12537
+ }
12134
12538
  function detectFilterCost(html) {
12135
12539
  const src = maskHtmlComments(html);
12136
12540
  const svgConvIds = svgConvolutionFilterIds(src);
12137
12541
  const animated = [];
12138
12542
  const staticFullBleed = [];
12543
+ const staticTransformed = [];
12139
12544
  const indeterminate = [];
12140
12545
  const add = (bucket, site) => {
12141
12546
  const s = site.replace(/\s+/g, " ").trim().slice(0, 120);
12142
12547
  if (!bucket.includes(s))
12143
12548
  bucket.push(s);
12144
12549
  };
12145
- for (const own of styleBlockOwnDecls(src)) {
12550
+ const staticFilterSites = new Map;
12551
+ for (const { selector, own } of styleBlockRules(src)) {
12146
12552
  const hit = filterDecls(own).filter((val) => filterValueConvolves(val, svgConvIds));
12147
- if (hit.length && isFullBleedStyle(own))
12553
+ if (!hit.length)
12554
+ continue;
12555
+ if (isFullBleedStyle(own))
12148
12556
  add(staticFullBleed, `<style> 规则:filter:${hit[0]}`);
12557
+ for (const key of selectorSubjectKeys(selector))
12558
+ if (!staticFilterSites.has(key))
12559
+ staticFilterSites.set(key, `<style> 规则 ${selector.slice(0, 60)}:filter:${hit[0]}`);
12149
12560
  }
12150
12561
  for (const m of src.matchAll(/<[a-zA-Z][^>]*>/g)) {
12151
12562
  const tag = m[0];
@@ -12155,9 +12566,13 @@ function detectFilterCost(html) {
12155
12566
  const byAttr = refAttr !== undefined && filterValueConvolves(refAttr, svgConvIds) ? refAttr : null;
12156
12567
  if (!inline.length && byAttr === null)
12157
12568
  continue;
12569
+ const site = inline.length ? `内联 style:filter:${inline[0]}` : `SVG 属性:filter="${byAttr}"`;
12570
+ for (const key of tagIdentityKeys(tag))
12571
+ if (!staticFilterSites.has(key))
12572
+ staticFilterSites.set(key, site);
12158
12573
  if (!isFullBleedStyle(style))
12159
12574
  continue;
12160
- add(staticFullBleed, inline.length ? `内联 style:filter:${inline[0]}` : `SVG 属性:filter="${byAttr}"`);
12575
+ add(staticFullBleed, site);
12161
12576
  }
12162
12577
  const js = maskJsComments(scriptBodiesOnly(src));
12163
12578
  for (const body of tweenVarObjects(js)) {
@@ -12172,7 +12587,22 @@ function detectFilterCost(html) {
12172
12587
  if (svgConvIds.size && /(?:^|[{,])\s*["']?stdDeviation["']?\s*:/.test(body))
12173
12588
  add(animated, "补间驱动 SVG 滤镜基元的 stdDeviation");
12174
12589
  }
12175
- return { animated, staticFullBleed, indeterminate };
12590
+ if (staticFilterSites.size) {
12591
+ const bindings = selectorBindings(js);
12592
+ for (const { method, target, bodies } of tweenCalls(js)) {
12593
+ if (!TWEENING_METHOD.test(method))
12594
+ continue;
12595
+ const driven = bodies.some((b) => topLevelObjKeys(b).some((k) => GSAP_TRANSFORM_KEY.test(k)));
12596
+ if (!driven)
12597
+ continue;
12598
+ for (const key of tweenTargetKeys(target, bindings)) {
12599
+ const site = staticFilterSites.get(key);
12600
+ if (site)
12601
+ add(staticTransformed, `${site} —— 而 \`.${method}(${target.trim().slice(0, 30)}, …)\` 驱动了它的 transform`);
12602
+ }
12603
+ }
12604
+ }
12605
+ return { animated, staticFullBleed, staticTransformed, indeterminate };
12176
12606
  }
12177
12607
  function lintParticle(html, opts = {}) {
12178
12608
  const v = [];
@@ -12278,6 +12708,8 @@ function lintParticle(html, opts = {}) {
12278
12708
  push("c-filter-animated", false, `时间线补间直接驱动了真卷积滤镜(${cost.animated.slice(0, 3).join(";")})——` + "blur / drop-shadow / feGaussianBlur 是**真卷积**,每帧成本 ∝「被覆盖面积 × 半径 × 帧数」;" + "一旦被补间驱动,卷积结果**每帧失效**、缓存彻底失灵,这是本组三项里成本机制最重的一项。" + "改法:用 `opacity` / `transform`(`scale` / 位移)表达同一叙事动作;" + "确需滤镜就做成**静态两态切换**(滤镜值不随时间连续变),并把被滤元素的几何覆盖面积收到真正需要的那块矩形上。" + "本项是**成本提示**,不影响 ok / 退出码 / 铺轨;**未报 ≠ 这颗便宜**——真判据是真渲染出片计时");
12279
12709
  if (cost.staticFullBleed.length)
12280
12710
  push("c-filter-static-fullbleed", false, `整幅静态真卷积滤镜(${cost.staticFullBleed.slice(0, 3).join(";")})——该声明自身即全幅` + "(`position:absolute|fixed` + `inset:0` 或等价铺满),于是每帧都要对整幅做一次卷积。" + "**首选改法:缩小被滤镜覆盖的几何面积**(把滤镜收到它真正需要的那块矩形上——面积一项同时压掉卷积与整幅逐帧合成两笔成本);" + "次选把静态滤镜结果**预烘成图**,且 MUST 用 **RGBA PNG**(透明叠加颗粒的 alpha 是成片合成的必需通道," + "烘成 JPEG 或任何无 alpha 格式会在成片里塌成不透明色块),但预烘只消得掉卷积、消不掉整幅逐帧合成,收益有上限、必有残差。" + "去不去滤镜属审美取值,判断权在作者:本项只给信息与改法,**恒非致命**、不拦铺轨;未报 ≠ 便宜,真判据是真渲染出片计时");
12711
+ if (cost.staticTransformed.length)
12712
+ push("c-filter-static-transformed", false, `静态真卷积滤镜的元素,其 transform 被补间驱动(${cost.staticTransformed.slice(0, 3).join(";")})——` + "滤镜值本身不变,但被滤子树**每帧换一个几何**,卷积结果每帧失效、必须重算," + "与 `c-filter-animated` 是**同一类缓存失效**,只是驱动源从滤镜值换成了 transform。" + "r69 真渲实测:同颗粒对照下这一档 **+42.7%**,比「补间驱动 blur」的 +36.2% **还贵**——它是本组里最容易被忽略的一档。" + "改法(按优先级):① 把滤镜移到**不参与位移/缩放**的那一层(滤镜层与运动层拆开,运动交给外层容器);" + "② 缩小被滤元素的几何覆盖面积;③ 把静态滤镜结果**预烘成 RGBA PNG** 再让它随 transform 走(预烘图平移是零卷积)。" + "⚠️ 射程只收 `transform`:`opacity` 被补间驱动**不在本项射程**(opacity 侧尚无对照实测,不凭推测扩)。" + "本项恒非致命、不拦铺轨;未报 ≠ 便宜,真判据是真渲染出片计时");
12281
12713
  if (cost.indeterminate.length)
12282
12714
  push("c-filter-indeterminate", false, `该处 filter 值无法静态判定是否含真卷积(${cost.indeterminate.slice(0, 3).join(";")})——` + "补间值不是字符串字面量(变量 / 模板串 / 函数返回),本 lint 不做表达式求值与常量折叠," + "故对该处**未作判定**:既不是「判过且通过」,也不是命中。请人工确认它是否会驱动 blur / drop-shadow;" + "若是,按 `c-filter-animated` 的改法处理。本项恒非致命、不拦铺轨");
12283
12715
  return { ok: !v.some((x) => x.fatal), violations: v, opaque, compositionId: cid };
@@ -12506,18 +12938,18 @@ function resolveDispatch(opts) {
12506
12938
  throw new Error("需 --project <目录> 或显式 --dispatch <path>");
12507
12939
  }
12508
12940
  function locateGtrk2(baseDir) {
12509
- return [join23(baseDir, "gtrk", "project.gtrk"), join23(baseDir, "project.gtrk")].find((p) => existsSync20(p));
12941
+ return [join23(baseDir, "gtrk", "project.gtrk"), join23(baseDir, "project.gtrk")].find((p) => existsSync21(p));
12510
12942
  }
12511
12943
  function locateSrcHtml(baseDir, compositionId) {
12512
12944
  for (const d of MG_SRC_DIRS) {
12513
12945
  const p = join23(baseDir, d, `${compositionId}.html`);
12514
- if (existsSync20(p))
12946
+ if (existsSync21(p))
12515
12947
  return p;
12516
12948
  }
12517
12949
  return;
12518
12950
  }
12519
12951
  async function readMgQueue(dispatchPath) {
12520
- if (!existsSync20(dispatchPath))
12952
+ if (!existsSync21(dispatchPath))
12521
12953
  throw new Error(`找不到派单清单:${dispatchPath}(先跑 gtrk split 落地派单)`);
12522
12954
  const dispatch = JSON.parse(await readFile7(dispatchPath, "utf8"));
12523
12955
  const queue = dispatch.mg ?? dispatch.rrv_mg;
@@ -12700,7 +13132,7 @@ async function runLay(opts) {
12700
13132
  if (opts.replaceAll && orphans.length > 0) {
12701
13133
  log.warn(`--replace-all:已显式授权重置整轨,轨上其余 ${orphans.length} 颗已铺颗粒将被剥离(不走增量保留)。`);
12702
13134
  }
12703
- await mkdir9(join23(gtrkDir, ...MG_ASSET_DIR.split("/")), { recursive: true });
13135
+ await mkdir10(join23(gtrkDir, ...MG_ASSET_DIR.split("/")), { recursive: true });
12704
13136
  for (const it of items) {
12705
13137
  await copyFile(srcByComp.get(it.composition_id), join23(gtrkDir, ...it.html_rel.split("/")));
12706
13138
  }
@@ -12751,7 +13183,7 @@ async function runLint(args, opts) {
12751
13183
  let dispatchIds;
12752
13184
  let slotDuration;
12753
13185
  let compositionId;
12754
- if (opts.dispatch && existsSync20(resolve12(opts.dispatch))) {
13186
+ if (opts.dispatch && existsSync21(resolve12(opts.dispatch))) {
12755
13187
  const queue = await readMgQueue(resolve12(opts.dispatch));
12756
13188
  dispatchIds = queue.map((q) => q.composition_id);
12757
13189
  const byName = queue.find((q) => q.composition_id === nameId);
@@ -12817,12 +13249,12 @@ function done(opts, result) {
12817
13249
  }
12818
13250
 
12819
13251
  // src/lib/mad/mad.ts
12820
- import { mkdir as mkdir12, writeFile as writeFile11 } from "node:fs/promises";
12821
- import { existsSync as existsSync23, statSync as statSync3 } from "node:fs";
13252
+ import { mkdir as mkdir13, writeFile as writeFile11 } from "node:fs/promises";
13253
+ import { existsSync as existsSync24, statSync as statSync3 } from "node:fs";
12822
13254
  import { resolve as resolve13, join as join27 } from "node:path";
12823
13255
 
12824
13256
  // src/lib/convert/types.ts
12825
- function num(v, d = 0) {
13257
+ function num2(v, d = 0) {
12826
13258
  return typeof v === "number" && isFinite(v) ? v : d;
12827
13259
  }
12828
13260
  function parseCubicBezier(e) {
@@ -12871,19 +13303,19 @@ function mergeChannelTracks(ta, tb, defA, defB) {
12871
13303
  if (!track || track.length === 0)
12872
13304
  return dflt;
12873
13305
  if (t <= track[0].t)
12874
- return num(track[0].v, dflt);
13306
+ return num2(track[0].v, dflt);
12875
13307
  for (let i = 1;i < track.length; i++) {
12876
13308
  if (t <= track[i].t) {
12877
13309
  const p = track[i - 1];
12878
13310
  const q = track[i];
12879
13311
  const span = q.t - p.t;
12880
13312
  if (span <= 0)
12881
- return num(q.v, dflt);
13313
+ return num2(q.v, dflt);
12882
13314
  const k = (t - p.t) / span;
12883
- return num(p.v, dflt) + (num(q.v, dflt) - num(p.v, dflt)) * k;
13315
+ return num2(p.v, dflt) + (num2(q.v, dflt) - num2(p.v, dflt)) * k;
12884
13316
  }
12885
13317
  }
12886
- return num(track[track.length - 1].v, dflt);
13318
+ return num2(track[track.length - 1].v, dflt);
12887
13319
  };
12888
13320
  const times = new Set;
12889
13321
  for (const k of ta ?? [])
@@ -12904,17 +13336,17 @@ function sampleTrack(track, t, def) {
12904
13336
  if (!track || track.length === 0)
12905
13337
  return def;
12906
13338
  if (t <= track[0].t)
12907
- return num(track[0].v, def);
13339
+ return num2(track[0].v, def);
12908
13340
  for (let i = 1;i < track.length; i++) {
12909
13341
  const a = track[i - 1];
12910
13342
  const b = track[i];
12911
13343
  if (t <= b.t) {
12912
13344
  const span = Math.max(b.t - a.t, 0.000001);
12913
13345
  const k = (t - a.t) / span;
12914
- return num(a.v, def) + (num(b.v, def) - num(a.v, def)) * k;
13346
+ return num2(a.v, def) + (num2(b.v, def) - num2(a.v, def)) * k;
12915
13347
  }
12916
13348
  }
12917
- return num(track[track.length - 1].v, def);
13349
+ return num2(track[track.length - 1].v, def);
12918
13350
  }
12919
13351
  function phasePair(i) {
12920
13352
  return [Math.sin(i * 2.399), Math.cos(i * 3.11)];
@@ -12942,13 +13374,13 @@ function baseRot(anim, t) {
12942
13374
  return sampleTrack(anim.rot, t, 0);
12943
13375
  }
12944
13376
  function opWindow(fx, ly) {
12945
- const t0 = num(fx.t0, num(ly.in, 0));
12946
- const t1 = num(fx.t1, num(ly.out, t0 + 1));
13377
+ const t0 = num2(fx.t0, num2(ly.in, 0));
13378
+ const t1 = num2(fx.t1, num2(ly.out, t0 + 1));
12947
13379
  return [t0, t1];
12948
13380
  }
12949
13381
  function bakeLayerOps(ly) {
12950
13382
  const anim = ly.anim ?? {};
12951
- const pos = [num(ly.pos?.[0], 0), num(ly.pos?.[1], 0)];
13383
+ const pos = [num2(ly.pos?.[0], 0), num2(ly.pos?.[1], 0)];
12952
13384
  const tracks = [];
12953
13385
  const warnings = [];
12954
13386
  for (const fx of ly.fx ?? []) {
@@ -12964,8 +13396,8 @@ function bakeLayerOps(ly) {
12964
13396
  const times = [];
12965
13397
  const values = [];
12966
13398
  for (const k of tr) {
12967
- const t = num(k.t, 0);
12968
- const v = num(k.v, 1);
13399
+ const t = num2(k.t, 0);
13400
+ const v = num2(k.v, 1);
12969
13401
  const [bx, by] = baseScale(anim, t);
12970
13402
  times.push(round(t, 3));
12971
13403
  values.push([round(bx * v * 100, 3), round(by * v * 100, 3)]);
@@ -12978,9 +13410,9 @@ function bakeLayerOps(ly) {
12978
13410
  if (op === "flicker") {
12979
13411
  if (t1 - t0 < 0.05)
12980
13412
  continue;
12981
- let freq = num(fx.freq, 8);
13413
+ let freq = num2(fx.freq, 8);
12982
13414
  freq = clamp(freq >= 0.5 ? freq : freq * 30, 0.5, 20);
12983
- const mag = clamp(num(fx.mag, 0.6), 0.05, 1);
13415
+ const mag = clamp(num2(fx.mag, 0.6), 0.05, 1);
12984
13416
  const hi = 1 + mag * 0.8;
12985
13417
  const lo = Math.max(0.3, 1 - mag * 0.6);
12986
13418
  const n = Math.min(dn(Math.max(2, Math.floor((t1 - t0) * freq * 2))), 160);
@@ -12999,8 +13431,8 @@ function bakeLayerOps(ly) {
12999
13431
  if (t1 - t0 < 0.05)
13000
13432
  continue;
13001
13433
  if (op === "shake") {
13002
- const amp0 = num(fx.mag ?? fx.amp, 18);
13003
- const freq = clamp(num(fx.freq, 20), 1, 40);
13434
+ const amp0 = num2(fx.mag ?? fx.amp, 18);
13435
+ const freq = clamp(num2(fx.freq, 20), 1, 40);
13004
13436
  const n = Math.min(dn(Math.max(2, Math.floor((t1 - t0) * freq))), 160);
13005
13437
  const tr = fx.tracks?.mag;
13006
13438
  const seqX = [];
@@ -13030,9 +13462,9 @@ function bakeLayerOps(ly) {
13030
13462
  }
13031
13463
  if (op === "oscillate") {
13032
13464
  if (fx.rot === true) {
13033
- const a1 = num(fx.a1, -3);
13034
- const a2 = num(fx.a2, 3);
13035
- const freq = clamp(num(fx.freq, 2), 0.2, 20);
13465
+ const a1 = num2(fx.a1, -3);
13466
+ const a2 = num2(fx.a2, 3);
13467
+ const freq = clamp(num2(fx.freq, 2), 0.2, 20);
13036
13468
  const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 120);
13037
13469
  const times = [round(t0, 3)];
13038
13470
  const values = [[round(baseRot(anim, t0), 3)]];
@@ -13049,9 +13481,9 @@ function bakeLayerOps(ly) {
13049
13481
  }
13050
13482
  tracks.push({ prop: "rotation", times, values, window: [t0, t1] });
13051
13483
  } else {
13052
- const mag0 = num(fx.mag, 40);
13053
- const ang = num(fx.angle, 90) * Math.PI / 180;
13054
- const freq = clamp(num(fx.freq, 4), 0.2, 30);
13484
+ const mag0 = num2(fx.mag, 40);
13485
+ const ang = num2(fx.angle, 90) * Math.PI / 180;
13486
+ const freq = clamp(num2(fx.freq, 4), 0.2, 30);
13055
13487
  const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 160);
13056
13488
  const tr = fx.tracks?.mag;
13057
13489
  const times = [round(t0, 3)];
@@ -13083,13 +13515,13 @@ function bakeLayerOps(ly) {
13083
13515
  let lo;
13084
13516
  let hi;
13085
13517
  if (fx.amp != null) {
13086
- lo = 1 - num(fx.amp, 0);
13087
- hi = 1 + num(fx.amp, 0);
13518
+ lo = 1 - num2(fx.amp, 0);
13519
+ hi = 1 + num2(fx.amp, 0);
13088
13520
  } else {
13089
- lo = num(fx.min, 1);
13090
- hi = num(fx.max, 1.06);
13521
+ lo = num2(fx.min, 1);
13522
+ hi = num2(fx.max, 1.06);
13091
13523
  }
13092
- const freq = clamp(num(fx.freq, 2), 0.2, 20);
13524
+ const freq = clamp(num2(fx.freq, 2), 0.2, 20);
13093
13525
  const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 160);
13094
13526
  const tr = fx.tracks?.max;
13095
13527
  const times = [round(t0, 3)];
@@ -13251,7 +13683,7 @@ function positionBaseKeys(anim, pos) {
13251
13683
  function scaleBaseKeys(anim, coverExpr) {
13252
13684
  const sc = (v) => coverExpr ? `${r37(v)}*${coverExpr}` : `${r37(v)}`;
13253
13685
  if (anim.scale?.length) {
13254
- return anim.scale.map((k) => ({ t: k.t, value: `[${sc(num(k.v, 1) * 100)}, ${sc(num(k.v, 1) * 100)}]`, e: k.e }));
13686
+ return anim.scale.map((k) => ({ t: k.t, value: `[${sc(num2(k.v, 1) * 100)}, ${sc(num2(k.v, 1) * 100)}]`, e: k.e }));
13255
13687
  }
13256
13688
  if (anim.sx?.length || anim.sy?.length) {
13257
13689
  const merged = mergeChannelTracks(anim.sx, anim.sy, 1, 1);
@@ -13266,9 +13698,9 @@ function emitBakedOps(ctx, ly, layerVar, tf, coverExpr) {
13266
13698
  if (tracks.length === 0)
13267
13699
  return;
13268
13700
  const anim = ly.anim ?? {};
13269
- const pos = [num(ly.pos?.[0], 0), num(ly.pos?.[1], 0)];
13270
- const inn = num(ly.in, 0);
13271
- const out = Math.max(inn + 0.01, num(ly.out, inn + 1));
13701
+ const pos = [num2(ly.pos?.[0], 0), num2(ly.pos?.[1], 0)];
13702
+ const inn = num2(ly.in, 0);
13703
+ const out = Math.max(inn + 0.01, num2(ly.out, inn + 1));
13272
13704
  const sc = (v) => coverExpr ? `${r37(v)}*${coverExpr}` : `${r37(v)}`;
13273
13705
  const brights = tracks.filter((t) => t.prop === "brightness");
13274
13706
  if (brights.length) {
@@ -13315,7 +13747,7 @@ function emitBakedOps(ctx, ly, layerVar, tf, coverExpr) {
13315
13747
  }
13316
13748
  const rotBaked = tracks.filter((t) => t.prop === "rotation");
13317
13749
  if (rotBaked.length) {
13318
- const baseKeys = (anim.rot ?? []).map((k) => ({ t: k.t, value: `${r37(num(k.v, 0))}`, e: k.e }));
13750
+ const baseKeys = (anim.rot ?? []).map((k) => ({ t: k.t, value: `${r37(num2(k.v, 0))}`, e: k.e }));
13319
13751
  const bakedKeys = [];
13320
13752
  const windows = [];
13321
13753
  for (const bt of rotBaked) {
@@ -13333,10 +13765,10 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13333
13765
  const c3 = ir.canvas;
13334
13766
  const id = ++ctx.layerSeq;
13335
13767
  const v = `ly${id}`;
13336
- const inn = num(ly.in, 0);
13337
- const out = Math.max(inn + 0.01, num(ly.out, c3.duration));
13338
- const px = num(ly.pos?.[0], c3.w / 2);
13339
- const py = num(ly.pos?.[1], c3.h / 2);
13768
+ const inn = num2(ly.in, 0);
13769
+ const out = Math.max(inn + 0.01, num2(ly.out, c3.duration));
13770
+ const px = num2(ly.pos?.[0], c3.w / 2);
13771
+ const py = num2(ly.pos?.[1], c3.h / 2);
13340
13772
  const name = esc(`${ly.id || "L" + id} [${ly.type}]`);
13341
13773
  L.push(``);
13342
13774
  L.push(` // ---- 层 ${name} in=${r37(inn)} out=${r37(out)} ----`);
@@ -13358,9 +13790,9 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13358
13790
  const footageHit = ly.type === "image" || ly.type === "video" ? ctx.footage[ly.id] : undefined;
13359
13791
  const fgVar = footageHit ? ctx.footageVars.get(footageHit.path) : undefined;
13360
13792
  if (footageHit && fgVar) {
13361
- const slotW = Math.max(2, Math.round(num(ly.w, c3.w)));
13362
- const slotH = Math.max(2, Math.round(num(ly.h, c3.h)));
13363
- const srcOffset = num(footageHit.srcOffset, 0);
13793
+ const slotW = Math.max(2, Math.round(num2(ly.w, c3.w)));
13794
+ const slotH = Math.max(2, Math.round(num2(ly.h, c3.h)));
13795
+ const srcOffset = num2(footageHit.srcOffset, 0);
13364
13796
  const cvv = `cv${id}`;
13365
13797
  coverExpr = cvv;
13366
13798
  L.push(` // 素材注入(${esc(String(ly.type))}): footage srcOffset=${r37(srcOffset)}`);
@@ -13391,14 +13823,14 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13391
13823
  L.push(` } catch (e) {}`);
13392
13824
  } else if (ly.type === "shape") {
13393
13825
  const col = colorArr(ly.fill, [0.2, 0.33, 0.67]);
13394
- const w = Math.max(2, Math.round(num(ly.w, 400)));
13395
- const h = Math.max(2, Math.round(num(ly.h, 400)));
13826
+ const w = Math.max(2, Math.round(num2(ly.w, 400)));
13827
+ const h = Math.max(2, Math.round(num2(ly.h, 400)));
13396
13828
  if (ly.shape === "ellipse")
13397
13829
  L.push(` // TODO: 原层为椭圆形状,固态占位,可手动换 shape layer`);
13398
13830
  L.push(` var ${v} = ${cv}.layers.addSolid([${col.join(",")}], "${name}", ${w}, ${h}, 1, ${r37(c3.duration)});`);
13399
13831
  } else {
13400
- const w = Math.max(2, Math.round(num(ly.w, c3.w)));
13401
- const h = Math.max(2, Math.round(num(ly.h, c3.h)));
13832
+ const w = Math.max(2, Math.round(num2(ly.w, c3.w)));
13833
+ const h = Math.max(2, Math.round(num2(ly.h, c3.h)));
13402
13834
  L.push(` // 占位素材(${esc(String(ly.type))}${ly.asset ? ` asset=${esc(String(ly.asset))}` : ""}): 请替换为真实素材`);
13403
13835
  L.push(` var ${v} = ${cv}.layers.addSolid([0.35,0.35,0.4], "${name} [占位]", ${w}, ${h}, 1, ${r37(c3.duration)});`);
13404
13836
  }
@@ -13428,10 +13860,10 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13428
13860
  }
13429
13861
  }
13430
13862
  if (anim.rot?.length && !bakedProps.has("rotation")) {
13431
- emitKeys(ctx, `${tf}.property("ADBE Rotate Z")`, anim.rot.map((k) => ({ t: k.t, value: `${r37(num(k.v, 0))}`, e: k.e })));
13863
+ emitKeys(ctx, `${tf}.property("ADBE Rotate Z")`, anim.rot.map((k) => ({ t: k.t, value: `${r37(num2(k.v, 0))}`, e: k.e })));
13432
13864
  }
13433
13865
  if (anim.opacity?.length) {
13434
- emitKeys(ctx, `${tf}.property("ADBE Opacity")`, anim.opacity.map((k) => ({ t: k.t, value: `${r37(Math.min(1, Math.max(0, num(k.v, 1))) * 100)}`, e: k.e })));
13866
+ emitKeys(ctx, `${tf}.property("ADBE Opacity")`, anim.opacity.map((k) => ({ t: k.t, value: `${r37(Math.min(1, Math.max(0, num2(k.v, 1))) * 100)}`, e: k.e })));
13435
13867
  }
13436
13868
  if (anim.ls?.length) {
13437
13869
  L.push(` // TODO: letterspacing 轨未自动映射(AE 需 Animator>Tracking),共 ${anim.ls.length} 帧`);
@@ -13445,8 +13877,8 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13445
13877
  }
13446
13878
  function emitGroupAnim(ctx, v, ly) {
13447
13879
  const anim = ly.anim ?? {};
13448
- const px = num(ly.pos?.[0], 0);
13449
- const py = num(ly.pos?.[1], 0);
13880
+ const px = num2(ly.pos?.[0], 0);
13881
+ const py = num2(ly.pos?.[1], 0);
13450
13882
  if (anim.x?.length || anim.y?.length) {
13451
13883
  const merged = mergeChannelTracks(anim.x, anim.y, px, py);
13452
13884
  emitKeys(ctx, `${v}.property("ADBE Transform Group").property("ADBE Position")`, merged.map((k) => ({ t: k.t, value: `[${r37(k.a - px)}, ${r37(k.b - py)}]`, e: k.e })));
@@ -13530,10 +13962,10 @@ function madJsx(opts) {
13530
13962
  const subVars = [];
13531
13963
  windows.forEach((win, wi) => {
13532
13964
  const c3 = win.ir.canvas;
13533
- const sw = Math.max(4, Math.round(num(c3.w, 1920)));
13534
- const sh = Math.max(4, Math.round(num(c3.h, 1080)));
13535
- const sfps = Math.min(99, Math.max(1, num(c3.fps, 30)));
13536
- const sdur = Math.max(0.1, num(c3.duration, 3));
13965
+ const sw = Math.max(4, Math.round(num2(c3.w, 1920)));
13966
+ const sh = Math.max(4, Math.round(num2(c3.h, 1080)));
13967
+ const sfps = Math.min(99, Math.max(1, num2(c3.fps, 30)));
13968
+ const sdur = Math.max(0.1, num2(c3.duration, 3));
13537
13969
  const subName = esc(`${win.uid}-${win.seq}`);
13538
13970
  const subVar = `sub${wi}`;
13539
13971
  L.push(``);
@@ -13789,8 +14221,8 @@ function beatQuantized(natLens, analysis) {
13789
14221
 
13790
14222
  // src/lib/mad/data.ts
13791
14223
  import { createHash as createHash2 } from "node:crypto";
13792
- import { mkdir as mkdir10, readFile as readFile8, rename as rename2, rm, writeFile as writeFile10, readdir } from "node:fs/promises";
13793
- import { existsSync as existsSync21 } from "node:fs";
14224
+ import { mkdir as mkdir11, readFile as readFile8, rename as rename2, rm, writeFile as writeFile10, readdir as readdir2 } from "node:fs/promises";
14225
+ import { existsSync as existsSync22 } from "node:fs";
13794
14226
  import { join as join25 } from "node:path";
13795
14227
  function madCacheDir() {
13796
14228
  return homeFile("mad-cache");
@@ -13820,7 +14252,7 @@ async function atomicWrite(dest, data) {
13820
14252
  await rename2(tmp, dest);
13821
14253
  }
13822
14254
  async function verifyFile(path, sha256) {
13823
- if (!existsSync21(path))
14255
+ if (!existsSync22(path))
13824
14256
  return false;
13825
14257
  try {
13826
14258
  const buf = await readFile8(path);
@@ -13844,7 +14276,7 @@ function validateManifest(obj) {
13844
14276
  }
13845
14277
  async function cleanupOldVersions(cacheRoot, keepVersion, warn) {
13846
14278
  try {
13847
- const entries = await readdir(cacheRoot);
14279
+ const entries = await readdir2(cacheRoot);
13848
14280
  for (const e of entries) {
13849
14281
  const m = /^v(\d+)$/.exec(e);
13850
14282
  if (m && Number(m[1]) !== keepVersion) {
@@ -13872,7 +14304,7 @@ async function ensureMadData(opts, deps) {
13872
14304
  warn(`manifest 拉取失败(${e instanceof Error ? e.message : String(e)}),回退本地缓存`);
13873
14305
  }
13874
14306
  if (!manifest) {
13875
- if (!existsSync21(snapshotPath)) {
14307
+ if (!existsSync22(snapshotPath)) {
13876
14308
  throw new Error("首次使用需联网下载技法数据。请连网后重跑 `gtrk tool mad`(数据会缓存到本地,之后可离线出片)。");
13877
14309
  }
13878
14310
  try {
@@ -13894,12 +14326,12 @@ async function ensureMadData(opts, deps) {
13894
14326
  const needDownload = !!opts.refresh || !cacheValid;
13895
14327
  if (needDownload) {
13896
14328
  if (!online) {
13897
- if (existsSync21(poolPath)) {
14329
+ if (existsSync22(poolPath)) {
13898
14330
  throw new Error("本地技法数据缓存损坏且当前离线。请连网重跑,或加 --refresh 强制重新下载。");
13899
14331
  }
13900
14332
  throw new Error("首次使用需联网下载技法数据。请连网后重跑 `gtrk tool mad`(数据会缓存到本地,之后可离线出片)。");
13901
14333
  }
13902
- await mkdir10(verDir, { recursive: true });
14334
+ await mkdir11(verDir, { recursive: true });
13903
14335
  warn(`下载技法池数据 mad_pool(版本 v${version},约 ${Math.round(poolMeta.size / 1024)} KB)…`);
13904
14336
  const res = await deps.fetchFn(poolMeta.url);
13905
14337
  if (!res.ok)
@@ -13912,7 +14344,7 @@ async function ensureMadData(opts, deps) {
13912
14344
  warn(`技法池数据就绪(v${version})`);
13913
14345
  }
13914
14346
  if (online) {
13915
- await mkdir10(cacheRoot, { recursive: true });
14347
+ await mkdir11(cacheRoot, { recursive: true });
13916
14348
  await atomicWrite(snapshotPath, new Uint8Array(Buffer.from(JSON.stringify(manifest), "utf8")));
13917
14349
  await cleanupOldVersions(cacheRoot, version, warn);
13918
14350
  }
@@ -13929,8 +14361,8 @@ async function ensureMadData(opts, deps) {
13929
14361
 
13930
14362
  // src/lib/mad/pool.ts
13931
14363
  import { gunzipSync } from "node:zlib";
13932
- import { mkdir as mkdir11, readFile as readFile9 } from "node:fs/promises";
13933
- import { existsSync as existsSync22 } from "node:fs";
14364
+ import { mkdir as mkdir12, readFile as readFile9 } from "node:fs/promises";
14365
+ import { existsSync as existsSync23 } from "node:fs";
13934
14366
  import { join as join26 } from "node:path";
13935
14367
  function shardPath(verDir, shard) {
13936
14368
  return join26(verDir, "ir", `${shard}.json.gz`);
@@ -13950,7 +14382,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
13950
14382
  if (cached)
13951
14383
  return cached;
13952
14384
  const path = shardPath(verDir, shard);
13953
- if (existsSync22(path)) {
14385
+ if (existsSync23(path)) {
13954
14386
  try {
13955
14387
  const s2 = decodeShard(await readFile9(path));
13956
14388
  memo.set(shard, s2);
@@ -13968,7 +14400,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
13968
14400
  throw new Error(`IR 分片下载失败 HTTP ${res.status}:${url}`);
13969
14401
  const buf = new Uint8Array(await res.arrayBuffer());
13970
14402
  const s = decodeShard(buf);
13971
- await mkdir11(join26(verDir, "ir"), { recursive: true });
14403
+ await mkdir12(join26(verDir, "ir"), { recursive: true });
13972
14404
  await atomicWrite(path, buf);
13973
14405
  memo.set(shard, s);
13974
14406
  return s;
@@ -13982,7 +14414,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
13982
14414
  return ir;
13983
14415
  },
13984
14416
  shardCached(shard) {
13985
- return memo.has(shard) || existsSync22(shardPath(verDir, shard));
14417
+ return memo.has(shard) || existsSync23(shardPath(verDir, shard));
13986
14418
  }
13987
14419
  };
13988
14420
  }
@@ -14066,7 +14498,7 @@ async function runMad(inputArg, opts, deps = {}) {
14066
14498
  if (!inputArg)
14067
14499
  throw new Error("用法:gtrk tool mad <素材文件夹> [--bgm 歌.mp3]");
14068
14500
  const dirAbs = resolve13(inputArg);
14069
- if (!existsSync23(dirAbs) || !statSync3(dirAbs).isDirectory()) {
14501
+ if (!existsSync24(dirAbs) || !statSync3(dirAbs).isDirectory()) {
14070
14502
  throw new Error(`素材文件夹不存在或不是目录:${dirAbs}`);
14071
14503
  }
14072
14504
  const { videos, orientation, skipped } = scanFolder(dirAbs, {
@@ -14181,7 +14613,7 @@ async function runMad(inputArg, opts, deps = {}) {
14181
14613
  const bgm = level <= 2 && bgmForTrack ? { path: bgmForTrack, markers } : undefined;
14182
14614
  const { jsx } = madJsx({ master, windows, header, bgm });
14183
14615
  const outDir = opts.out ? resolve13(opts.out) : join27(process.cwd(), `mad-${timestamp4(now)}`);
14184
- await mkdir12(outDir, { recursive: true });
14616
+ await mkdir13(outDir, { recursive: true });
14185
14617
  const jsxPath = join27(outDir, "mad.jsx");
14186
14618
  await writeFile11(jsxPath, jsx);
14187
14619
  const result = {
@@ -14327,8 +14759,8 @@ async function runMadInTool(inputArg, opts) {
14327
14759
  }
14328
14760
 
14329
14761
  // src/commands/transcript.ts
14330
- import { existsSync as existsSync24 } from "node:fs";
14331
- import { mkdir as mkdir13, rename as rename3, rm as rm2, stat as stat6, writeFile as writeFile12 } from "node:fs/promises";
14762
+ import { existsSync as existsSync25 } from "node:fs";
14763
+ import { mkdir as mkdir14, rename as rename3, rm as rm2, stat as stat6, writeFile as writeFile12 } from "node:fs/promises";
14332
14764
  import { basename as basename14, dirname as dirname11, extname as extname8, join as join28, resolve as resolve14 } from "node:path";
14333
14765
 
14334
14766
  // src/lib/transcript.ts
@@ -14466,7 +14898,7 @@ async function validateTranscriptInput(input) {
14466
14898
  throw new Error("视频转文字稿仅支持本地视频文件,不支持 URL、平台视频地址或远端下载");
14467
14899
  }
14468
14900
  const inputAbs = resolve14(input);
14469
- if (!existsSync24(inputAbs))
14901
+ if (!existsSync25(inputAbs))
14470
14902
  throw new Error(`本地视频不存在:${inputAbs}`);
14471
14903
  const info = await stat6(inputAbs);
14472
14904
  if (!info.isFile())
@@ -14485,7 +14917,7 @@ function resolveTranscriptOutput(inputAbs, out) {
14485
14917
  return output;
14486
14918
  }
14487
14919
  async function writeMarkdownAtomic(path, markdown) {
14488
- await mkdir13(dirname11(path), { recursive: true });
14920
+ await mkdir14(dirname11(path), { recursive: true });
14489
14921
  const temp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
14490
14922
  try {
14491
14923
  await writeFile12(temp, markdown, "utf8");
@@ -14563,8 +14995,8 @@ function registerTranscript(program2) {
14563
14995
 
14564
14996
  // src/commands/music-visualizer.ts
14565
14997
  import { resolve as resolve15, join as join29, dirname as dirname12, basename as basename15, extname as extname9 } from "node:path";
14566
- import { mkdir as mkdir14, writeFile as writeFile13 } from "node:fs/promises";
14567
- import { existsSync as existsSync25 } from "node:fs";
14998
+ import { mkdir as mkdir15, writeFile as writeFile13 } from "node:fs/promises";
14999
+ import { existsSync as existsSync26 } from "node:fs";
14568
15000
  var TASK_TYPE5 = "music_visualizer";
14569
15001
  var PRICE_KEY2 = "music_visualizer";
14570
15002
  var HEX_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
@@ -14612,7 +15044,7 @@ function timestamp5() {
14612
15044
  return `${p(d.getFullYear() % 100)}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
14613
15045
  }
14614
15046
  function assertExt(pathAbs, exts, label) {
14615
- if (!existsSync25(pathAbs))
15047
+ if (!existsSync26(pathAbs))
14616
15048
  throw new Error(`${label}不存在:${pathAbs}`);
14617
15049
  const e = extname9(pathAbs).toLowerCase();
14618
15050
  if (exts.length && !exts.includes(e)) {
@@ -14728,7 +15160,7 @@ async function runMusicVisualizer(audio, opts) {
14728
15160
  });
14729
15161
  const { taskId } = submitted;
14730
15162
  log.info(`task_id = ${taskId}`);
14731
- await mkdir14(outDir, { recursive: true });
15163
+ await mkdir15(outDir, { recursive: true });
14732
15164
  await writeFile13(join29(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE5, fileId: submitted.fileId, source: audioAbs, template, createdAt: new Date().toISOString() }, null, 2));
14733
15165
  log.step("③ 云端处理中(每 5s 轮询)…");
14734
15166
  const result = await pollTask(cfg, TASK_TYPE5, taskId, (status, progress) => {
@@ -14780,7 +15212,7 @@ try {
14780
15212
  process.loadEnvFile?.();
14781
15213
  } catch {}
14782
15214
  migrateLegacyHome();
14783
- var { version } = JSON.parse(readFileSync5(join30(packageRoot(), "package.json"), "utf8"));
15215
+ var { version } = JSON.parse(readFileSync6(join30(packageRoot(), "package.json"), "utf8"));
14784
15216
  var program2 = new Command;
14785
15217
  program2.name("gtrk").description("同合云成片流水线 CLI —— agent 驱动云端任务、产物拉回本地、三方工程文件(客户端/剪映/PR)互通").version(version);
14786
15218
  registerInstall(program2);