@gitruck/cli 0.2.20 → 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
@@ -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,12 +7315,140 @@ 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";
7318
+ import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
7289
7319
  import { existsSync as existsSync16 } from "node:fs";
7290
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
+ }
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";
7451
+ import { mkdir as mkdir6, writeFile as writeFile6, stat as stat5 } from "node:fs/promises";
7294
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";
@@ -8902,7 +9060,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
8902
9060
  const isMulti = MULTI_INPUT_KINDS.has(descriptor.input.kind);
8903
9061
  const inputList = isMulti ? (Array.isArray(inputArg) ? inputArg : inputArg != null ? [inputArg] : []).map((p) => resolve5(p)) : undefined;
8904
9062
  const inputAbs = isMulti ? inputList[0] : typeof inputArg === "string" ? resolve5(inputArg) : undefined;
8905
- const baseName = inputAbs ? basename7(inputAbs, extname4(inputAbs)) : descriptor.name;
9063
+ const baseName2 = inputAbs ? basename7(inputAbs, extname4(inputAbs)) : descriptor.name;
8906
9064
  if (isMulti) {
8907
9065
  validateToolInputs(descriptor, inputList);
8908
9066
  } else {
@@ -8914,7 +9072,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
8914
9072
  const ctx = {
8915
9073
  inputAbs,
8916
9074
  ...inputList ? { inputAbsList: inputList } : {},
8917
- baseName,
9075
+ baseName: baseName2,
8918
9076
  ffmpegPath: opts.ffmpegPath,
8919
9077
  opts,
8920
9078
  extraParams,
@@ -8971,7 +9129,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
8971
9129
  taskId = submitted.taskId;
8972
9130
  fileId = submitted.fileId;
8973
9131
  }
8974
- await mkdir5(outDir, { recursive: true });
9132
+ await mkdir6(outDir, { recursive: true });
8975
9133
  const fingerprint2 = inputList ? await Promise.all(inputList.map((p) => safeFingerprint(p))) : inputAbs ? await safeFingerprint(inputAbs) : undefined;
8976
9134
  await writeFile6(join16(outDir, "task.json"), JSON.stringify({
8977
9135
  tool: descriptor.name,
@@ -9077,6 +9235,34 @@ function buildLong2ShortPayload(fileId, opts, geo, inputAbs, formats, draftTarge
9077
9235
  mergeParams(p, parseExtraParams2(opts.param ?? [], opts.paramsJson));
9078
9236
  return p;
9079
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
+ }
9080
9266
  function registerLong2Short(program2) {
9081
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) => {
9082
9268
  await runLong2Short(input, opts);
@@ -9124,7 +9310,7 @@ async function runLong2Short(input, opts) {
9124
9310
  });
9125
9311
  const { taskId, fileId } = { taskId: submitted.taskId, fileId: submitted.fileId };
9126
9312
  log.info(`task_id = ${taskId}`);
9127
- await mkdir6(outDir, { recursive: true });
9313
+ await mkdir7(outDir, { recursive: true });
9128
9314
  await writeFile7(join17(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE2, fileId, source: inputAbs, formats, createdAt: new Date().toISOString() }, null, 2));
9129
9315
  log.step("④ 云端处理中(选段/跳剪" + (opts.splitScreen ? "/分屏" : "") + ",每 5s 轮询)…");
9130
9316
  const output = await pollToolTask(cfg, TASK_TYPE2, taskId, {
@@ -9150,7 +9336,7 @@ async function runLong2Short(input, opts) {
9150
9336
  if (!/^(?:[A-Za-z]:[\\/]|[\\/])/.test(dest))
9151
9337
  dest = join17(dirname5(inputAbs), dest);
9152
9338
  try {
9153
- await mkdir6(dirname5(dest), { recursive: true });
9339
+ await mkdir7(dirname5(dest), { recursive: true });
9154
9340
  await download(url, dest);
9155
9341
  splitLanded++;
9156
9342
  } catch (e) {
@@ -9186,20 +9372,40 @@ async function runLong2Short(input, opts) {
9186
9372
  log.warn(`clip${i} 落地失败(不连坐其余):${errors[`clip${i}`]}`);
9187
9373
  }
9188
9374
  }
9189
- if (draftRoot) {
9190
- for (const [i, cr] of clipResults.entries()) {
9191
- const src = join17(cr.dir, "jianying");
9192
- if (!existsSync16(src))
9193
- continue;
9194
- const dest = `${draftTarget}_clip${i}`;
9195
- try {
9196
- await mkdir6(dest, { recursive: true });
9197
- await cp2(src, dest, { recursive: true });
9198
- } catch (e) {
9199
- errors[`clip${i}:jianying:draft`] = e instanceof Error ? e.message : String(e);
9200
- }
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);
9201
9397
  }
9202
- 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);
9203
9409
  }
9204
9410
  await writeFile7(join17(outDir, "report.json"), JSON.stringify(output.report ?? {}, null, 2));
9205
9411
  const ok = Object.keys(errors).length === 0 && clipResults.some((c3) => c3.ok);
@@ -9360,7 +9566,7 @@ function registerRender(program2) {
9360
9566
  // src/commands/split.ts
9361
9567
  import { resolve as resolve9, join as join21, dirname as dirname8, basename as basename11 } from "node:path";
9362
9568
  import { existsSync as existsSync18 } from "node:fs";
9363
- import { readFile as readFile5, writeFile as writeFile8, mkdir as mkdir7 } from "node:fs/promises";
9569
+ import { readFile as readFile5, writeFile as writeFile8, mkdir as mkdir8 } from "node:fs/promises";
9364
9570
  import { createHash } from "node:crypto";
9365
9571
 
9366
9572
  // src/lib/gtrk-writeback.ts
@@ -9481,7 +9687,7 @@ async function runView(baseDir, gtrkPath, transcriptPath, opts) {
9481
9687
  const { gtrk } = readGtrk(gtrkPath);
9482
9688
  const view = projectTranscript(transcript, gtrk, { words: opts.words });
9483
9689
  const splitDir = join21(baseDir, "split");
9484
- await mkdir7(splitDir, { recursive: true });
9690
+ await mkdir8(splitDir, { recursive: true });
9485
9691
  const viewPath = join21(splitDir, "view.json");
9486
9692
  await writeFile8(viewPath, JSON.stringify(view, null, 2));
9487
9693
  const dropped = view.utterances.filter((u) => u.dropped).length;
@@ -9540,7 +9746,7 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
9540
9746
  });
9541
9747
  writeStructMetaSplit(gtrkPath, gtrk, landing.split, mtimeMs);
9542
9748
  const splitDir = join21(baseDir, "split");
9543
- await mkdir7(splitDir, { recursive: true });
9749
+ await mkdir8(splitDir, { recursive: true });
9544
9750
  const dispatchPath = join21(splitDir, "dispatch.json");
9545
9751
  await writeFile8(dispatchPath, JSON.stringify(landing.dispatch, null, 2));
9546
9752
  let mdPath = null;
@@ -9579,7 +9785,7 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
9579
9785
  // src/commands/matrix.ts
9580
9786
  import { resolve as resolve11, join as join22, dirname as dirname9, basename as basename12 } from "node:path";
9581
9787
  import { existsSync as existsSync20 } from "node:fs";
9582
- import { readFile as readFile6, writeFile as writeFile9, mkdir as mkdir8, rename } from "node:fs/promises";
9788
+ import { readFile as readFile6, writeFile as writeFile9, mkdir as mkdir9, rename } from "node:fs/promises";
9583
9789
 
9584
9790
  // src/lib/solid-png.ts
9585
9791
  import { deflateSync } from "node:zlib";
@@ -10359,8 +10565,8 @@ function collectMaterialRefs(gtrk) {
10359
10565
  const id = c3[key];
10360
10566
  if (typeof id !== "string" || id === "")
10361
10567
  continue;
10362
- const list = out.get(id) ?? [];
10363
- list.push({
10568
+ const list2 = out.get(id) ?? [];
10569
+ list2.push({
10364
10570
  track: group,
10365
10571
  track_index: trackIndex,
10366
10572
  clip_id: typeof c3.clip_id === "string" ? c3.clip_id : null,
@@ -10368,7 +10574,7 @@ function collectMaterialRefs(gtrk) {
10368
10574
  track_ed: clipTrackEd(c3),
10369
10575
  key
10370
10576
  });
10371
- out.set(id, list);
10577
+ out.set(id, list2);
10372
10578
  }
10373
10579
  }
10374
10580
  }
@@ -10410,15 +10616,15 @@ function checkMaterialIntegrity(opts) {
10410
10616
  }
10411
10617
  if (present)
10412
10618
  continue;
10413
- const list = refs.get(id) ?? [];
10619
+ const list2 = refs.get(id) ?? [];
10414
10620
  const entry = {
10415
10621
  id,
10416
10622
  path,
10417
10623
  kind,
10418
10624
  resolved,
10419
- referenced: list.length > 0,
10420
- refCount: list.length,
10421
- refs: list
10625
+ referenced: list2.length > 0,
10626
+ refCount: list2.length,
10627
+ refs: list2
10422
10628
  };
10423
10629
  (kind === "relative" ? dangling : external).push(entry);
10424
10630
  }
@@ -10567,9 +10773,9 @@ function dedupeBeatQueries(queries) {
10567
10773
  const best = bestByClip.get(r.clip_id);
10568
10774
  if (best.qi === qi && best.r === r)
10569
10775
  return true;
10570
- const list = best.r.also_matched_queries ??= [];
10571
- if (!list.includes(q.query))
10572
- list.push(q.query);
10776
+ const list2 = best.r.also_matched_queries ??= [];
10777
+ if (!list2.includes(q.query))
10778
+ list2.push(q.query);
10573
10779
  return false;
10574
10780
  });
10575
10781
  }
@@ -10792,7 +10998,7 @@ async function runPlanMode(cfg, tier, broll, overrides, columnId, opts) {
10792
10998
  beats
10793
10999
  });
10794
11000
  const splitDir = join22(baseDir, "split");
10795
- await mkdir8(splitDir, { recursive: true });
11001
+ await mkdir9(splitDir, { recursive: true });
10796
11002
  const planPath = join22(splitDir, "broll-plan.json");
10797
11003
  await writeFile9(planPath, JSON.stringify(plan, null, 2));
10798
11004
  log.ok(`候选清单已生成:${planPath}(${beats.length} beat · ${okCount}/${totalQueries} query 成功 · ${resultCount} 条候选)`);
@@ -10857,7 +11063,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10857
11063
  log.step(`▶ 候选铺轨(${layN} 轨 · 平铺 ${slotCount} 槽位 · ${clipIds.size} 个 clip,preview 代理)…`);
10858
11064
  const gtrkDir = dirname9(gtrkPath);
10859
11065
  const previewDir = join22(gtrkDir, ...BROLL_PREVIEW_DIR.split("/"));
10860
- await mkdir8(previewDir, { recursive: true });
11066
+ await mkdir9(previewDir, { recursive: true });
10861
11067
  const prevSource = new Map;
10862
11068
  const prevBroll = gtrk.struct_meta?.broll;
10863
11069
  for (const b of prevBroll?.beats ?? []) {
@@ -10918,8 +11124,8 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10918
11124
  forceRelay
10919
11125
  });
