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