@gitruck/cli 0.2.22 → 0.2.23

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
@@ -4197,7 +4197,7 @@ var {
4197
4197
 
4198
4198
  // src/index.ts
4199
4199
  import { readFileSync as readFileSync6 } from "node:fs";
4200
- import { join as join30 } from "node:path";
4200
+ import { join as join31 } from "node:path";
4201
4201
 
4202
4202
  // src/lib/paths.ts
4203
4203
  import { dirname, join } from "node:path";
@@ -6833,6 +6833,68 @@ function assertDurationConsistent(originalDuration, artifactAbs, ffmpegPath, tol
6833
6833
  throw new Error(`抽出物时长(${got.toFixed(2)}s)与原片(${originalDuration.toFixed(2)}s)不一致(容差 ${tolSec}s),` + `疑似抽取异常,已中止上传`);
6834
6834
  }
6835
6835
  }
6836
+ function assFontNames(assText) {
6837
+ const names = new Set;
6838
+ for (const line2 of assText.split(/\r?\n/)) {
6839
+ if (!line2.startsWith("Style:"))
6840
+ continue;
6841
+ const font = line2.slice("Style:".length).split(",")[1]?.trim();
6842
+ if (font)
6843
+ names.add(font);
6844
+ }
6845
+ return [...names];
6846
+ }
6847
+ async function probeFontAvailable(font, ffmpegPath) {
6848
+ const { ffmpeg } = requireFfmpeg(ffmpegPath);
6849
+ try {
6850
+ await runFfmpeg(ffmpeg, [
6851
+ "-v",
6852
+ "error",
6853
+ "-f",
6854
+ "lavfi",
6855
+ "-i",
6856
+ "color=c=black:s=64x64:d=0.04",
6857
+ "-vf",
6858
+ `drawtext=font='${font}':text=A`,
6859
+ "-frames:v",
6860
+ "1",
6861
+ "-f",
6862
+ "null",
6863
+ "-"
6864
+ ]);
6865
+ return true;
6866
+ } catch (e) {
6867
+ const msg = e instanceof Error ? e.message : String(e);
6868
+ if (/valid font|font.*not found|Fontconfig error/i.test(msg))
6869
+ return false;
6870
+ return null;
6871
+ }
6872
+ }
6873
+ async function burnSubtitle(videoAbs, assAbs, outAbs, opts = {}) {
6874
+ const { ffmpeg } = requireFfmpeg(opts.ffmpegPath);
6875
+ const filterPath = assAbs.replace(/\\/g, "/").replace(/:/g, "\\:");
6876
+ await runFfmpeg(ffmpeg, [
6877
+ "-y",
6878
+ "-v",
6879
+ "error",
6880
+ "-i",
6881
+ videoAbs,
6882
+ "-vf",
6883
+ `ass='${filterPath}'`,
6884
+ "-c:v",
6885
+ opts.codec ?? "libx264",
6886
+ "-crf",
6887
+ String(opts.crf ?? 18),
6888
+ "-preset",
6889
+ "medium",
6890
+ "-c:a",
6891
+ "copy",
6892
+ "-movflags",
6893
+ "+faststart",
6894
+ outAbs
6895
+ ]);
6896
+ return outAbs;
6897
+ }
6836
6898
 
6837
6899
  // src/lib/materialize.ts
6838
6900
  import { join as join14, basename as basename5 } from "node:path";
@@ -7314,8 +7376,8 @@ async function runOralCut(input, opts) {
7314
7376
  }
7315
7377
 
7316
7378
  // src/commands/long2short.ts
7317
- import { resolve as resolve6, join as join17, dirname as dirname5, basename as basename8, extname as extname5 } from "node:path";
7318
- import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
7379
+ import { resolve as resolve6, join as join18, dirname as dirname6, basename as basename8, extname as extname5 } from "node:path";
7380
+ import { mkdir as mkdir7, writeFile as writeFile8 } from "node:fs/promises";
7319
7381
  import { existsSync as existsSync16 } from "node:fs";
7320
7382
 
7321
7383
  // src/lib/clip-brief.ts
@@ -7445,10 +7507,110 @@ function renderClipsOverview(clips, ctx) {
7445
7507
  `)}
7446
7508
  `;
7447
7509
  }
7510
+ var POLISH_LABEL = {
7511
+ split_screen: "智能分屏",
7512
+ camera: "克制运镜",
7513
+ speed: "整体调速",
7514
+ seam: "接缝过渡",
7515
+ subtitle: "智能字幕",
7516
+ subtitle_remap: "字幕时轴重映射",
7517
+ purify_source: "去除原字幕",
7518
+ unknown: "未具名润色项"
7519
+ };
7520
+ function renderProReport(clips, report, ctx) {
7521
+ const name = ctx.source.split(/[\/]/).pop() || ctx.source;
7522
+ const out = [`# ${name} · 长剪短精剪报告`, ""];
7523
+ out.push(`- 源片:\`${ctx.source}\``);
7524
+ out.push(`- 成片:${clips.length} 条`);
7525
+ const jc = report?.jump_cut;
7526
+ if (typeof jc === "boolean")
7527
+ out.push(`- 跳剪:${jc ? "开" : "关"}`);
7528
+ if (ctx.taskId)
7529
+ out.push(`- task_id:\`${ctx.taskId}\``);
7530
+ out.push("");
7531
+ out.push("| # | 标题 | 时长 | 评分 | 简介 |", "| --- | --- | --- | --- | --- |");
7532
+ for (const [i, clip] of clips.entries()) {
7533
+ const dur = clipDurationMs(clip);
7534
+ const score = num(clip.score);
7535
+ out.push(`| clip${i} | ${cell(str(clip.title) ?? "—")} | ${dur != null ? fmtTime(dur) : "—"} | ${score ?? "—"} | ${cell(str(clip.summary) ?? "—")} |`);
7536
+ }
7537
+ out.push("");
7538
+ const degraded = report?.degraded_items;
7539
+ const degRows = [];
7540
+ if (degraded && typeof degraded === "object" && !Array.isArray(degraded)) {
7541
+ for (const [k, v] of Object.entries(degraded)) {
7542
+ const names = list(v).map((x) => POLISH_LABEL[x] ?? x);
7543
+ if (names.length)
7544
+ degRows.push(`- **${k}**:${names.join("、")}`);
7545
+ }
7546
+ }
7547
+ if (degRows.length) {
7548
+ out.push("## ⚠️ 润色降级", "");
7549
+ out.push("以下片子照常出片,但列出的润色项**没有做成**——效果与预期不同,别当成完整成片直接发布。", "");
7550
+ out.push(...degRows, "");
7551
+ }
7552
+ for (const [i, clip] of clips.entries()) {
7553
+ const title = str(clip.title);
7554
+ out.push("---", "", `## clip${i}${title ? `「${title}」` : ""}`, "");
7555
+ const bits = [];
7556
+ const dur = clipDurationMs(clip);
7557
+ if (dur != null)
7558
+ bits.push(`时长 ${fmtTime(dur)}`);
7559
+ const segCount = clipSegmentCount(clip);
7560
+ if (segCount != null)
7561
+ bits.push(`${segCount} 个保留片段`);
7562
+ const span = sourceSpan(clip);
7563
+ if (span)
7564
+ bits.push(`源片 ${fmtTime(span[0])}–${fmtTime(span[1])}`);
7565
+ const f = ctx.clipFiles?.[i];
7566
+ if (f)
7567
+ bits.push(`成片 ${baseName(f)}`);
7568
+ if (bits.length)
7569
+ out.push(`- ${bits.join(" · ")}`, "");
7570
+ const score = num(clip.score);
7571
+ const reason = str(clip.score_reason);
7572
+ if (score != null || reason) {
7573
+ out.push("### 入选理由", "");
7574
+ out.push([score != null ? `**评分 ${score}**` : null, reason].filter(Boolean).join(" — "), "");
7575
+ }
7576
+ const summary = str(clip.summary);
7577
+ if (summary)
7578
+ out.push("### 简介", "", summary, "");
7579
+ const note = str(clip.jumpcut_note);
7580
+ if (note)
7581
+ out.push("### 跳剪", "", jumpcutText(note), "");
7582
+ const cats = [
7583
+ ["主题", list(clip.themes)],
7584
+ ["标签", list(clip.tags)],
7585
+ ["类型", list(clip.genres)],
7586
+ ["调性", list(clip.moods)]
7587
+ ];
7588
+ const shown = cats.filter(([, v]) => v.length);
7589
+ if (shown.length) {
7590
+ out.push("### 分类与调性", "");
7591
+ for (const [k, v] of shown)
7592
+ out.push(`- ${k}:${v.join("、")}`);
7593
+ out.push("");
7594
+ }
7595
+ const hl = Array.isArray(clip.highlight_words) ? clip.highlight_words : [];
7596
+ const hlRows = hl.map((h) => ({ text: str(h?.text), at: num(h?.begin_time) })).filter((h) => h.text);
7597
+ if (hlRows.length) {
7598
+ out.push("### 高光词(源片时码)", "");
7599
+ for (const h of hlRows)
7600
+ out.push(`- ${h.at != null ? `${fmtTime(h.at)} ` : ""}「${h.text}」`);
7601
+ out.push("");
7602
+ }
7603
+ }
7604
+ return `${out.join(`
7605
+ `).replace(/\n{3,}/g, `
7606
+
7607
+ `).trimEnd()}
7608
+ `;
7609
+ }
7448
7610
 
7449
7611
  // src/lib/tool-runner.ts