10920
11126
  if (summary.refused) {
10921
- const list = summary.keptEditedTracks;
10922
- log.err(`拒绝铺轨:${list.length} 条候选轨已被你在客户端编辑过(track_index ${list.join("/") || "-"})——` + "本次不剥它们、也不铺新轨,工程文件零改动。");
11127
+ const list2 = summary.keptEditedTracks;
11128
+ log.err(`拒绝铺轨:${list2.length} 条候选轨已被你在客户端编辑过(track_index ${list2.join("/") || "-"})——` + "本次不剥它们、也不铺新轨,工程文件零改动。");
10923
11129
  for (const w of warnings)
10924
11130
  log.warn(w);
10925
11131
  log.warn("下一步二选一:① 在客户端处置那条轨(删掉 / 移走 / 改用别的轨)后重跑本命令;" + "② 确知要丢弃那条轨上的编辑 → 加 --force-relay 强制剥离重铺" + "(会删掉已确认原片的 broll-raw-* 素材登记,盘上原片文件成孤儿,不可恢复)。");
@@ -10927,7 +11133,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10927
11133
  return {
10928
11134
  lay: {
10929
11135
  refused: true,
10930
- keptEditedTracks: list,
11136
+ keptEditedTracks: list2,
10931
11137
  laidTracks: [],
10932
11138
  laidClips: 0,
10933
11139
  removedTracks: [],
@@ -10945,7 +11151,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
10945
11151
  const abs = join22(gtrkDir, ...rel.split("/"));
10946
11152
  try {
10947
11153
  if (!existsSync20(abs)) {
10948
- await mkdir8(dirname9(abs), { recursive: true });
11154
+ await mkdir9(dirname9(abs), { recursive: true });
10949
11155
  const tmp = `${abs}.tmp-${process.pid}`;
10950
11156
  await writeFile9(tmp, encodeSolidPng(spec));
10951
11157
  await rename(tmp, abs);
@@ -11062,7 +11268,7 @@ function slugify3(name) {
11062
11268
  // src/commands/mg.ts
11063
11269
  import { resolve as resolve12, join as join23, dirname as dirname10, basename as basename13 } from "node:path";
11064
11270
  import { existsSync as existsSync21 } from "node:fs";
11065
- import { readFile as readFile7, mkdir as mkdir9, copyFile } from "node:fs/promises";
11271
+ import { readFile as readFile7, mkdir as mkdir10, copyFile } from "node:fs/promises";
11066
11272
 
11067
11273
  // src/lib/mg-lint.ts
11068
11274
  var CDN_OK = [/lib\.baomitu\.com/i, /cdnjs\.cloudflare\.com/i, /unpkg\.com/i];
@@ -11926,9 +12132,9 @@ function detectPrimitiveLoops(html) {
11926
12132
  }
11927
12133
  const callsByFunction = new Map;
11928
12134
  const pushCall = (fn, at) => {
11929
- const list = callsByFunction.get(fn.declStart) ?? [];
11930
- list.push({ at, loops: executionLoopsAt(at) });
11931
- 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);
11932
12138
  };
11933
12139
  const functionNames = new Set(functions.map((fn) => fn.name).filter((name) => name !== null));
11934
12140
  for (const call of code.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
@@ -12129,21 +12335,27 @@ function filterDecls(styleDecls) {
12129
12335
  out.push(m[1].trim());
12130
12336
  return out;
12131
12337
  }
12132
- function styleBlockOwnDecls(html) {
12338
+ function styleBlockRules(html) {
12133
12339
  const out = [];
12134
12340
  for (const sm of html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)) {
12135
12341
  const css = sm[1].replace(/\/\*[\s\S]*?\*\//g, " ");
12136
12342
  const stack = [];
12343
+ const selStack = [];
12344
+ let segStart = 0;
12137
12345
  for (let i = 0;i < css.length; i++) {
12138
12346
  const c3 = css[i];
12139
12347
  if (c3 === '"' || c3 === "'") {
12140
12348
  i = skipString(css, i);
12141
12349
  continue;
12142
12350
  }
12143
- if (c3 === "{")
12351
+ if (c3 === "{") {
12144
12352
  stack.push(i);
12145
- else if (c3 === "}") {
12353
+ selStack.push(css.slice(segStart, i));
12354
+ segStart = i + 1;
12355
+ } else if (c3 === "}") {
12146
12356
  const open2 = stack.pop();
12357
+ const sel = selStack.pop() ?? "";
12358
+ segStart = i + 1;
12147
12359
  if (open2 === undefined)
12148
12360
  continue;
12149
12361
  let own = css.slice(open2 + 1, i);
@@ -12151,8 +12363,9 @@ function styleBlockOwnDecls(html) {
12151
12363
  prev = own;
12152
12364
  own = own.replace(/\{[^{}]*\}/g, " ");
12153
12365
  }
12154
- out.push(own);
12155
- }
12366
+ out.push({ selector: sel.trim(), own });
12367
+ } else if (c3 === ";")
12368
+ segStart = i + 1;
12156
12369
  }
12157
12370
  }
12158
12371
  return out;
@@ -12212,21 +12425,138 @@ function tweenVarObjects(js) {
12212
12425
  }
12213
12426
  return out;
12214
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
+ }
12215
12538
  function detectFilterCost(html) {
12216
12539
  const src = maskHtmlComments(html);
12217
12540
  const svgConvIds = svgConvolutionFilterIds(src);
12218
12541
  const animated = [];
12219
12542
  const staticFullBleed = [];
12543
+ const staticTransformed = [];
12220
12544
  const indeterminate = [];
12221
12545
  const add = (bucket, site) => {
12222
12546
  const s = site.replace(/\s+/g, " ").trim().slice(0, 120);
12223
12547
  if (!bucket.includes(s))
12224
12548
  bucket.push(s);
12225
12549
  };
12226
- for (const own of styleBlockOwnDecls(src)) {
12550
+ const staticFilterSites = new Map;
12551
+ for (const { selector, own } of styleBlockRules(src)) {
12227
12552
  const hit = filterDecls(own).filter((val) => filterValueConvolves(val, svgConvIds));
12228
- if (hit.length && isFullBleedStyle(own))
12553
+ if (!hit.length)
12554
+ continue;
12555
+ if (isFullBleedStyle(own))
12229
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]}`);
12230
12560
  }
12231
12561
  for (const m of src.matchAll(/<[a-zA-Z][^>]*>/g)) {
12232
12562
  const tag = m[0];
@@ -12236,9 +12566,13 @@ function detectFilterCost(html) {
12236
12566
  const byAttr = refAttr !== undefined && filterValueConvolves(refAttr, svgConvIds) ? refAttr : null;
12237
12567
  if (!inline.length && byAttr === null)
12238
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);
12239
12573
  if (!isFullBleedStyle(style))
12240
12574
  continue;
12241
- add(staticFullBleed, inline.length ? `内联 style:filter:${inline[0]}` : `SVG 属性:filter="${byAttr}"`);
12575
+ add(staticFullBleed, site);
12242
12576
  }
12243
12577
  const js = maskJsComments(scriptBodiesOnly(src));
12244
12578
  for (const body of tweenVarObjects(js)) {
@@ -12253,7 +12587,22 @@ function detectFilterCost(html) {
12253
12587
  if (svgConvIds.size && /(?:^|[{,])\s*["']?stdDeviation["']?\s*:/.test(body))
12254
12588
  add(animated, "补间驱动 SVG 滤镜基元的 stdDeviation");
12255
12589
  }
12256
- 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 };
12257
12606
  }
12258
12607
  function lintParticle(html, opts = {}) {
12259
12608
  const v = [];
@@ -12359,6 +12708,8 @@ function lintParticle(html, opts = {}) {
12359
12708
  push("c-filter-animated", false, `时间线补间直接驱动了真卷积滤镜(${cost.animated.slice(0, 3).join(";")})——` + "blur / drop-shadow / feGaussianBlur 是**真卷积**,每帧成本 ∝「被覆盖面积 × 半径 × 帧数」;" + "一旦被补间驱动,卷积结果**每帧失效**、缓存彻底失灵,这是本组三项里成本机制最重的一项。" + "改法:用 `opacity` / `transform`(`scale` / 位移)表达同一叙事动作;" + "确需滤镜就做成**静态两态切换**(滤镜值不随时间连续变),并把被滤元素的几何覆盖面积收到真正需要的那块矩形上。" + "本项是**成本提示**,不影响 ok / 退出码 / 铺轨;**未报 ≠ 这颗便宜**——真判据是真渲染出片计时");
12360
12709
  if (cost.staticFullBleed.length)
12361
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 侧尚无对照实测,不凭推测扩)。" + "本项恒非致命、不拦铺轨;未报 ≠ 便宜,真判据是真渲染出片计时");
12362
12713
  if (cost.indeterminate.length)
12363
12714
  push("c-filter-indeterminate", false, `该处 filter 值无法静态判定是否含真卷积(${cost.indeterminate.slice(0, 3).join(";")})——` + "补间值不是字符串字面量(变量 / 模板串 / 函数返回),本 lint 不做表达式求值与常量折叠," + "故对该处**未作判定**:既不是「判过且通过」,也不是命中。请人工确认它是否会驱动 blur / drop-shadow;" + "若是,按 `c-filter-animated` 的改法处理。本项恒非致命、不拦铺轨");
12364
12715
  return { ok: !v.some((x) => x.fatal), violations: v, opaque, compositionId: cid };
@@ -12781,7 +13132,7 @@ async function runLay(opts) {
12781
13132
  if (opts.replaceAll && orphans.length > 0) {
12782
13133
  log.warn(`--replace-all:已显式授权重置整轨,轨上其余 ${orphans.length} 颗已铺颗粒将被剥离(不走增量保留)。`);
12783
13134
  }
12784
- await mkdir9(join23(gtrkDir, ...MG_ASSET_DIR.split("/")), { recursive: true });
13135
+ await mkdir10(join23(gtrkDir, ...MG_ASSET_DIR.split("/")), { recursive: true });
12785
13136
  for (const it of items) {
12786
13137
  await copyFile(srcByComp.get(it.composition_id), join23(gtrkDir, ...it.html_rel.split("/")));
12787
13138
  }
@@ -12898,12 +13249,12 @@ function done(opts, result) {
12898
13249
  }
12899
13250
 
12900
13251
  // src/lib/mad/mad.ts
12901
- import { mkdir as mkdir12, writeFile as writeFile11 } from "node:fs/promises";
13252
+ import { mkdir as mkdir13, writeFile as writeFile11 } from "node:fs/promises";
12902
13253
  import { existsSync as existsSync24, statSync as statSync3 } from "node:fs";
12903
13254
  import { resolve as resolve13, join as join27 } from "node:path";
12904
13255
 
12905
13256
  // src/lib/convert/types.ts
12906
- function num(v, d = 0) {
13257
+ function num2(v, d = 0) {
12907
13258
  return typeof v === "number" && isFinite(v) ? v : d;
12908
13259
  }
12909
13260
  function parseCubicBezier(e) {
@@ -12952,19 +13303,19 @@ function mergeChannelTracks(ta, tb, defA, defB) {
12952
13303
  if (!track || track.length === 0)
12953
13304
  return dflt;
12954
13305
  if (t <= track[0].t)
12955
- return num(track[0].v, dflt);
13306
+ return num2(track[0].v, dflt);
12956
13307
  for (let i = 1;i < track.length; i++) {
12957
13308
  if (t <= track[i].t) {
12958
13309
  const p = track[i - 1];
12959
13310
  const q = track[i];
12960
13311
  const span = q.t - p.t;
12961
13312
  if (span <= 0)
12962
- return num(q.v, dflt);
13313
+ return num2(q.v, dflt);
12963
13314
  const k = (t - p.t) / span;
12964
- 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;
12965
13316
  }
12966
13317
  }
12967
- return num(track[track.length - 1].v, dflt);
13318
+ return num2(track[track.length - 1].v, dflt);
12968
13319
  };
12969
13320
  const times = new Set;
12970
13321
  for (const k of ta ?? [])
@@ -12985,17 +13336,17 @@ function sampleTrack(track, t, def) {
12985
13336
  if (!track || track.length === 0)
12986
13337
  return def;
12987
13338
  if (t <= track[0].t)
12988
- return num(track[0].v, def);
13339
+ return num2(track[0].v, def);
12989
13340
  for (let i = 1;i < track.length; i++) {
12990
13341
  const a = track[i - 1];
12991
13342
  const b = track[i];
12992
13343
  if (t <= b.t) {
12993
13344
  const span = Math.max(b.t - a.t, 0.000001);
12994
13345
  const k = (t - a.t) / span;
12995
- 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;
12996
13347
  }
12997
13348
  }
12998
- return num(track[track.length - 1].v, def);
13349
+ return num2(track[track.length - 1].v, def);
12999
13350
  }
13000
13351
  function phasePair(i) {
13001
13352
  return [Math.sin(i * 2.399), Math.cos(i * 3.11)];
@@ -13023,13 +13374,13 @@ function baseRot(anim, t) {
13023
13374
  return sampleTrack(anim.rot, t, 0);
13024
13375
  }
13025
13376
  function opWindow(fx, ly) {
13026
- const t0 = num(fx.t0, num(ly.in, 0));
13027
- 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));
13028
13379
  return [t0, t1];
13029
13380
  }
13030
13381
  function bakeLayerOps(ly) {
13031
13382
  const anim = ly.anim ?? {};
13032
- 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)];
13033
13384
  const tracks = [];
13034
13385
  const warnings = [];
13035
13386
  for (const fx of ly.fx ?? []) {
@@ -13045,8 +13396,8 @@ function bakeLayerOps(ly) {
13045
13396
  const times = [];
13046
13397
  const values = [];
13047
13398
  for (const k of tr) {
13048
- const t = num(k.t, 0);
13049
- const v = num(k.v, 1);
13399
+ const t = num2(k.t, 0);
13400
+ const v = num2(k.v, 1);
13050
13401
  const [bx, by] = baseScale(anim, t);
13051
13402
  times.push(round(t, 3));
13052
13403
  values.push([round(bx * v * 100, 3), round(by * v * 100, 3)]);
@@ -13059,9 +13410,9 @@ function bakeLayerOps(ly) {
13059
13410
  if (op === "flicker") {
13060
13411
  if (t1 - t0 < 0.05)
13061
13412
  continue;
13062
- let freq = num(fx.freq, 8);
13413
+ let freq = num2(fx.freq, 8);
13063
13414
  freq = clamp(freq >= 0.5 ? freq : freq * 30, 0.5, 20);
13064
- const mag = clamp(num(fx.mag, 0.6), 0.05, 1);
13415
+ const mag = clamp(num2(fx.mag, 0.6), 0.05, 1);
13065
13416
  const hi = 1 + mag * 0.8;
13066
13417
  const lo = Math.max(0.3, 1 - mag * 0.6);
13067
13418
  const n = Math.min(dn(Math.max(2, Math.floor((t1 - t0) * freq * 2))), 160);
@@ -13080,8 +13431,8 @@ function bakeLayerOps(ly) {
13080
13431
  if (t1 - t0 < 0.05)
13081
13432
  continue;
13082
13433
  if (op === "shake") {
13083
- const amp0 = num(fx.mag ?? fx.amp, 18);
13084
- 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);
13085
13436
  const n = Math.min(dn(Math.max(2, Math.floor((t1 - t0) * freq))), 160);
13086
13437
  const tr = fx.tracks?.mag;
13087
13438
  const seqX = [];
@@ -13111,9 +13462,9 @@ function bakeLayerOps(ly) {
13111
13462
  }
13112
13463
  if (op === "oscillate") {
13113
13464
  if (fx.rot === true) {
13114
- const a1 = num(fx.a1, -3);
13115
- const a2 = num(fx.a2, 3);
13116
- 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);
13117
13468
  const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 120);
13118
13469
  const times = [round(t0, 3)];
13119
13470
  const values = [[round(baseRot(anim, t0), 3)]];
@@ -13130,9 +13481,9 @@ function bakeLayerOps(ly) {
13130
13481
  }
13131
13482
  tracks.push({ prop: "rotation", times, values, window: [t0, t1] });
13132
13483
  } else {
13133
- const mag0 = num(fx.mag, 40);
13134
- const ang = num(fx.angle, 90) * Math.PI / 180;
13135
- 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);
13136
13487
  const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 160);
13137
13488
  const tr = fx.tracks?.mag;
13138
13489
  const times = [round(t0, 3)];
@@ -13164,13 +13515,13 @@ function bakeLayerOps(ly) {
13164
13515
  let lo;
13165
13516
  let hi;
13166
13517
  if (fx.amp != null) {
13167
- lo = 1 - num(fx.amp, 0);
13168
- hi = 1 + num(fx.amp, 0);
13518
+ lo = 1 - num2(fx.amp, 0);
13519
+ hi = 1 + num2(fx.amp, 0);
13169
13520
  } else {
13170
- lo = num(fx.min, 1);
13171
- hi = num(fx.max, 1.06);
13521
+ lo = num2(fx.min, 1);
13522
+ hi = num2(fx.max, 1.06);
13172
13523
  }
13173
- const freq = clamp(num(fx.freq, 2), 0.2, 20);
13524
+ const freq = clamp(num2(fx.freq, 2), 0.2, 20);
13174
13525
  const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 160);
13175
13526
  const tr = fx.tracks?.max;
13176
13527
  const times = [round(t0, 3)];
@@ -13332,7 +13683,7 @@ function positionBaseKeys(anim, pos) {
13332
13683
  function scaleBaseKeys(anim, coverExpr) {
13333
13684
  const sc = (v) => coverExpr ? `${r37(v)}*${coverExpr}` : `${r37(v)}`;
13334
13685
  if (anim.scale?.length) {
13335
- 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 }));
13336
13687
  }
13337
13688
  if (anim.sx?.length || anim.sy?.length) {
13338
13689
  const merged = mergeChannelTracks(anim.sx, anim.sy, 1, 1);
@@ -13347,9 +13698,9 @@ function emitBakedOps(ctx, ly, layerVar, tf, coverExpr) {
13347
13698
  if (tracks.length === 0)
13348
13699
  return;
13349
13700
  const anim = ly.anim ?? {};
13350
- const pos = [num(ly.pos?.[0], 0), num(ly.pos?.[1], 0)];
13351
- const inn = num(ly.in, 0);
13352
- 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));
13353
13704
  const sc = (v) => coverExpr ? `${r37(v)}*${coverExpr}` : `${r37(v)}`;
13354
13705
  const brights = tracks.filter((t) => t.prop === "brightness");
13355
13706
  if (brights.length) {
@@ -13396,7 +13747,7 @@ function emitBakedOps(ctx, ly, layerVar, tf, coverExpr) {
13396
13747
  }
13397
13748
  const rotBaked = tracks.filter((t) => t.prop === "rotation");
13398
13749
  if (rotBaked.length) {
13399
- 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 }));
13400
13751
  const bakedKeys = [];
13401
13752
  const windows = [];
13402
13753
  for (const bt of rotBaked) {
@@ -13414,10 +13765,10 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13414
13765
  const c3 = ir.canvas;
13415
13766
  const id = ++ctx.layerSeq;
13416
13767
  const v = `ly${id}`;
13417
- const inn = num(ly.in, 0);
13418
- const out = Math.max(inn + 0.01, num(ly.out, c3.duration));
13419
- const px = num(ly.pos?.[0], c3.w / 2);
13420
- 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);
13421
13772
  const name = esc(`${ly.id || "L" + id} [${ly.type}]`);
13422
13773
  L.push(``);
13423
13774
  L.push(` // ---- 层 ${name} in=${r37(inn)} out=${r37(out)} ----`);
@@ -13439,9 +13790,9 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13439
13790
  const footageHit = ly.type === "image" || ly.type === "video" ? ctx.footage[ly.id] : undefined;
13440
13791
  const fgVar = footageHit ? ctx.footageVars.get(footageHit.path) : undefined;
13441
13792
  if (footageHit && fgVar) {
13442
- const slotW = Math.max(2, Math.round(num(ly.w, c3.w)));
13443
- const slotH = Math.max(2, Math.round(num(ly.h, c3.h)));
13444
- 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);
13445
13796
  const cvv = `cv${id}`;
13446
13797
  coverExpr = cvv;
13447
13798
  L.push(` // 素材注入(${esc(String(ly.type))}): footage srcOffset=${r37(srcOffset)}`);
@@ -13472,14 +13823,14 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13472
13823
  L.push(` } catch (e) {}`);
13473
13824
  } else if (ly.type === "shape") {
13474
13825
  const col = colorArr(ly.fill, [0.2, 0.33, 0.67]);
13475
- const w = Math.max(2, Math.round(num(ly.w, 400)));
13476
- 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)));
13477
13828
  if (ly.shape === "ellipse")
13478
13829
  L.push(` // TODO: 原层为椭圆形状,固态占位,可手动换 shape layer`);
13479
13830
  L.push(` var ${v} = ${cv}.layers.addSolid([${col.join(",")}], "${name}", ${w}, ${h}, 1, ${r37(c3.duration)});`);
13480
13831
  } else {
13481
- const w = Math.max(2, Math.round(num(ly.w, c3.w)));
13482
- 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)));
13483
13834
  L.push(` // 占位素材(${esc(String(ly.type))}${ly.asset ? ` asset=${esc(String(ly.asset))}` : ""}): 请替换为真实素材`);
13484
13835
  L.push(` var ${v} = ${cv}.layers.addSolid([0.35,0.35,0.4], "${name} [占位]", ${w}, ${h}, 1, ${r37(c3.duration)});`);
13485
13836
  }
@@ -13509,10 +13860,10 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13509
13860
  }
13510
13861
  }
13511
13862
  if (anim.rot?.length && !bakedProps.has("rotation")) {
13512
- 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 })));
13513
13864
  }
13514
13865
  if (anim.opacity?.length) {
13515
- 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 })));
13516
13867
  }
13517
13868
  if (anim.ls?.length) {
13518
13869
  L.push(` // TODO: letterspacing 轨未自动映射(AE 需 Animator>Tracking),共 ${anim.ls.length} 帧`);
@@ -13526,8 +13877,8 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
13526
13877
  }
13527
13878
  function emitGroupAnim(ctx, v, ly) {
13528
13879
  const anim = ly.anim ?? {};
13529
- const px = num(ly.pos?.[0], 0);
13530
- const py = num(ly.pos?.[1], 0);
13880
+ const px = num2(ly.pos?.[0], 0);
13881
+ const py = num2(ly.pos?.[1], 0);
13531
13882
  if (anim.x?.length || anim.y?.length) {
13532
13883
  const merged = mergeChannelTracks(anim.x, anim.y, px, py);
13533
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 })));
@@ -13611,10 +13962,10 @@ function madJsx(opts) {
13611
13962
  const subVars = [];
13612
13963
  windows.forEach((win, wi) => {
13613
13964
  const c3 = win.ir.canvas;
13614
- const sw = Math.max(4, Math.round(num(c3.w, 1920)));
13615
- const sh = Math.max(4, Math.round(num(c3.h, 1080)));
13616
- const sfps = Math.min(99, Math.max(1, num(c3.fps, 30)));
13617
- 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));
13618
13969
  const subName = esc(`${win.uid}-${win.seq}`);
13619
13970
  const subVar = `sub${wi}`;
13620
13971
  L.push(``);
@@ -13870,7 +14221,7 @@ function beatQuantized(natLens, analysis) {
13870
14221
 
13871
14222
  // src/lib/mad/data.ts
13872
14223
  import { createHash as createHash2 } from "node:crypto";
13873
- import { mkdir as mkdir10, readFile as readFile8, rename as rename2, rm, writeFile as writeFile10, readdir } from "node:fs/promises";
14224
+ import { mkdir as mkdir11, readFile as readFile8, rename as rename2, rm, writeFile as writeFile10, readdir as readdir2 } from "node:fs/promises";
13874
14225
  import { existsSync as existsSync22 } from "node:fs";
13875
14226
  import { join as join25 } from "node:path";
13876
14227
  function madCacheDir() {
@@ -13925,7 +14276,7 @@ function validateManifest(obj) {
13925
14276
  }
13926
14277
  async function cleanupOldVersions(cacheRoot, keepVersion, warn) {
13927
14278
  try {
13928
- const entries = await readdir(cacheRoot);
14279
+ const entries = await readdir2(cacheRoot);
13929
14280
  for (const e of entries) {
13930
14281
  const m = /^v(\d+)$/.exec(e);
13931
14282
  if (m && Number(m[1]) !== keepVersion) {
@@ -13980,7 +14331,7 @@ async function ensureMadData(opts, deps) {
13980
14331
  }
13981
14332
  throw new Error("首次使用需联网下载技法数据。请连网后重跑 `gtrk tool mad`(数据会缓存到本地,之后可离线出片)。");
13982
14333
  }
13983
- await mkdir10(verDir, { recursive: true });
14334
+ await mkdir11(verDir, { recursive: true });
13984
14335
  warn(`下载技法池数据 mad_pool(版本 v${version},约 ${Math.round(poolMeta.size / 1024)} KB)…`);
13985
14336
  const res = await deps.fetchFn(poolMeta.url);
13986
14337
  if (!res.ok)
@@ -13993,7 +14344,7 @@ async function ensureMadData(opts, deps) {
13993
14344
  warn(`技法池数据就绪(v${version})`);
13994
14345
  }
13995
14346
  if (online) {
13996
- await mkdir10(cacheRoot, { recursive: true });
14347
+ await mkdir11(cacheRoot, { recursive: true });
13997
14348
  await atomicWrite(snapshotPath, new Uint8Array(Buffer.from(JSON.stringify(manifest), "utf8")));
13998
14349
  await cleanupOldVersions(cacheRoot, version, warn);
13999
14350
  }
@@ -14010,7 +14361,7 @@ async function ensureMadData(opts, deps) {
14010
14361
 
14011
14362
  // src/lib/mad/pool.ts
14012
14363
  import { gunzipSync } from "node:zlib";
14013
- import { mkdir as mkdir11, readFile as readFile9 } from "node:fs/promises";
14364
+ import { mkdir as mkdir12, readFile as readFile9 } from "node:fs/promises";
14014
14365
  import { existsSync as existsSync23 } from "node:fs";
14015
14366
  import { join as join26 } from "node:path";
14016
14367
  function shardPath(verDir, shard) {
@@ -14049,7 +14400,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
14049
14400
  throw new Error(`IR 分片下载失败 HTTP ${res.status}:${url}`);
14050
14401
  const buf = new Uint8Array(await res.arrayBuffer());
14051
14402
  const s = decodeShard(buf);
14052
- await mkdir11(join26(verDir, "ir"), { recursive: true });
14403
+ await mkdir12(join26(verDir, "ir"), { recursive: true });
14053
14404
  await atomicWrite(path, buf);
14054
14405
  memo.set(shard, s);
14055
14406
  return s;
@@ -14262,7 +14613,7 @@ async function runMad(inputArg, opts, deps = {}) {
14262
14613
  const bgm = level <= 2 && bgmForTrack ? { path: bgmForTrack, markers } : undefined;
14263
14614
  const { jsx } = madJsx({ master, windows, header, bgm });
14264
14615
  const outDir = opts.out ? resolve13(opts.out) : join27(process.cwd(), `mad-${timestamp4(now)}`);
14265
- await mkdir12(outDir, { recursive: true });
14616
+ await mkdir13(outDir, { recursive: true });
14266
14617
  const jsxPath = join27(outDir, "mad.jsx");
14267
14618
  await writeFile11(jsxPath, jsx);
14268
14619
  const result = {
@@ -14409,7 +14760,7 @@ async function runMadInTool(inputArg, opts) {
14409
14760
 
14410
14761
  // src/commands/transcript.ts
14411
14762
  import { existsSync as existsSync25 } from "node:fs";
14412
- import { mkdir as mkdir13, rename as rename3, rm as rm2, stat as stat6, writeFile as writeFile12 } from "node:fs/promises";
14763
+ import { mkdir as mkdir14, rename as rename3, rm as rm2, stat as stat6, writeFile as writeFile12 } from "node:fs/promises";
14413
14764
  import { basename as basename14, dirname as dirname11, extname as extname8, join as join28, resolve as resolve14 } from "node:path";
14414
14765
 
14415
14766
  // src/lib/transcript.ts
@@ -14566,7 +14917,7 @@ function resolveTranscriptOutput(inputAbs, out) {
14566
14917
  return output;
14567
14918
  }
14568
14919
  async function writeMarkdownAtomic(path, markdown) {
14569
- await mkdir13(dirname11(path), { recursive: true });
14920
+ await mkdir14(dirname11(path), { recursive: true });
14570
14921
  const temp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
14571
14922
  try {
14572
14923
  await writeFile12(temp, markdown, "utf8");
@@ -14644,7 +14995,7 @@ function registerTranscript(program2) {
14644
14995
 
14645
14996
  // src/commands/music-visualizer.ts
14646
14997
  import { resolve as resolve15, join as join29, dirname as dirname12, basename as basename15, extname as extname9 } from "node:path";
14647
- import { mkdir as mkdir14, writeFile as writeFile13 } from "node:fs/promises";
14998
+ import { mkdir as mkdir15, writeFile as writeFile13 } from "node:fs/promises";
14648
14999
  import { existsSync as existsSync26 } from "node:fs";
14649
15000
  var TASK_TYPE5 = "music_visualizer";
14650
15001
  var PRICE_KEY2 = "music_visualizer";
@@ -14809,7 +15160,7 @@ async function runMusicVisualizer(audio, opts) {
14809
15160
  });
14810
15161
  const { taskId } = submitted;
14811
15162
  log.info(`task_id = ${taskId}`);
14812
- await mkdir14(outDir, { recursive: true });
15163
+ await mkdir15(outDir, { recursive: true });
14813
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));
14814
15165
  log.step("③ 云端处理中(每 5s 轮询)…");
14815
15166
  const result = await pollTask(cfg, TASK_TYPE5, taskId, (status, progress) => {