7450
- import { resolve as resolve5, join as join16, dirname as dirname4, basename as basename7, extname as extname4 } from "node:path";
7451
- import { mkdir as mkdir6, writeFile as writeFile6, stat as stat5 } from "node:fs/promises";
7612
+ import { resolve as resolve5, join as join17, dirname as dirname5, basename as basename7, extname as extname4 } from "node:path";
7613
+ import { mkdir as mkdir6, writeFile as writeFile7, stat as stat5 } from "node:fs/promises";
7452
7614
  import { createWriteStream, existsSync as existsSync15 } from "node:fs";
7453
7615
  import { Readable as Readable2 } from "node:stream";
7454
7616
  import { pipeline } from "node:stream/promises";
@@ -7534,11 +7696,13 @@ async function resolveToolPricing(priceKey, pricingContext, fetchFn = fetch) {
7534
7696
 
7535
7697
  // src/lib/tool-descriptors.ts
7536
7698
  import { readFileSync as readFileSync4 } from "node:fs";
7537
- import { extname as extname3, resolve as resolvePath } from "node:path";
7699
+ import { readFile as readFile5, writeFile as writeFile6 } from "node:fs/promises";
7700
+ import { dirname as dirname4, extname as extname3, join as join16, resolve as resolvePath } from "node:path";
7538
7701
  var MULTI_INPUT_KINDS = new Set(["images", "videos"]);
7539
7702
  var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif", ".tif", ".tiff", ".heic", ".heif", ".avif"];
7540
7703
  var VIDEO_EXTS = [".mp4", ".mov", ".mkv", ".webm", ".avi", ".m4v", ".flv", ".wmv", ".mpg", ".mpeg", ".ts", ".m2ts"];
7541
7704
  var AUDIO_EXTS = [".mp3", ".wav", ".m4a", ".aac", ".flac", ".ogg", ".opus", ".wma"];
7705
+ var isAudioFile = (p) => AUDIO_EXTS.includes(extname3(p).toLowerCase());
7542
7706
  var PUBLIC_VIDEO_EXTS = [
7543
7707
  ".mp4",
7544
7708
  ".avi",
@@ -8482,18 +8646,37 @@ var videoAiSubtitle = {
8482
8646
  { flag: "--subtitle-type <style>", desc: `字幕样式:${AI_SUBTITLE_TYPES.join("/")}(未传则用服务端默认)` },
8483
8647
  { flag: "--subtitle-color <color>", desc: `字幕颜色:${AI_SUBTITLE_COLORS.join("/")}(未传则用服务端默认)` }
8484
8648
  ],
8649
+ async preprocess(ctx) {
8650
+ const inputAbs = ctx.inputAbs;
8651
+ if (ctx.opts.needPure === true) {
8652
+ ctx.warn("--need-pure 需要画面(去原字幕是画面操作),本次整片上传;只要字幕文件时去掉该 flag 即可只传音频");
8653
+ return inputAbs;
8654
+ }
8655
+ if (isAudioFile(inputAbs))
8656
+ return inputAbs;
8657
+ const audio = await extractAudio(inputAbs, ctx.ffmpegPath);
8658
+ return audio;
8659
+ },
8485
8660
  buildPayload(fileId, ctx) {
8486
8661
  const language = ctx.opts.language == null ? "" : String(ctx.opts.language).trim();
8487
8662
  if (!language)
8488
8663
  throw new Error("--language 必填:请指定源语种代码(具体取值见云端 API 文档 / 服务端支持列表)");
8489
8664
  const payload = { file_id: fileId, language };
8665
+ const inputAbs = ctx.inputAbs;
8666
+ if (inputAbs && !isAudioFile(inputAbs)) {
8667
+ try {
8668
+ const geo = probeGeometry(inputAbs, ctx.ffmpegPath);
8669
+ if (geo.width > 0 && geo.height > 0)
8670
+ payload.video_size = [geo.width, geo.height];
8671
+ } catch {
8672
+ ctx.warn("探原片几何失败,本次不回传 video_size(服务端将按 1920x1080 兜底出字幕)");
8673
+ }
8674
+ }
8490
8675
  if (ctx.opts.translateLanguage != null) {
8491
8676
  const t = String(ctx.opts.translateLanguage).trim();
8492
8677
  if (t)
8493
8678
  payload.translate_language = t;
8494
8679
  }
8495
- if (ctx.opts.needRender === true)
8496
- payload.need_render = true;
8497
8680
  if (ctx.opts.needPure === true)
8498
8681
  payload.need_pure = true;
8499
8682
  if (ctx.opts.subtitleType != null) {
@@ -8523,10 +8706,135 @@ var videoAiSubtitle = {
8523
8706
  files.push({ url: pure, filename: `${ctx.baseName}-pure${extFromUrl(pure, ".mp4")}` });
8524
8707
  return files;
8525
8708
  },
8709
+ async postprocess(ctx, landed) {
8710
+ if (ctx.opts.needRender !== true)
8711
+ return;
8712
+ const inputAbs = ctx.inputAbs;
8713
+ if (isAudioFile(inputAbs)) {
8714
+ throw new Error("--need-render 需要视频输入:本次输入是音频文件,无画面可烧");
8715
+ }
8716
+ const ass = landed.find((p) => p.toLowerCase().endsWith(".ass"));
8717
+ if (!ass)
8718
+ throw new Error("未拉回 .ass 字幕文件,无法本地烧录");
8719
+ const fonts = assFontNames(await readFile5(ass, "utf8"));
8720
+ const missing = [];
8721
+ for (const f of fonts) {
8722
+ if (await probeFontAvailable(f, ctx.ffmpegPath) === false)
8723
+ missing.push(f);
8724
+ }
8725
+ if (missing.length) {
8726
+ throw new Error(`本机缺字幕模板所需字体:${missing.join("、")}。` + `装上后重跑即可(字幕文件已落地,无需重新提交任务)。CLI 不自带字体、也不用替代字体顶——` + `替代字体烧出来的成片观感与模板设计不符且难以察觉。`);
8727
+ }
8728
+ const out = join16(dirname4(ass), `${ctx.baseName}-subtitled.mp4`);
8729
+ await burnSubtitle(inputAbs, ass, out, { ffmpegPath: ctx.ffmpegPath });
8730
+ return [out];
8731
+ },
8526
8732
  mapResult(out) {
8527
8733
  return { summary: typeof out.summary === "string" ? out.summary : "", asr: out.asr ?? null };
8528
8734
  }
8529
8735
  };
8736
+ var PRO_DURATION_PREFS = ["auto", "short", "medium", "long"];
8737
+ var videoLong2ShortPro = {
8738
+ name: "video_long2short_pro",
8739
+ title: "长剪短·精剪(直接出片)",
8740
+ description: "长内容按语义抽多条高光短片并一键出成品:选段+跳剪+分屏内核,叠加模糊底画布适配/克制运镜/调速保音高/智能字幕。只出成片,不产工程文件——要可编辑工程请用 gtrk long2short(粗剪)。",
8741
+ kind: "cloud",
8742
+ input: { kind: "video", exts: PUBLIC_VIDEO_EXTS },
8743
+ priceKey: "video_long2short_pro",
8744
+ outputHint: "逐条高光成片 mp4 + 切片报告 clips.md",
8745
+ enabled: true,
8746
+ taskType: "video_long2short_pro",
8747
+ pollTimeoutMs: 4 * 60 * 60 * 1000,
8748
+ options: [
8749
+ { flag: "--language <code>", desc: "源语种代码(精剪必填;取值以服务端支持列表为准)" },
8750
+ { flag: "--output-language <code>", desc: "选段/文案的输出语种(未传则同源语种)" },
8751
+ { flag: "--main-topic <text>", desc: "主题引导(影响选段偏好)" },
8752
+ { flag: "--output-size <s>", desc: "成片画幅 9:16|16:9|1:1 或自定义 WxH(未传则服务端默认)" },
8753
+ { flag: "--no-jump-cut", desc: "关闭跳剪(默认开:片内去水词/冗余,只删不重排)" },
8754
+ { flag: "--duration-pref <p>", desc: `成片时长偏好 ${PRO_DURATION_PREFS.join("/")}(成片条数由内容语义决定、不可指定)` },
8755
+ { flag: "--max-clip-sec <n>", desc: "单条成片时长安全上限(秒;未传则服务端默认)" },
8756
+ { flag: "--split-screen", desc: "开启智能分屏(多人同框段合成分屏画面)" },
8757
+ { flag: "--split-orientation <o>", desc: "分屏方向 auto|lr|tb(未传则服务端默认)" },
8758
+ { flag: "--speed-factor <n>", desc: "整体调速倍率(保音高;未传则服务端默认)" },
8759
+ { flag: "--no-camera-move", desc: "关闭克制运镜(默认开:亚像素推/拉)" },
8760
+ { flag: "--no-subtitle", desc: "关闭智能字幕(默认开:成片内烧录字幕)" },
8761
+ { flag: "--subtitle-translate-language <code>", desc: "字幕译文语种(未传则单语字幕)" }
8762
+ ],
8763
+ buildPayload(fileId, ctx) {
8764
+ const o = ctx.opts;
8765
+ const language = o.language == null ? "" : String(o.language).trim();
8766
+ if (!language)
8767
+ throw new Error("--language 必填:请指定源语种代码(具体取值见云端 API 文档 / 服务端支持列表)");
8768
+ const p = { file_id: fileId, language };
8769
+ if (o.outputLanguage != null && String(o.outputLanguage).trim())
8770
+ p.output_language = String(o.outputLanguage).trim();
8771
+ if (o.mainTopic != null && String(o.mainTopic).trim())
8772
+ p.main_topic = String(o.mainTopic).trim();
8773
+ if (o.outputSize != null && String(o.outputSize).trim())
8774
+ p.output_size = String(o.outputSize).trim();
8775
+ if (o.jumpCut === false)
8776
+ p.jump_cut = false;
8777
+ const duration = {};
8778
+ if (o.durationPref != null && String(o.durationPref).trim())
8779
+ duration.pref = String(o.durationPref).trim();
8780
+ const mcs = parseCountFlag(o.maxClipSec, "--max-clip-sec");
8781
+ if (mcs != null)
8782
+ duration.max_clip_sec = mcs;
8783
+ if (Object.keys(duration).length)
8784
+ p.duration = duration;
8785
+ if (o.splitScreen === true) {
8786
+ const ss = { enable: true };
8787
+ if (o.splitOrientation != null && String(o.splitOrientation).trim())
8788
+ ss.orientation = String(o.splitOrientation).trim();
8789
+ p.split_screen = ss;
8790
+ }
8791
+ if (o.speedFactor != null) {
8792
+ const n = Number(o.speedFactor);
8793
+ if (!Number.isFinite(n))
8794
+ throw new Error(`--speed-factor 需要有限数值,拿到「${o.speedFactor}」`);
8795
+ p.speed = { factor: n };
8796
+ }
8797
+ if (o.cameraMove === false)
8798
+ p.camera_move = { enable: false };
8799
+ const sub = {};
8800
+ if (o.subtitle === false)
8801
+ sub.enable = false;
8802
+ if (o.subtitleTranslateLanguage != null && String(o.subtitleTranslateLanguage).trim()) {
8803
+ sub.translate_language = String(o.subtitleTranslateLanguage).trim();
8804
+ }
8805
+ if (Object.keys(sub).length)
8806
+ p.subtitle = sub;
8807
+ return p;
8808
+ },
8809
+ mapOutputs(out, _ctx) {
8810
+ const clips = Array.isArray(out.clips) ? out.clips : [];
8811
+ const files = [];
8812
+ for (const [i, clip] of clips.entries()) {
8813
+ const f = clip?.file;
8814
+ const url = typeof f?.download_url === "string" ? f.download_url : undefined;
8815
+ if (!url)
8816
+ continue;
8817
+ files.push({ url, filename: `clip${i}${extFromUrl(url, ".mp4")}` });
8818
+ }
8819
+ return files;
8820
+ },
8821
+ mapResult(out) {
8822
+ return { clips: out.clips ?? [], report: out.report ?? {} };
8823
+ },
8824
+ async postprocess(ctx, landed, out) {
8825
+ const clips = Array.isArray(out.clips) ? out.clips : [];
8826
+ if (!clips.length)
8827
+ return;
8828
+ const mp4s = landed.filter((p) => p.toLowerCase().endsWith(".mp4"));
8829
+ const byIndex = clips.map((_c, i) => mp4s.find((p) => new RegExp(`clip${i}.[a-z0-9]+$`, "i").test(p)));
8830
+ const dest = join16(dirname4(landed[0] ?? "."), "clips.md");
8831
+ await writeFile6(dest, renderProReport(clips, out.report ?? {}, {
8832
+ source: ctx.inputAbs ?? ctx.baseName,
8833
+ clipFiles: byIndex
8834
+ }), "utf8");
8835
+ return [dest];
8836
+ }
8837
+ };
8530
8838
  function parseCountFlag(v, flag) {
8531
8839
  if (v == null)
8532
8840
  return;
@@ -8818,6 +9126,7 @@ var TOOL_REGISTRY = [
8818
9126
  imageClassicTemplate,
8819
9127
  imageVerticalStitch,
8820
9128
  videoSplitScreen,
9129
+ videoLong2ShortPro,
8821
9130
  audioTtsClone,
8822
9131
  mad
8823
9132
  ];
@@ -9040,9 +9349,9 @@ function resolveOutDir(descriptor, inputAbs, out) {
9040
9349
  return resolve5(out);
9041
9350
  if (inputAbs) {
9042
9351
  const base = basename7(inputAbs, extname4(inputAbs));
9043
- return join16(dirname4(inputAbs), `${base}-${descriptor.name}`);
9352
+ return join17(dirname5(inputAbs), `${base}-${descriptor.name}`);
9044
9353
  }
9045
- return join16(process.cwd(), `${descriptor.name}-${timestamp2()}`);
9354
+ return join17(process.cwd(), `${descriptor.name}-${timestamp2()}`);
9046
9355
  }
9047
9356
  async function safeFingerprint(inputAbs) {
9048
9357
  try {
@@ -9131,7 +9440,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
9131
9440
  }
9132
9441
  await mkdir6(outDir, { recursive: true });
9133
9442
  const fingerprint2 = inputList ? await Promise.all(inputList.map((p) => safeFingerprint(p))) : inputAbs ? await safeFingerprint(inputAbs) : undefined;
9134
- await writeFile6(join16(outDir, "task.json"), JSON.stringify({
9443
+ await writeFile7(join17(outDir, "task.json"), JSON.stringify({
9135
9444
  tool: descriptor.name,
9136
9445
  taskType,
9137
9446
  taskId,
@@ -9153,7 +9462,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
9153
9462
  const errors = {};
9154
9463
  const items = descriptor.mapOutputs ? descriptor.mapOutputs(outputResult, ctx) : [];
9155
9464
  for (const it of items) {
9156
- const dest = join16(outDir, it.filename);
9465
+ const dest = join17(outDir, it.filename);
9157
9466
  try {
9158
9467
  await deps.downloadStream(it.url, dest);
9159
9468
  files.push(dest);
@@ -9161,11 +9470,20 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
9161
9470
  errors[it.filename] = e instanceof Error ? e.message : String(e);
9162
9471
  }
9163
9472
  }
9473
+ if (descriptor.postprocess) {
9474
+ try {
9475
+ const extra = await descriptor.postprocess(ctx, [...files], outputResult);
9476
+ if (Array.isArray(extra))
9477
+ files.push(...extra);
9478
+ } catch (e) {
9479
+ errors[`${descriptor.name}:postprocess`] = e instanceof Error ? e.message : String(e);
9480
+ }
9481
+ }
9164
9482
  let resultFile;
9165
9483
  const structured = descriptor.mapResult ? descriptor.mapResult(outputResult, ctx) : undefined;
9166
9484
  if (structured != null) {
9167
- resultFile = join16(outDir, "result-output.json");
9168
- await writeFile6(resultFile, JSON.stringify(structured, null, 2));
9485
+ resultFile = join17(outDir, "result-output.json");
9486
+ await writeFile7(resultFile, JSON.stringify(structured, null, 2));
9169
9487
  }
9170
9488
  if (items.length === 0 && resultFile == null) {
9171
9489
  errors["output"] = "任务完成但未解析到任何产物(下载链接与结构化结果均为空,output_result 形态异常)";
@@ -9183,7 +9501,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
9183
9501
  ...resultFile ? { resultFile } : {},
9184
9502
  ...Object.keys(errors).length ? { errors } : {}
9185
9503
  };
9186
- await writeFile6(join16(outDir, "result.json"), JSON.stringify({ ...result, finishedAt: new Date().toISOString() }, null, 2));
9504
+ await writeFile7(join17(outDir, "result.json"), JSON.stringify({ ...result, finishedAt: new Date().toISOString() }, null, 2));
9187
9505
  return result;
9188
9506
  }
9189
9507
 
@@ -9240,7 +9558,7 @@ async function copyClipDraftsToRoot(clipDirs, draftTarget, errors) {
9240
9558
  let complete = 0;
9241
9559
  let attempted = 0;
9242
9560
  for (const [i, dir] of clipDirs.entries()) {
9243
- const src = join17(dir, "jianying");
9561
+ const src = join18(dir, "jianying");
9244
9562
  if (!existsSync16(src)) {
9245
9563
  paths.push(null);
9246
9564
  continue;
@@ -9280,7 +9598,7 @@ async function runLong2Short(input, opts) {
9280
9598
  if (!formats.includes("gtrk"))
9281
9599
  formats.push("gtrk");
9282
9600
  const wantJianying = formats.some((f) => f === "jianying" || f === "capcut");
9283
- const outDir = resolve6(opts.out ?? join17(dirname5(inputAbs), `${projName}-long2short`));
9601
+ const outDir = resolve6(opts.out ?? join18(dirname6(inputAbs), `${projName}-long2short`));
9284
9602
  const outName = basename8(outDir);
9285
9603
  let draftRoot;
9286
9604
  if (wantJianying) {
@@ -9290,7 +9608,7 @@ async function runLong2Short(input, opts) {
9290
9608
  else
9291
9609
  log.warn("没找到剪映草稿目录 → 将只产 draft_content.json、缺 meta。可加 --jianying-draft-dir <你的草稿目录> 重跑。");
9292
9610
  }
9293
- const draftTarget = draftRoot ? join17(draftRoot, outName) : undefined;
9611
+ const draftTarget = draftRoot ? join18(draftRoot, outName) : undefined;
9294
9612
  log.step(`▶ 长剪短:${basename8(inputAbs)}(${opts.splitScreen ? "720p 代理 · 智能分屏" : "纯选段 · 音频上传"},格式 ${formats.join("/")})`);
9295
9613
  log.step("① 本地预处理(探几何 + 抽音频/720p 代理)…");
9296
9614
  const geo = probeGeometry(inputAbs, opts.ffmpegPath);
@@ -9311,7 +9629,7 @@ async function runLong2Short(input, opts) {
9311
9629
  const { taskId, fileId } = { taskId: submitted.taskId, fileId: submitted.fileId };
9312
9630
  log.info(`task_id = ${taskId}`);
9313
9631
  await mkdir7(outDir, { recursive: true });
9314
- await writeFile7(join17(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE2, fileId, source: inputAbs, formats, createdAt: new Date().toISOString() }, null, 2));
9632
+ await writeFile8(join18(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE2, fileId, source: inputAbs, formats, createdAt: new Date().toISOString() }, null, 2));
9315
9633
  log.step("④ 云端处理中(选段/跳剪" + (opts.splitScreen ? "/分屏" : "") + ",每 5s 轮询)…");
9316
9634
  const output = await pollToolTask(cfg, TASK_TYPE2, taskId, {
9317
9635
  timeoutMs: POLL_TIMEOUT_MS,
@@ -9334,9 +9652,9 @@ async function runLong2Short(input, opts) {
9334
9652
  continue;
9335
9653
  }
9336
9654
  if (!/^(?:[A-Za-z]:[\\/]|[\\/])/.test(dest))
9337
- dest = join17(dirname5(inputAbs), dest);
9655
+ dest = join18(dirname6(inputAbs), dest);
9338
9656
  try {
9339
- await mkdir7(dirname5(dest), { recursive: true });
9657
+ await mkdir7(dirname6(dest), { recursive: true });
9340
9658
  await download(url, dest);
9341
9659
  splitLanded++;
9342
9660
  } catch (e) {
@@ -9350,7 +9668,7 @@ async function runLong2Short(input, opts) {
9350
9668
  log.step(`⑥ 逐 clip 拉回三方工程(共 ${clips.length} 条)…`);
9351
9669
  const clipResults = [];
9352
9670
  for (const [i, clip] of clips.entries()) {
9353
- const clipDir = join17(outDir, `clip${i}`);
9671
+ const clipDir = join18(outDir, `clip${i}`);
9354
9672
  const { files: clipFiles, ...clipMeta } = clip;
9355
9673
  try {
9356
9674
  const r = await materializeResult({
@@ -9391,13 +9709,13 @@ async function runLong2Short(input, opts) {
9391
9709
  for (const [i, cr] of clipResults.entries()) {
9392
9710
  try {
9393
9711
  await mkdir7(cr.dir, { recursive: true });
9394
- await writeFile7(join17(cr.dir, "clip.md"), renderClipBrief(clips[i], i, cr.files));
9712
+ await writeFile8(join18(cr.dir, "clip.md"), renderClipBrief(clips[i], i, cr.files));
9395
9713
  } catch (e) {
9396
9714
  errors[`clip${i}:brief`] = e instanceof Error ? e.message : String(e);
9397
9715
  }
9398
9716
  }
9399
9717
  try {
9400
- await writeFile7(join17(outDir, "clips.md"), renderClipsOverview(clips, {
9718
+ await writeFile8(join18(outDir, "clips.md"), renderClipsOverview(clips, {
9401
9719
  source: inputAbs,
9402
9720
  jumpCut: opts.jumpCut !== false,
9403
9721
  splitMaterials: { landed: splitLanded, total: manifest.length },
@@ -9407,7 +9725,7 @@ async function runLong2Short(input, opts) {
9407
9725
  } catch (e) {
9408
9726
  errors["clips.md"] = e instanceof Error ? e.message : String(e);
9409
9727
  }
9410
- await writeFile7(join17(outDir, "report.json"), JSON.stringify(output.report ?? {}, null, 2));
9728
+ await writeFile8(join18(outDir, "report.json"), JSON.stringify(output.report ?? {}, null, 2));
9411
9729
  const ok = Object.keys(errors).length === 0 && clipResults.some((c3) => c3.ok);
9412
9730
  const rootResult = {
9413
9731
  ok,
@@ -9418,10 +9736,10 @@ async function runLong2Short(input, opts) {
9418
9736
  outDir,
9419
9737
  clips: clipResults,
9420
9738
  splitMaterials: { landed: splitLanded, total: manifest.length },
9421
- reportFile: join17(outDir, "report.json"),
9739
+ reportFile: join18(outDir, "report.json"),
9422
9740
  ...Object.keys(errors).length ? { errors } : {}
9423
9741
  };
9424
- await writeFile7(join17(outDir, "result.json"), JSON.stringify({ ...rootResult, finishedAt: new Date().toISOString() }, null, 2));
9742
+ await writeFile8(join18(outDir, "result.json"), JSON.stringify({ ...rootResult, finishedAt: new Date().toISOString() }, null, 2));
9425
9743
  if (opts.json)
9426
9744
  console.log(JSON.stringify(rootResult));
9427
9745
  if (opts.open) {
@@ -9437,7 +9755,7 @@ async function runLong2Short(input, opts) {
9437
9755
  }
9438
9756
 
9439
9757
  // src/commands/oralcut-result.ts
9440
- import { resolve as resolve7, join as join18 } from "node:path";
9758
+ import { resolve as resolve7, join as join19 } from "node:path";
9441
9759
  var TASK_TYPE3 = "cli/video_oral_cut_for_cli";
9442
9760
  function timestamp3() {
9443
9761
  const d = new Date;
@@ -9471,7 +9789,7 @@ async function runOralCutResult(taskId, opts) {
9471
9789
  const pct = got.progress != null ? ` ${Math.round(got.progress)}%` : "";
9472
9790
  throw new Error(`任务尚未完成(当前 ${got.status || "未知"}${pct}),暂无法取回结果;请稍后再试。`);
9473
9791
  }
9474
- const outDir = resolve7(opts.out ?? join18(process.cwd(), `${taskId}-video-project-${timestamp3()}`));
9792
+ const outDir = resolve7(opts.out ?? join19(process.cwd(), `${taskId}-video-project-${timestamp3()}`));
9475
9793
  const draftDir = resolveJianyingDraftDir(opts.jianyingDraftDir);
9476
9794
  await materializeResult({
9477
9795
  outDir,
@@ -9531,7 +9849,7 @@ function registerUpgrade(program2) {
9531
9849
  }
9532
9850
 
9533
9851
  // src/commands/render.ts
9534
- import { resolve as resolve8, dirname as dirname6, join as join19, basename as basename9, extname as extname6 } from "node:path";
9852
+ import { resolve as resolve8, dirname as dirname7, join as join20, basename as basename9, extname as extname6 } from "node:path";
9535
9853
  import { existsSync as existsSync17 } from "node:fs";
9536
9854
  function registerRender(program2) {
9537
9855
  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) => {
@@ -9540,7 +9858,7 @@ function registerRender(program2) {
9540
9858
  const gtrkAbs = resolve8(gtrk);
9541
9859
  if (!existsSync17(gtrkAbs))
9542
9860
  throw new Error(`gtrk 工程不存在:${gtrkAbs}`);
9543
- const outMp4 = resolve8(opts.out ?? join19(dirname6(gtrkAbs), `${basename9(gtrkAbs, extname6(gtrkAbs))}.mp4`));
9861
+ const outMp4 = resolve8(opts.out ?? join20(dirname7(gtrkAbs), `${basename9(gtrkAbs, extname6(gtrkAbs))}.mp4`));
9544
9862
  log.step(`▶ 本地渲染:${basename9(gtrkAbs)} → ${basename9(outMp4)}`);
9545
9863
  const project = await readGtrkFile(gtrkAbs);
9546
9864
  const result = await renderGtrk(project, outMp4, {
@@ -9556,7 +9874,7 @@ function registerRender(program2) {
9556
9874
  log.tickEnd();
9557
9875
  log.ok(`渲染完成:${outMp4}(${result.duration.toFixed(1)}s)`);
9558
9876
  if (opts.open)
9559
- openFolder(dirname6(outMp4));
9877
+ openFolder(dirname7(outMp4));
9560
9878
  if (opts.json) {
9561
9879
  console.log(JSON.stringify({ ok: true, output: outMp4, duration: result.duration }));
9562
9880
  }
@@ -9564,14 +9882,14 @@ function registerRender(program2) {
9564
9882
  }
9565
9883
 
9566
9884
  // src/commands/split.ts
9567
- import { resolve as resolve9, join as join21, dirname as dirname8, basename as basename11 } from "node:path";
9885
+ import { resolve as resolve9, join as join22, dirname as dirname9, basename as basename11 } from "node:path";
9568
9886
  import { existsSync as existsSync18 } from "node:fs";
9569
- import { readFile as readFile5, writeFile as writeFile8, mkdir as mkdir8 } from "node:fs/promises";
9887
+ import { readFile as readFile6, writeFile as writeFile9, mkdir as mkdir8 } from "node:fs/promises";
9570
9888
  import { createHash } from "node:crypto";
9571
9889
 
9572
9890
  // src/lib/gtrk-writeback.ts
9573
9891
  import { readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "node:fs";
9574
- import { dirname as dirname7, join as join20, basename as basename10 } from "node:path";
9892
+ import { dirname as dirname8, join as join21, basename as basename10 } from "node:path";
9575
9893
  import { randomBytes as randomBytes2 } from "node:crypto";
9576
9894
  function readGtrk(path) {
9577
9895
  const raw = readFileSync5(path, "utf8");
@@ -9598,7 +9916,7 @@ function writeStructMetaSplit(path, gtrk, splitObj, expectedMtimeMs) {
9598
9916
  }
9599
9917
  const nextStructMeta = { ...gtrk.struct_meta ?? {}, split: splitObj };
9600
9918
  const next = { ...gtrk, struct_meta: nextStructMeta };
9601
- const tmp = join20(dirname7(path), `.${basename10(path)}.${randomBytes2(6).toString("hex")}.tmp`);
9919
+ const tmp = join21(dirname8(path), `.${basename10(path)}.${randomBytes2(6).toString("hex")}.tmp`);
9602
9920
  try {
9603
9921
  writeFileSync2(tmp, JSON.stringify(next, null, 2));
9604
9922
  renameSync(tmp, path);
@@ -9614,7 +9932,7 @@ function writeGtrkAtomic(path, next, expectedMtimeMs) {
9614
9932
  if (cur !== expectedMtimeMs) {
9615
9933
  throw new Error("工程文件在 matrix 运行期间被外部修改(保存冲突),已拒绝写入;请关闭客户端未保存的工程或重跑(plan 与已下载代理均保留)");
9616
9934
  }
9617
- const tmp = join20(dirname7(path), `.${basename10(path)}.${randomBytes2(6).toString("hex")}.tmp`);
9935
+ const tmp = join21(dirname8(path), `.${basename10(path)}.${randomBytes2(6).toString("hex")}.tmp`);
9618
9936
  try {
9619
9937
  writeFileSync2(tmp, JSON.stringify(next, null, 2));
9620
9938
  renameSync(tmp, path);
@@ -9642,7 +9960,7 @@ function resolvePaths(opts) {
9642
9960
  if (opts.gtrk) {
9643
9961
  gtrkPath = resolve9(opts.gtrk);
9644
9962
  } else if (project) {
9645
- gtrkPath = firstExisting([join21(project, "gtrk", "project.gtrk"), join21(project, "project.gtrk")]) ?? join21(project, "gtrk", "project.gtrk");
9963
+ gtrkPath = firstExisting([join22(project, "gtrk", "project.gtrk"), join22(project, "project.gtrk")]) ?? join22(project, "gtrk", "project.gtrk");
9646
9964
  } else {
9647
9965
  throw new Error("需 --project <目录> 或显式 --gtrk <path>");
9648
9966
  }
@@ -9653,11 +9971,11 @@ function resolvePaths(opts) {
9653
9971
  transcriptPath = resolve9(opts.transcript);
9654
9972
  else if (project)
9655
9973
  transcriptPath = firstExisting([
9656
- join21(project, "transcript", "transcript.json"),
9657
- join21(project, "json", "transcript.json"),
9658
- join21(project, "transcript.json")
9974
+ join22(project, "transcript", "transcript.json"),
9975
+ join22(project, "json", "transcript.json"),
9976
+ join22(project, "transcript.json")
9659
9977
  ]);
9660
- const baseDir = project ?? dirname8(gtrkPath);
9978
+ const baseDir = project ?? dirname9(gtrkPath);
9661
9979
  return { baseDir, gtrkPath, transcriptPath };
9662
9980
  }
9663
9981
  function slugify2(name) {
@@ -9665,7 +9983,7 @@ function slugify2(name) {
9665
9983
  return s || "project";
9666
9984
  }
9667
9985
  async function loadTranscript(path) {
9668
- const t = JSON.parse(await readFile5(path, "utf8"));
9986
+ const t = JSON.parse(await readFile6(path, "utf8"));
9669
9987
  if (!t || !Array.isArray(t.utterances) || typeof t.material_id !== "string" || typeof t.text_hash !== "string") {
9670
9988
  throw new Error(`transcript.json 结构异常(缺 utterances/material_id/text_hash):${path}`);
9671
9989
  }
@@ -9686,10 +10004,10 @@ async function runView(baseDir, gtrkPath, transcriptPath, opts) {
9686
10004
  const transcript = await loadTranscript(transcriptPath);
9687
10005
  const { gtrk } = readGtrk(gtrkPath);
9688
10006
  const view = projectTranscript(transcript, gtrk, { words: opts.words });
9689
- const splitDir = join21(baseDir, "split");
10007
+ const splitDir = join22(baseDir, "split");
9690
10008
  await mkdir8(splitDir, { recursive: true });
9691
- const viewPath = join21(splitDir, "view.json");
9692
- await writeFile8(viewPath, JSON.stringify(view, null, 2));
10009
+ const viewPath = join22(splitDir, "view.json");
10010
+ await writeFile9(viewPath, JSON.stringify(view, null, 2));
9693
10011
  const dropped = view.utterances.filter((u) => u.dropped).length;
9694
10012
  log.ok(`投影视图已生成:${viewPath}(${view.utterances.length} 条,其中 ${dropped} 条被剪)`);
9695
10013
  const result = {
@@ -9711,7 +10029,7 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
9711
10029
  if (!transcriptPath || !existsSync18(transcriptPath))
9712
10030
  throw new Error(TRANSCRIPT_MISSING);
9713
10031
  log.step("▶ 校验拆分稿并落地…");
9714
- const doc = JSON.parse(await readFile5(splitdocPath, "utf8"));
10032
+ const doc = JSON.parse(await readFile6(splitdocPath, "utf8"));
9715
10033
  const transcript = await loadTranscript(transcriptPath);
9716
10034
  const { gtrk, mtimeMs } = readGtrk(gtrkPath);
9717
10035
  assertGtrkV1(gtrk);
@@ -9745,14 +10063,14 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
9745
10063
  }
9746
10064
  });
9747
10065
  writeStructMetaSplit(gtrkPath, gtrk, landing.split, mtimeMs);
9748
- const splitDir = join21(baseDir, "split");
10066
+ const splitDir = join22(baseDir, "split");
9749
10067
  await mkdir8(splitDir, { recursive: true });
9750
- const dispatchPath = join21(splitDir, "dispatch.json");
9751
- await writeFile8(dispatchPath, JSON.stringify(landing.dispatch, null, 2));
10068
+ const dispatchPath = join22(splitDir, "dispatch.json");
10069
+ await writeFile9(dispatchPath, JSON.stringify(landing.dispatch, null, 2));
9752
10070
  let mdPath = null;
9753
10071
  if (opts.md) {
9754
- mdPath = join21(splitDir, "visual-split.md");
9755
- await writeFile8(mdPath, renderSplitMarkdown(doc, landing, { projectSlug, projectedAt }));
10072
+ mdPath = join22(splitDir, "visual-split.md");
10073
+ await writeFile9(mdPath, renderSplitMarkdown(doc, landing, { projectSlug, projectedAt }));
9756
10074
  }
9757
10075
  log.ok(`落地完成:${landing.split.beats.length}/${doc.beats.length} beat 落轨` + `(MG ${landing.dispatch.mg.length} · FILM_BROLL ${landing.dispatch.film_broll.length} · AI_DRAMA ${landing.dispatch.ai_drama.length})`);
9758
10076
  for (const s of landing.skipped)
@@ -9783,9 +10101,9 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
9783
10101
  }
9784
10102
 
9785
10103
  // src/commands/matrix.ts
9786
- import { resolve as resolve11, join as join22, dirname as dirname9, basename as basename12 } from "node:path";
10104
+ import { resolve as resolve11, join as join23, dirname as dirname10, basename as basename12 } from "node:path";
9787
10105
  import { existsSync as existsSync20 } from "node:fs";
9788
- import { readFile as readFile6, writeFile as writeFile9, mkdir as mkdir9, rename } from "node:fs/promises";
10106
+ import { readFile as readFile7, writeFile as writeFile10, mkdir as mkdir9, rename } from "node:fs/promises";
9789
10107
 
9790
10108
  // src/lib/solid-png.ts
9791
10109
  import { deflateSync } from "node:zlib";
@@ -10925,16 +11243,16 @@ async function runPlanMode(cfg, tier, broll, overrides, columnId, opts) {
10925
11243
  let baseDir;
10926
11244
  if (opts.dispatch) {
10927
11245
  dispatchPath = resolve11(opts.dispatch);
10928
- baseDir = dirname9(dirname9(dispatchPath));
11246
+ baseDir = dirname10(dirname10(dispatchPath));
10929
11247
  } else if (opts.project) {
10930
11248
  baseDir = resolve11(opts.project);
10931
- dispatchPath = join22(baseDir, "split", "dispatch.json");
11249
+ dispatchPath = join23(baseDir, "split", "dispatch.json");
10932
11250
  } else {
10933
11251
  throw new Error('需 --project <目录> 或显式 --dispatch <path>(ad-hoc 检索用:gtrk matrix search "<query>")');
10934
11252
  }
10935
11253
  if (!existsSync20(dispatchPath))
10936
11254
  throw new Error(`找不到派单清单:${dispatchPath}(先跑 gtrk split <拆分稿> 落地派单)`);
10937
- const dispatch = JSON.parse(await readFile6(dispatchPath, "utf8"));
11255
+ const dispatch = JSON.parse(await readFile7(dispatchPath, "utf8"));
10938
11256
  const rawQueue = Array.isArray(dispatch.film_broll) ? dispatch.film_broll : [];
10939
11257
  const earlyGtrkPath = locateGtrk(baseDir);
10940
11258
  let earlyGtrk;
@@ -10997,10 +11315,10 @@ async function runPlanMode(cfg, tier, broll, overrides, columnId, opts) {
10997
11315
  columnId,
10998
11316
  beats
10999
11317
  });
11000
- const splitDir = join22(baseDir, "split");
11318
+ const splitDir = join23(baseDir, "split");
11001
11319
  await mkdir9(splitDir, { recursive: true });
11002
- const planPath = join22(splitDir, "broll-plan.json");
11003
- await writeFile9(planPath, JSON.stringify(plan, null, 2));
11320
+ const planPath = join23(splitDir, "broll-plan.json");
11321
+ await writeFile10(planPath, JSON.stringify(plan, null, 2));
11004
11322
  log.ok(`候选清单已生成:${planPath}(${beats.length} beat · ${okCount}/${totalQueries} query 成功 · ${resultCount} 条候选)`);
11005
11323
  log.info("清单只含引用不含素材:cover_url 可直接预览;url 带签名默认 24h 过期,过期重跑本命令即重签。");
11006
11324
  const layN = parseLay(opts.lay);
@@ -11047,13 +11365,13 @@ function parseScoreFloor(raw) {
11047
11365
  return SCORE_FLOOR_DEFAULT;
11048
11366
  }
11049
11367
  function locateGtrk(baseDir) {
11050
- const cands = [join22(baseDir, "gtrk", "project.gtrk"), join22(baseDir, "project.gtrk")];
11368
+ const cands = [join23(baseDir, "gtrk", "project.gtrk"), join23(baseDir, "project.gtrk")];
11051
11369
  return cands.find((p) => existsSync20(p));
11052
11370
  }
11053
11371
  async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRelay, reproj) {
11054
11372
  const gtrkPath = locateGtrk(baseDir);
11055
11373
  if (!gtrkPath) {
11056
- log.warn(`未找到工程文件(${join22(baseDir, "gtrk", "project.gtrk")}),跳过铺轨——plan 已产出,可后续在有工程的目录重跑`);
11374
+ log.warn(`未找到工程文件(${join23(baseDir, "gtrk", "project.gtrk")}),跳过铺轨——plan 已产出,可后续在有工程的目录重跑`);
11057
11375
  return;
11058
11376
  }
11059
11377
  const { gtrk, mtimeMs } = readGtrk(gtrkPath);
@@ -11061,8 +11379,8 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
11061
11379
  const { fills, clipIds } = planBeatFills(plan, layN, scoreFloor);
11062
11380
  const slotCount = [...fills.values()].flat().reduce((n, s) => n + s.length, 0);
11063
11381
  log.step(`▶ 候选铺轨(${layN} 轨 · 平铺 ${slotCount} 槽位 · ${clipIds.size} 个 clip,preview 代理)…`);
11064
- const gtrkDir = dirname9(gtrkPath);
11065
- const previewDir = join22(gtrkDir, ...BROLL_PREVIEW_DIR.split("/"));
11382
+ const gtrkDir = dirname10(gtrkPath);
11383
+ const previewDir = join23(gtrkDir, ...BROLL_PREVIEW_DIR.split("/"));
11066
11384
  await mkdir9(previewDir, { recursive: true });
11067
11385
  const prevSource = new Map;
11068
11386
  const prevBroll = gtrk.struct_meta?.broll;
@@ -11085,7 +11403,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
11085
11403
  if (!cand)
11086
11404
  continue;
11087
11405
  const rel = `${BROLL_PREVIEW_DIR}/${clipId}.mp4`;
11088
- const abs = join22(gtrkDir, ...rel.split("/"));
11406
+ const abs = join23(gtrkDir, ...rel.split("/"));
11089
11407
  if (existsSync20(abs)) {
11090
11408
  const prev = prevSource.get(clipId);
11091
11409
  if (prev !== "raw") {
@@ -11148,12 +11466,12 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
11148
11466
  const canvas = gtrk.video_size;
11149
11467
  const spec = { hex: BLACK_BED_HEX, width: canvas[0], height: canvas[1] };
11150
11468
  const rel = solidRelPath(spec);
11151
- const abs = join22(gtrkDir, ...rel.split("/"));
11469
+ const abs = join23(gtrkDir, ...rel.split("/"));
11152
11470
  try {
11153
11471
  if (!existsSync20(abs)) {
11154
- await mkdir9(dirname9(abs), { recursive: true });
11472
+ await mkdir9(dirname10(abs), { recursive: true });
11155
11473
  const tmp = `${abs}.tmp-${process.pid}`;
11156
- await writeFile9(tmp, encodeSolidPng(spec));
11474
+ await writeFile10(tmp, encodeSolidPng(spec));
11157
11475
  await rename(tmp, abs);
11158
11476
  }
11159
11477
  } catch (e) {
@@ -11216,7 +11534,7 @@ async function downloadProxy(cand, absPath, opts = {}) {
11216
11534
  if (previewUrl) {
11217
11535
  const bytes = await tryFetch(previewUrl);
11218
11536
  if (bytes) {
11219
- await writeFile9(absPath, bytes);
11537
+ await writeFile10(absPath, bytes);
11220
11538
  return "preview";
11221
11539
  }
11222
11540
  }
@@ -11224,7 +11542,7 @@ async function downloadProxy(cand, absPath, opts = {}) {
11224
11542
  return null;
11225
11543
  const raw = await tryFetch(cand.url);
11226
11544
  if (raw) {
11227
- await writeFile9(absPath, raw);
11545
+ await writeFile10(absPath, raw);
11228
11546
  log.warn(`clip ${cand.clip_id} 无 preview 代理,已回落原片(${(raw.length / 1048576).toFixed(1)}MB)`);
11229
11547
  return "raw";
11230
11548
  }
@@ -11247,7 +11565,7 @@ async function runAdhoc(query, cfg, tier, broll, overrides, columnId, opts) {
11247
11565
  };
11248
11566
  if (opts.out) {
11249
11567
  const outPath = resolve11(opts.out);
11250
- await writeFile9(outPath, JSON.stringify({ query, recalled: data.recalled, results }, null, 2));
11568
+ await writeFile10(outPath, JSON.stringify({ query, recalled: data.recalled, results }, null, 2));
11251
11569
  log.ok(`结果已落盘:${outPath}`);
11252
11570
  result.outPath = outPath;
11253
11571
  } else if (!opts.json) {
@@ -11266,9 +11584,9 @@ function slugify3(name) {
11266
11584
  }
11267
11585
 
11268
11586
  // src/commands/mg.ts
11269
- import { resolve as resolve12, join as join23, dirname as dirname10, basename as basename13 } from "node:path";
11587
+ import { resolve as resolve12, join as join24, dirname as dirname11, basename as basename13 } from "node:path";
11270
11588
  import { existsSync as existsSync21 } from "node:fs";
11271
- import { readFile as readFile7, mkdir as mkdir10, copyFile } from "node:fs/promises";
11589
+ import { readFile as readFile8, mkdir as mkdir10, copyFile } from "node:fs/promises";
11272
11590
 
11273
11591
  // src/lib/mg-lint.ts
11274
11592
  var CDN_OK = [/lib\.baomitu\.com/i, /cdnjs\.cloudflare\.com/i, /unpkg\.com/i];
@@ -12929,20 +13247,20 @@ async function runMg(words, opts) {
12929
13247
  function resolveDispatch(opts) {
12930
13248
  if (opts.dispatch) {
12931
13249
  const dispatchPath = resolve12(opts.dispatch);
12932
- return { dispatchPath, baseDir: dirname10(dirname10(dispatchPath)) };
13250
+ return { dispatchPath, baseDir: dirname11(dirname11(dispatchPath)) };
12933
13251
  }
12934
13252
  if (opts.project) {
12935
13253
  const baseDir = resolve12(opts.project);
12936
- return { dispatchPath: join23(baseDir, "split", "dispatch.json"), baseDir };
13254
+ return { dispatchPath: join24(baseDir, "split", "dispatch.json"), baseDir };
12937
13255
  }
12938
13256
  throw new Error("需 --project <目录> 或显式 --dispatch <path>");
12939
13257
  }
12940
13258
  function locateGtrk2(baseDir) {
12941
- return [join23(baseDir, "gtrk", "project.gtrk"), join23(baseDir, "project.gtrk")].find((p) => existsSync21(p));
13259
+ return [join24(baseDir, "gtrk", "project.gtrk"), join24(baseDir, "project.gtrk")].find((p) => existsSync21(p));
12942
13260
  }
12943
13261
  function locateSrcHtml(baseDir, compositionId) {
12944
13262
  for (const d of MG_SRC_DIRS) {
12945
- const p = join23(baseDir, d, `${compositionId}.html`);
13263
+ const p = join24(baseDir, d, `${compositionId}.html`);
12946
13264
  if (existsSync21(p))
12947
13265
  return p;
12948
13266
  }
@@ -12951,7 +13269,7 @@ function locateSrcHtml(baseDir, compositionId) {
12951
13269
  async function readMgQueue(dispatchPath) {
12952
13270
  if (!existsSync21(dispatchPath))
12953
13271
  throw new Error(`找不到派单清单:${dispatchPath}(先跑 gtrk split 落地派单)`);
12954
- const dispatch = JSON.parse(await readFile7(dispatchPath, "utf8"));
13272
+ const dispatch = JSON.parse(await readFile8(dispatchPath, "utf8"));
12955
13273
  const queue = dispatch.mg ?? dispatch.rrv_mg;
12956
13274
  return Array.isArray(queue) ? queue : [];
12957
13275
  }
@@ -13025,10 +13343,10 @@ async function runLay(opts) {
13025
13343
  const srcPath = locateSrcHtml(baseDir, q.composition_id);
13026
13344
  if (!srcPath) {
13027
13345
  skipped.push({ beat: q.beat, reason: "缺颗粒 HTML(未产出)" });
13028
- log.warn(`${q.beat}:缺 ${join23(baseDir, MG_SRC_DIRS[0], `${q.composition_id}.html`)},跳过`);
13346
+ log.warn(`${q.beat}:缺 ${join24(baseDir, MG_SRC_DIRS[0], `${q.composition_id}.html`)},跳过`);
13029
13347
  continue;
13030
13348
  }
13031
- const html = await readFile7(srcPath, "utf8");
13349
+ const html = await readFile8(srcPath, "utf8");
13032
13350
  const category = typeof q.category === "string" ? q.category : undefined;
13033
13351
  const slotDuration = Math.round((win.track_ed - win.track_st) * 1000) / 1000;
13034
13352
  const lint = lintParticle(html, {
@@ -13073,7 +13391,7 @@ async function runLay(opts) {
13073
13391
  });
13074
13392
  }
13075
13393
  if (!gtrkPath || !project) {
13076
- log.warn(`未找到工程文件(${join23(baseDir, "gtrk", "project.gtrk")}),跳过铺轨——lint 已完成`);
13394
+ log.warn(`未找到工程文件(${join24(baseDir, "gtrk", "project.gtrk")}),跳过铺轨——lint 已完成`);
13077
13395
  return done(opts, {
13078
13396
  ok: skipped.length === 0,
13079
13397
  mode: "lay",
@@ -13090,7 +13408,7 @@ async function runLay(opts) {
13090
13408
  });
13091
13409
  }
13092
13410
  const { gtrk, mtimeMs } = project;
13093
- const gtrkDir = dirname10(gtrkPath);
13411
+ const gtrkDir = dirname11(gtrkPath);
13094
13412
  const covered = new Set(items.map((it) => it.composition_id));
13095
13413
  const orphans = laidBefore.filter((id) => !covered.has(id));
13096
13414
  const inDispatch = new Set(allQueue.map((q) => q.composition_id));
@@ -13132,9 +13450,9 @@ async function runLay(opts) {
13132
13450
  if (opts.replaceAll && orphans.length > 0) {
13133
13451
  log.warn(`--replace-all:已显式授权重置整轨,轨上其余 ${orphans.length} 颗已铺颗粒将被剥离(不走增量保留)。`);
13134
13452
  }
13135
- await mkdir10(join23(gtrkDir, ...MG_ASSET_DIR.split("/")), { recursive: true });
13453
+ await mkdir10(join24(gtrkDir, ...MG_ASSET_DIR.split("/")), { recursive: true });
13136
13454
  for (const it of items) {
13137
- await copyFile(srcByComp.get(it.composition_id), join23(gtrkDir, ...it.html_rel.split("/")));
13455
+ await copyFile(srcByComp.get(it.composition_id), join24(gtrkDir, ...it.html_rel.split("/")));
13138
13456
  }
13139
13457
  const { next, summary, mg } = layMgTracks({ gtrk, items, generatedAt: new Date().toISOString(), keep });
13140
13458
  const written = withTimecodeSource(next, "mg", reproj);
@@ -13178,7 +13496,7 @@ async function runLint(args, opts) {
13178
13496
  const file = args[0];
13179
13497
  if (!file)
13180
13498
  throw new Error("用法:gtrk mg lint <particle.html> [--dispatch <path>]");
13181
- const html = await readFile7(resolve12(file), "utf8");
13499
+ const html = await readFile8(resolve12(file), "utf8");
13182
13500
  const nameId = basename13(file).replace(/\.html?$/i, "");
13183
13501
  let dispatchIds;
13184
13502
  let slotDuration;
@@ -13249,9 +13567,9 @@ function done(opts, result) {
13249
13567
  }
13250
13568
 
13251
13569
  // src/lib/mad/mad.ts
13252
- import { mkdir as mkdir13, writeFile as writeFile11 } from "node:fs/promises";
13570
+ import { mkdir as mkdir13, writeFile as writeFile12 } from "node:fs/promises";
13253
13571
  import { existsSync as existsSync24, statSync as statSync3 } from "node:fs";
13254
- import { resolve as resolve13, join as join27 } from "node:path";
13572
+ import { resolve as resolve13, join as join28 } from "node:path";
13255
13573
 
13256
13574
  // src/lib/convert/types.ts
13257
13575
  function num2(v, d = 0) {
@@ -14031,7 +14349,7 @@ function madJsx(opts) {
14031
14349
 
14032
14350
  // src/lib/mad/scan.ts
14033
14351
  import { readdirSync, statSync as statSync2 } from "node:fs";
14034
- import { extname as extname7, join as join24 } from "node:path";
14352
+ import { extname as extname7, join as join25 } from "node:path";
14035
14353
  var VIDEO_EXTS2 = new Set(defaultExtsFor("video") ?? []);
14036
14354
  function scanFolder(dirAbs, opts = {}) {
14037
14355
  const probe = opts.probe ?? probeGeometry;
@@ -14042,7 +14360,7 @@ function scanFolder(dirAbs, opts = {}) {
14042
14360
  } catch {
14043
14361
  throw new Error(`素材文件夹不存在或无法读取:${dirAbs}`);
14044
14362
  }
14045
- const files = entries.filter((n) => VIDEO_EXTS2.has(extname7(n).toLowerCase())).map((n) => join24(dirAbs, n)).filter((p) => {
14363
+ const files = entries.filter((n) => VIDEO_EXTS2.has(extname7(n).toLowerCase())).map((n) => join25(dirAbs, n)).filter((p) => {
14046
14364
  try {
14047
14365
  return statSync2(p).isFile();
14048
14366
  } catch {
@@ -14221,9 +14539,9 @@ function beatQuantized(natLens, analysis) {
14221
14539
 
14222
14540
  // src/lib/mad/data.ts
14223
14541
  import { createHash as createHash2 } from "node:crypto";
14224
- import { mkdir as mkdir11, readFile as readFile8, rename as rename2, rm, writeFile as writeFile10, readdir as readdir2 } from "node:fs/promises";
14542
+ import { mkdir as mkdir11, readFile as readFile9, rename as rename2, rm, writeFile as writeFile11, readdir as readdir2 } from "node:fs/promises";
14225
14543
  import { existsSync as existsSync22 } from "node:fs";
14226
- import { join as join25 } from "node:path";
14544
+ import { join as join26 } from "node:path";
14227
14545
  function madCacheDir() {
14228
14546
  return homeFile("mad-cache");
14229
14547
  }
@@ -14248,14 +14566,14 @@ async function fetchWithTimeout(fetchFn, url, timeoutMs) {
14248
14566
  }
14249
14567
  async function atomicWrite(dest, data) {
14250
14568
  const tmp = `${dest}.tmp-${process.pid}-${Date.now()}`;
14251
- await writeFile10(tmp, data);
14569
+ await writeFile11(tmp, data);
14252
14570
  await rename2(tmp, dest);
14253
14571
  }
14254
14572
  async function verifyFile(path, sha256) {
14255
14573
  if (!existsSync22(path))
14256
14574
  return false;
14257
14575
  try {
14258
- const buf = await readFile8(path);
14576
+ const buf = await readFile9(path);
14259
14577
  return sha256Hex(buf) === sha256;
14260
14578
  } catch {
14261
14579
  return false;
@@ -14280,7 +14598,7 @@ async function cleanupOldVersions(cacheRoot, keepVersion, warn) {
14280
14598
  for (const e of entries) {
14281
14599
  const m = /^v(\d+)$/.exec(e);
14282
14600
  if (m && Number(m[1]) !== keepVersion) {
14283
- await rm(join25(cacheRoot, e), { recursive: true, force: true }).catch(() => {});
14601
+ await rm(join26(cacheRoot, e), { recursive: true, force: true }).catch(() => {});
14284
14602
  }
14285
14603
  }
14286
14604
  } catch {}
@@ -14289,7 +14607,7 @@ async function ensureMadData(opts, deps) {
14289
14607
  const { cacheRoot, warn } = deps;
14290
14608
  const timeout = deps.manifestTimeoutMs ?? 8000;
14291
14609
  const mfUrl = deps.manifestUrl ?? manifestUrl();
14292
- const snapshotPath = join25(cacheRoot, "manifest.json");
14610
+ const snapshotPath = join26(cacheRoot, "manifest.json");
14293
14611
  let manifest = null;
14294
14612
  let online = false;
14295
14613
  try {
@@ -14308,7 +14626,7 @@ async function ensureMadData(opts, deps) {
14308
14626
  throw new Error("首次使用需联网下载技法数据。请连网后重跑 `gtrk tool mad`(数据会缓存到本地,之后可离线出片)。");
14309
14627
  }
14310
14628
  try {
14311
- manifest = validateManifest(JSON.parse(await readFile8(snapshotPath, "utf8")));
14629
+ manifest = validateManifest(JSON.parse(await readFile9(snapshotPath, "utf8")));
14312
14630
  } catch {
14313
14631
  throw new Error("本地 manifest 缓存损坏且当前离线。请连网重跑,或加 --refresh 强制重新下载。");
14314
14632
  }
@@ -14319,8 +14637,8 @@ async function ensureMadData(opts, deps) {
14319
14637
  }
14320
14638
  }
14321
14639
  const version = manifest.version;
14322
- const verDir = join25(cacheRoot, `v${version}`);
14323
- const poolPath = join25(verDir, "mad_pool.json");
14640
+ const verDir = join26(cacheRoot, `v${version}`);
14641
+ const poolPath = join26(verDir, "mad_pool.json");
14324
14642
  const poolMeta = manifest.datasets.mad_pool;
14325
14643
  const cacheValid = await verifyFile(poolPath, poolMeta.sha256);
14326
14644
  const needDownload = !!opts.refresh || !cacheValid;
@@ -14350,7 +14668,7 @@ async function ensureMadData(opts, deps) {
14350
14668
  }
14351
14669
  let pool;
14352
14670
  try {
14353
- pool = JSON.parse(await readFile8(poolPath, "utf8"));
14671
+ pool = JSON.parse(await readFile9(poolPath, "utf8"));
14354
14672
  if (!Array.isArray(pool))
14355
14673
  throw new Error("mad_pool 非数组");
14356
14674
  } catch (e) {
@@ -14361,11 +14679,11 @@ async function ensureMadData(opts, deps) {
14361
14679
 
14362
14680
  // src/lib/mad/pool.ts
14363
14681
  import { gunzipSync } from "node:zlib";
14364
- import { mkdir as mkdir12, readFile as readFile9 } from "node:fs/promises";
14682
+ import { mkdir as mkdir12, readFile as readFile10 } from "node:fs/promises";
14365
14683
  import { existsSync as existsSync23 } from "node:fs";
14366
- import { join as join26 } from "node:path";
14684
+ import { join as join27 } from "node:path";
14367
14685
  function shardPath(verDir, shard) {
14368
- return join26(verDir, "ir", `${shard}.json.gz`);
14686
+ return join27(verDir, "ir", `${shard}.json.gz`);
14369
14687
  }
14370
14688
  function decodeShard(gz) {
14371
14689
  const json = gunzipSync(gz).toString("utf8");
@@ -14384,7 +14702,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
14384
14702
  const path = shardPath(verDir, shard);
14385
14703
  if (existsSync23(path)) {
14386
14704
  try {
14387
- const s2 = decodeShard(await readFile9(path));
14705
+ const s2 = decodeShard(await readFile10(path));
14388
14706
  memo.set(shard, s2);
14389
14707
  return s2;
14390
14708
  } catch {
@@ -14400,7 +14718,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
14400
14718
  throw new Error(`IR 分片下载失败 HTTP ${res.status}:${url}`);
14401
14719
  const buf = new Uint8Array(await res.arrayBuffer());
14402
14720
  const s = decodeShard(buf);
14403
- await mkdir12(join26(verDir, "ir"), { recursive: true });
14721
+ await mkdir12(join27(verDir, "ir"), { recursive: true });
14404
14722
  await atomicWrite(path, buf);
14405
14723
  memo.set(shard, s);
14406
14724
  return s;
@@ -14612,10 +14930,10 @@ async function runMad(inputArg, opts, deps = {}) {
14612
14930
  const header = madHeader(version, now.toISOString());
14613
14931
  const bgm = level <= 2 && bgmForTrack ? { path: bgmForTrack, markers } : undefined;
14614
14932
  const { jsx } = madJsx({ master, windows, header, bgm });
14615
- const outDir = opts.out ? resolve13(opts.out) : join27(process.cwd(), `mad-${timestamp4(now)}`);
14933
+ const outDir = opts.out ? resolve13(opts.out) : join28(process.cwd(), `mad-${timestamp4(now)}`);
14616
14934
  await mkdir13(outDir, { recursive: true });
14617
- const jsxPath = join27(outDir, "mad.jsx");
14618
- await writeFile11(jsxPath, jsx);
14935
+ const jsxPath = join28(outDir, "mad.jsx");
14936
+ await writeFile12(jsxPath, jsx);
14619
14937
  const result = {
14620
14938
  ok: true,
14621
14939
  tool: "mad",
@@ -14626,7 +14944,7 @@ async function runMad(inputArg, opts, deps = {}) {
14626
14944
  degradeLevel: level,
14627
14945
  techniques: chosen.map((c3) => ({ uid: c3.entry.uid, pid: c3.entry.pid, cat: c3.entry.cat, t0: c3.entry.t0, t1: c3.entry.t1 }))
14628
14946
  };
14629
- await writeFile11(join27(outDir, "result.json"), JSON.stringify({ ...result, finishedAt: now.toISOString() }, null, 2));
14947
+ await writeFile12(join28(outDir, "result.json"), JSON.stringify({ ...result, finishedAt: now.toISOString() }, null, 2));
14630
14948
  warn(completionMessage(jsxPath, level));
14631
14949
  return result;
14632
14950
  }
@@ -14760,8 +15078,8 @@ async function runMadInTool(inputArg, opts) {
14760
15078
 
14761
15079
  // src/commands/transcript.ts
14762
15080
  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";
14764
- import { basename as basename14, dirname as dirname11, extname as extname8, join as join28, resolve as resolve14 } from "node:path";
15081
+ import { mkdir as mkdir14, rename as rename3, rm as rm2, stat as stat6, writeFile as writeFile13 } from "node:fs/promises";
15082
+ import { basename as basename14, dirname as dirname12, extname as extname8, join as join29, resolve as resolve14 } from "node:path";
14765
15083
 
14766
15084
  // src/lib/transcript.ts
14767
15085
  var AGENT_SUMMARY_PENDING = "<!-- gtrk:agent-summary-pending -->";
@@ -14911,16 +15229,16 @@ async function validateTranscriptInput(input) {
14911
15229
  }
14912
15230
  function resolveTranscriptOutput(inputAbs, out) {
14913
15231
  const base = basename14(inputAbs, extname8(inputAbs));
14914
- const output = out ? resolve14(out) : join28(dirname11(inputAbs), `${base}-transcript.md`);
15232
+ const output = out ? resolve14(out) : join29(dirname12(inputAbs), `${base}-transcript.md`);
14915
15233
  if (extname8(output).toLowerCase() !== ".md")
14916
15234
  throw new Error("--out 必须指向一个 .md 文件");
14917
15235
  return output;
14918
15236
  }
14919
15237
  async function writeMarkdownAtomic(path, markdown) {
14920
- await mkdir14(dirname11(path), { recursive: true });
15238
+ await mkdir14(dirname12(path), { recursive: true });
14921
15239
  const temp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
14922
15240
  try {
14923
- await writeFile12(temp, markdown, "utf8");
15241
+ await writeFile13(temp, markdown, "utf8");
14924
15242
  await rename3(temp, path);
14925
15243
  } finally {
14926
15244
  await rm2(temp, { force: true });
@@ -14994,8 +15312,8 @@ function registerTranscript(program2) {
14994
15312
  }
14995
15313
 
14996
15314
  // src/commands/music-visualizer.ts
14997
- import { resolve as resolve15, join as join29, dirname as dirname12, basename as basename15, extname as extname9 } from "node:path";
14998
- import { mkdir as mkdir15, writeFile as writeFile13 } from "node:fs/promises";
15315
+ import { resolve as resolve15, join as join30, dirname as dirname13, basename as basename15, extname as extname9 } from "node:path";
15316
+ import { mkdir as mkdir15, writeFile as writeFile14 } from "node:fs/promises";
14999
15317
  import { existsSync as existsSync26 } from "node:fs";
15000
15318
  var TASK_TYPE5 = "music_visualizer";
15001
15319
  var PRICE_KEY2 = "music_visualizer";
@@ -15114,7 +15432,7 @@ async function runMusicVisualizer(audio, opts) {
15114
15432
  assertExt(coverAbs, IMAGE_EXTS2, "封面图");
15115
15433
  const extraParams = parseExtraParams3(opts.param, opts.paramsJson);
15116
15434
  const projName = basename15(audioAbs, extname9(audioAbs));
15117
- const outDir = resolve15(opts.out ?? join29(dirname12(audioAbs), `${projName}-visualizer-${timestamp5()}`));
15435
+ const outDir = resolve15(opts.out ?? join30(dirname13(audioAbs), `${projName}-visualizer-${timestamp5()}`));
15118
15436
  log.step(`▶ 音乐可视化:${basename15(audioAbs)}(模板 ${template}${bgAbs ? " · 背景" : ""}${coverAbs ? " · 封面" : ""})`);
15119
15437
  let billingHint;
15120
15438
  try {
@@ -15161,7 +15479,7 @@ async function runMusicVisualizer(audio, opts) {
15161
15479
  const { taskId } = submitted;
15162
15480
  log.info(`task_id = ${taskId}`);
15163
15481
  await mkdir15(outDir, { recursive: true });
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));
15482
+ await writeFile14(join30(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE5, fileId: submitted.fileId, source: audioAbs, template, createdAt: new Date().toISOString() }, null, 2));
15165
15483
  log.step("③ 云端处理中(每 5s 轮询)…");
15166
15484
  const result = await pollTask(cfg, TASK_TYPE5, taskId, (status, progress) => {
15167
15485
  log.tick(`${status}${progress != null ? ` ${Math.round(progress)}%` : ""}`);
@@ -15172,7 +15490,7 @@ async function runMusicVisualizer(audio, opts) {
15172
15490
  const files = [];
15173
15491
  const errors = {};
15174
15492
  if (url) {
15175
- const dest = join29(outDir, `${projName}-visualizer${extFromUrl(url, ".mp4")}`);
15493
+ const dest = join30(outDir, `${projName}-visualizer${extFromUrl(url, ".mp4")}`);
15176
15494
  try {
15177
15495
  await downloadStream(url, dest);
15178
15496
  files.push(dest);
@@ -15194,7 +15512,7 @@ async function runMusicVisualizer(audio, opts) {
15194
15512
  ...Object.keys(errors).length ? { errors } : {},
15195
15513
  finishedAt: new Date().toISOString()
15196
15514
  };
15197
- await writeFile13(join29(outDir, "result.json"), JSON.stringify(resultJson, null, 2));
15515
+ await writeFile14(join30(outDir, "result.json"), JSON.stringify(resultJson, null, 2));
15198
15516
  if (opts.json) {
15199
15517
  process.stdout.write(`${JSON.stringify(resultJson)}
15200
15518
  `);
@@ -15212,7 +15530,7 @@ try {
15212
15530
  process.loadEnvFile?.();
15213
15531
  } catch {}
15214
15532
  migrateLegacyHome();
15215
- var { version } = JSON.parse(readFileSync6(join30(packageRoot(), "package.json"), "utf8"));
15533
+ var { version } = JSON.parse(readFileSync6(join31(packageRoot(), "package.json"), "utf8"));
15216
15534
  var program2 = new Command;
15217
15535
  program2.name("gtrk").description("同合云成片流水线 CLI —— agent 驱动云端任务、产物拉回本地、三方工程文件(客户端/剪映/PR)互通").version(version);
15218
15536
  registerInstall(program2);