@gitruck/cli 0.2.20 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +1 -1
- package/README.md +1 -1
- package/contracts/gsap-emit-v1.md +42 -3
- package/dist/index.js +484 -133
- package/package.json +65 -65
- package/skills/gtrk-long2short/SKILL.md +63 -61
package/dist/index.js
CHANGED
|
@@ -4557,6 +4557,7 @@ function writeUserConfig(patch) {
|
|
|
4557
4557
|
// src/lib/jianying.ts
|
|
4558
4558
|
import { join as join4, resolve as resolve2 } from "node:path";
|
|
4559
4559
|
import { existsSync as existsSync4 } from "node:fs";
|
|
4560
|
+
import { cp, mkdir, readdir } from "node:fs/promises";
|
|
4560
4561
|
function probeJianyingDraftDir() {
|
|
4561
4562
|
const local = process.env.LOCALAPPDATA;
|
|
4562
4563
|
if (!local)
|
|
@@ -4575,6 +4576,31 @@ function resolveJianyingDraftDir(opt) {
|
|
|
4575
4576
|
return saved;
|
|
4576
4577
|
return probeJianyingDraftDir();
|
|
4577
4578
|
}
|
|
4579
|
+
var JIANYING_DRAFT_FILES = ["draft_content.json", "draft_meta_info.json"];
|
|
4580
|
+
async function copyJianyingDraft(srcDir, destDir) {
|
|
4581
|
+
const entries = (await readdir(srcDir, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
4582
|
+
const chosen = new Map;
|
|
4583
|
+
for (const fixed of JIANYING_DRAFT_FILES) {
|
|
4584
|
+
const cands = entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith(fixed));
|
|
4585
|
+
const pick = cands.find((e) => e.name.toLowerCase() === fixed) ?? cands[0];
|
|
4586
|
+
if (pick)
|
|
4587
|
+
chosen.set(pick.name, fixed);
|
|
4588
|
+
}
|
|
4589
|
+
await mkdir(destDir, { recursive: true });
|
|
4590
|
+
const landed = {};
|
|
4591
|
+
const passthrough = [];
|
|
4592
|
+
for (const e of entries) {
|
|
4593
|
+
const fixed = chosen.get(e.name);
|
|
4594
|
+
const dest = join4(destDir, fixed ?? e.name);
|
|
4595
|
+
await cp(join4(srcDir, e.name), dest, { recursive: true });
|
|
4596
|
+
if (fixed)
|
|
4597
|
+
landed[fixed] = dest;
|
|
4598
|
+
else
|
|
4599
|
+
passthrough.push(e.name);
|
|
4600
|
+
}
|
|
4601
|
+
const missing = JIANYING_DRAFT_FILES.filter((f) => !landed[f]);
|
|
4602
|
+
return { landed, missing, complete: missing.length === 0, passthrough };
|
|
4603
|
+
}
|
|
4578
4604
|
|
|
4579
4605
|
// src/lib/open.ts
|
|
4580
4606
|
import { spawn } from "node:child_process";
|
|
@@ -6175,7 +6201,7 @@ function registerInstall(program2) {
|
|
|
6175
6201
|
|
|
6176
6202
|
// src/commands/oralcut.ts
|
|
6177
6203
|
import { resolve as resolve4, join as join15, dirname as dirname3, basename as basename6, extname as extname2 } from "node:path";
|
|
6178
|
-
import { mkdir as
|
|
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,12 +7315,140 @@ async function runOralCut(input, opts) {
|
|
|
7285
7315
|
|
|
7286
7316
|
// src/commands/long2short.ts
|
|
7287
7317
|
import { resolve as resolve6, join as join17, dirname as dirname5, basename as basename8, extname as extname5 } from "node:path";
|
|
7288
|
-
import { mkdir as
|
|
7318
|
+
import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
|
|
7289
7319
|
import { existsSync as existsSync16 } from "node:fs";
|
|
7290
7320
|
|
|
7321
|
+
// src/lib/clip-brief.ts
|
|
7322
|
+
var str = (v) => {
|
|
7323
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
7324
|
+
return s ? s : undefined;
|
|
7325
|
+
};
|
|
7326
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
7327
|
+
var list = (v) => Array.isArray(v) ? v.map((x) => str(x)).filter((x) => !!x) : [];
|
|
7328
|
+
function fmtTime(ms) {
|
|
7329
|
+
const total = Math.max(0, Math.round((num(ms) ?? 0) / 1000));
|
|
7330
|
+
const s = total % 60;
|
|
7331
|
+
const m = Math.floor(total / 60) % 60;
|
|
7332
|
+
const h = Math.floor(total / 3600);
|
|
7333
|
+
const mm = h ? String(m).padStart(2, "0") : String(m);
|
|
7334
|
+
return `${h ? `${h}:` : ""}${mm}:${String(s).padStart(2, "0")}`;
|
|
7335
|
+
}
|
|
7336
|
+
var baseName = (p) => p.split(/[\\/]/).pop() || p;
|
|
7337
|
+
var cell = (s) => s.replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ");
|
|
7338
|
+
function jumpcutText(note) {
|
|
7339
|
+
if (note === "jumpcut_off")
|
|
7340
|
+
return "未启用跳剪(`--no-jump-cut`),片段按原样保留。";
|
|
7341
|
+
if (note === "too_short")
|
|
7342
|
+
return "片段过短,跳剪跳过(保留全部内容)。";
|
|
7343
|
+
if (note === "degraded")
|
|
7344
|
+
return "跳剪降级:本条未压缩,保留全部内容。";
|
|
7345
|
+
return note;
|
|
7346
|
+
}
|
|
7347
|
+
function clipDurationMs(clip) {
|
|
7348
|
+
return num(clip.total_duration_ms) ?? num(clip.duration_ms);
|
|
7349
|
+
}
|
|
7350
|
+
function clipSegmentCount(clip) {
|
|
7351
|
+
return num(clip.segment_count) ?? (Array.isArray(clip.segments) ? clip.segments.length : undefined);
|
|
7352
|
+
}
|
|
7353
|
+
function sourceSpan(clip) {
|
|
7354
|
+
const segs = Array.isArray(clip.segments) ? clip.segments : [];
|
|
7355
|
+
if (segs.length) {
|
|
7356
|
+
const b2 = num(segs[0]?.begin_time);
|
|
7357
|
+
const e2 = num(segs[segs.length - 1]?.end_time);
|
|
7358
|
+
if (b2 != null && e2 != null)
|
|
7359
|
+
return [b2, e2];
|
|
7360
|
+
}
|
|
7361
|
+
const b = num(clip.begin_time);
|
|
7362
|
+
const e = num(clip.end_time);
|
|
7363
|
+
return b != null && e != null ? [b, e] : undefined;
|
|
7364
|
+
}
|
|
7365
|
+
function renderClipBrief(clip, index, files = {}) {
|
|
7366
|
+
const title = str(clip.title);
|
|
7367
|
+
const out = [`# clip${index}${title ? `「${title}」` : ""}`, ""];
|
|
7368
|
+
const bits = [];
|
|
7369
|
+
const dur = clipDurationMs(clip);
|
|
7370
|
+
if (dur != null)
|
|
7371
|
+
bits.push(`时长 ${fmtTime(dur)}`);
|
|
7372
|
+
const segCount = clipSegmentCount(clip);
|
|
7373
|
+
if (segCount != null)
|
|
7374
|
+
bits.push(`${segCount} 个保留片段`);
|
|
7375
|
+
const span = sourceSpan(clip);
|
|
7376
|
+
if (span)
|
|
7377
|
+
bits.push(`源片 ${fmtTime(span[0])}–${fmtTime(span[1])}`);
|
|
7378
|
+
if (bits.length)
|
|
7379
|
+
out.push(`- ${bits.join(" · ")}`, "");
|
|
7380
|
+
const score = num(clip.score);
|
|
7381
|
+
const reason = str(clip.score_reason);
|
|
7382
|
+
if (score != null || reason) {
|
|
7383
|
+
out.push("## 入选理由", "");
|
|
7384
|
+
out.push([score != null ? `**评分 ${score}**` : null, reason].filter(Boolean).join(" — "), "");
|
|
7385
|
+
}
|
|
7386
|
+
const summary = str(clip.summary);
|
|
7387
|
+
if (summary)
|
|
7388
|
+
out.push("## 简介", "", summary, "");
|
|
7389
|
+
const note = str(clip.jumpcut_note);
|
|
7390
|
+
if (note)
|
|
7391
|
+
out.push("## 跳剪", "", jumpcutText(note), "");
|
|
7392
|
+
const cats = [
|
|
7393
|
+
["主题", list(clip.themes)],
|
|
7394
|
+
["标签", list(clip.tags)],
|
|
7395
|
+
["类型", list(clip.genres)],
|
|
7396
|
+
["调性", list(clip.moods)]
|
|
7397
|
+
];
|
|
7398
|
+
const shown = cats.filter(([, v]) => v.length);
|
|
7399
|
+
if (shown.length) {
|
|
7400
|
+
out.push("## 分类与调性", "");
|
|
7401
|
+
for (const [k, v] of shown)
|
|
7402
|
+
out.push(`- ${k}:${v.join("、")}`);
|
|
7403
|
+
out.push("");
|
|
7404
|
+
}
|
|
7405
|
+
const hl = Array.isArray(clip.highlight_words) ? clip.highlight_words : [];
|
|
7406
|
+
const hlRows = hl.map((h) => ({ text: str(h?.text), at: num(h?.begin_time) })).filter((h) => h.text);
|
|
7407
|
+
if (hlRows.length) {
|
|
7408
|
+
out.push("## 高光词(源片时码)", "");
|
|
7409
|
+
for (const h of hlRows)
|
|
7410
|
+
out.push(`- ${h.at != null ? `${fmtTime(h.at)} ` : ""}「${h.text}」`);
|
|
7411
|
+
out.push("");
|
|
7412
|
+
}
|
|
7413
|
+
const formats = Object.keys(files).filter((f) => files[f]?.length);
|
|
7414
|
+
if (formats.length) {
|
|
7415
|
+
out.push("## 本条工程文件", "");
|
|
7416
|
+
for (const f of formats)
|
|
7417
|
+
out.push(`- ${f}:${files[f].map(baseName).join("、")}`);
|
|
7418
|
+
out.push("");
|
|
7419
|
+
}
|
|
7420
|
+
return `${out.join(`
|
|
7421
|
+
`).replace(/\n{3,}/g, `
|
|
7422
|
+
|
|
7423
|
+
`).trimEnd()}
|
|
7424
|
+
`;
|
|
7425
|
+
}
|
|
7426
|
+
function renderClipsOverview(clips, ctx) {
|
|
7427
|
+
const name = ctx.source.split(/[\\/]/).pop() || ctx.source;
|
|
7428
|
+
const out = [`# ${name} · 长剪短总览`, ""];
|
|
7429
|
+
out.push(`- 源片:\`${ctx.source}\``);
|
|
7430
|
+
out.push(`- 切片:${clips.length} 条`);
|
|
7431
|
+
out.push(`- 跳剪:${ctx.jumpCut ? "开" : "关"}`);
|
|
7432
|
+
if (ctx.splitMaterials?.total)
|
|
7433
|
+
out.push(`- 分屏素材:${ctx.splitMaterials.landed}/${ctx.splitMaterials.total} 条已落地`);
|
|
7434
|
+
if (ctx.taskId)
|
|
7435
|
+
out.push(`- task_id:\`${ctx.taskId}\``);
|
|
7436
|
+
out.push("");
|
|
7437
|
+
out.push("| # | 标题 | 时长 | 评分 | 简介 |", "| --- | --- | --- | --- | --- |");
|
|
7438
|
+
for (const [i, clip] of clips.entries()) {
|
|
7439
|
+
const dur = clipDurationMs(clip);
|
|
7440
|
+
const score = num(clip.score);
|
|
7441
|
+
out.push(`| clip${i} | ${cell(str(clip.title) ?? "—")} | ${dur != null ? fmtTime(dur) : "—"} | ${score ?? "—"} | ${cell(str(clip.summary) ?? "—")} |`);
|
|
7442
|
+
}
|
|
7443
|
+
out.push("", "> 逐条入选理由、跳剪说明、高光词见各 `clip{i}/clip.md`。");
|
|
7444
|
+
return `${out.join(`
|
|
7445
|
+
`)}
|
|
7446
|
+
`;
|
|
7447
|
+
}
|
|
7448
|
+
|
|
7291
7449
|
// src/lib/tool-runner.ts
|
|
7292
7450
|
import { resolve as resolve5, join as join16, dirname as dirname4, basename as basename7, extname as extname4 } from "node:path";
|
|
7293
|
-
import { mkdir as
|
|
7451
|
+
import { mkdir as mkdir6, writeFile as writeFile6, stat as stat5 } from "node:fs/promises";
|
|
7294
7452
|
import { createWriteStream, existsSync as existsSync15 } from "node:fs";
|
|
7295
7453
|
import { Readable as Readable2 } from "node:stream";
|
|
7296
7454
|
import { pipeline } from "node:stream/promises";
|
|
@@ -8902,7 +9060,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
|
|
|
8902
9060
|
const isMulti = MULTI_INPUT_KINDS.has(descriptor.input.kind);
|
|
8903
9061
|
const inputList = isMulti ? (Array.isArray(inputArg) ? inputArg : inputArg != null ? [inputArg] : []).map((p) => resolve5(p)) : undefined;
|
|
8904
9062
|
const inputAbs = isMulti ? inputList[0] : typeof inputArg === "string" ? resolve5(inputArg) : undefined;
|
|
8905
|
-
const
|
|
9063
|
+
const baseName2 = inputAbs ? basename7(inputAbs, extname4(inputAbs)) : descriptor.name;
|
|
8906
9064
|
if (isMulti) {
|
|
8907
9065
|
validateToolInputs(descriptor, inputList);
|
|
8908
9066
|
} else {
|
|
@@ -8914,7 +9072,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
|
|
|
8914
9072
|
const ctx = {
|
|
8915
9073
|
inputAbs,
|
|
8916
9074
|
...inputList ? { inputAbsList: inputList } : {},
|
|
8917
|
-
baseName,
|
|
9075
|
+
baseName: baseName2,
|
|
8918
9076
|
ffmpegPath: opts.ffmpegPath,
|
|
8919
9077
|
opts,
|
|
8920
9078
|
extraParams,
|
|
@@ -8971,7 +9129,7 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
|
|
|
8971
9129
|
taskId = submitted.taskId;
|
|
8972
9130
|
fileId = submitted.fileId;
|
|
8973
9131
|
}
|
|
8974
|
-
await
|
|
9132
|
+
await mkdir6(outDir, { recursive: true });
|
|
8975
9133
|
const fingerprint2 = inputList ? await Promise.all(inputList.map((p) => safeFingerprint(p))) : inputAbs ? await safeFingerprint(inputAbs) : undefined;
|
|
8976
9134
|
await writeFile6(join16(outDir, "task.json"), JSON.stringify({
|
|
8977
9135
|
tool: descriptor.name,
|
|
@@ -9077,6 +9235,34 @@ function buildLong2ShortPayload(fileId, opts, geo, inputAbs, formats, draftTarge
|
|
|
9077
9235
|
mergeParams(p, parseExtraParams2(opts.param ?? [], opts.paramsJson));
|
|
9078
9236
|
return p;
|
|
9079
9237
|
}
|
|
9238
|
+
async function copyClipDraftsToRoot(clipDirs, draftTarget, errors) {
|
|
9239
|
+
const paths = [];
|
|
9240
|
+
let complete = 0;
|
|
9241
|
+
let attempted = 0;
|
|
9242
|
+
for (const [i, dir] of clipDirs.entries()) {
|
|
9243
|
+
const src = join17(dir, "jianying");
|
|
9244
|
+
if (!existsSync16(src)) {
|
|
9245
|
+
paths.push(null);
|
|
9246
|
+
continue;
|
|
9247
|
+
}
|
|
9248
|
+
attempted++;
|
|
9249
|
+
const dest = `${draftTarget}_clip${i}`;
|
|
9250
|
+
try {
|
|
9251
|
+
const landing = await copyJianyingDraft(src, dest);
|
|
9252
|
+
if (landing.complete) {
|
|
9253
|
+
complete++;
|
|
9254
|
+
paths.push(dest);
|
|
9255
|
+
} else {
|
|
9256
|
+
paths.push(null);
|
|
9257
|
+
errors[`clip${i}:jianying:draft`] = `草稿两件套不全(缺 ${landing.missing.join("、")}),剪映列表里不会显示:${dest}`;
|
|
9258
|
+
}
|
|
9259
|
+
} catch (e) {
|
|
9260
|
+
paths.push(null);
|
|
9261
|
+
errors[`clip${i}:jianying:draft`] = e instanceof Error ? e.message : String(e);
|
|
9262
|
+
}
|
|
9263
|
+
}
|
|
9264
|
+
return { paths, complete, attempted };
|
|
9265
|
+
}
|
|
9080
9266
|
function registerLong2Short(program2) {
|
|
9081
9267
|
program2.command("long2short <input>").description("长剪短闭环:本地抽音频/720p 代理 → 只传抽出物 → 云端选段跳剪(可选分屏)→ 逐 clip 拉回 gtrk/剪映/PR 三方工程").option("--language <code>", "源语种(必填,如 zh-CN;取值以服务端支持列表为准)").option("--split-screen", "开启智能分屏:本地改传 720p 代理,云端在代理上检测多人同框并烤 720p 分屏素材(素材落毛片旁 split_screen/)").option("--split-orientation <o>", "分屏方向 auto|lr|tb(缺省服务端 auto=按内容随机)").option("--main-topic <text>", "主题引导(影响选段偏好)").option("--duration-pref <p>", "成片时长偏好(缺省服务端 auto;成片条数由内容语义决定、不可指定)").option("--max-clip-sec <n>", "单条成片时长安全上限(秒;缺省服务端默认)").option("--no-jump-cut", "关闭跳剪(默认开:片内去水词/冗余,只删不重排)").option("--output-size <s>", "输出画布 9:16|16:9|1:1 或自定义 WxH(缺省服务端 9:16)").option("-f, --formats <list>", "三方格式(逗号分隔,云端逐 clip 直产)", "gtrk,jianying,xml").option("--jianying-draft-dir <dir>", "剪映草稿根目录;传路径或 auto(默认读 gtrk init 配置 / 自动探测)").option("-o, --out <dir>", "产物根目录(缺省 = <毛片同目录>/<毛片名>-long2short)").option("--ffmpeg-path <dir>", "指定 ffmpeg/ffprobe 所在目录(缺省 ~/.gitruck/ffmpeg → 系统 PATH)").option("--param <k=v>", "透传任意云端参数(标量、可重复)", collectParam2, []).option("--params-json <json>", `透传任意云端参数(JSON 对象、支持嵌套;如 '{"split_screen":{"prefer_mode":"..."}}')`).option("--reupload", "强制重新上传,忽略本地上传缓存").option("--no-open", "完成后不自动打开产物根目录(默认会自动打开一次)").option("--json", "机读模式:人读日志转 stderr,stdout 只输出根级结果 JSON").action(async (input, opts) => {
|
|
9082
9268
|
await runLong2Short(input, opts);
|
|
@@ -9124,7 +9310,7 @@ async function runLong2Short(input, opts) {
|
|
|
9124
9310
|
});
|
|
9125
9311
|
const { taskId, fileId } = { taskId: submitted.taskId, fileId: submitted.fileId };
|
|
9126
9312
|
log.info(`task_id = ${taskId}`);
|
|
9127
|
-
await
|
|
9313
|
+
await mkdir7(outDir, { recursive: true });
|
|
9128
9314
|
await writeFile7(join17(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE2, fileId, source: inputAbs, formats, createdAt: new Date().toISOString() }, null, 2));
|
|
9129
9315
|
log.step("④ 云端处理中(选段/跳剪" + (opts.splitScreen ? "/分屏" : "") + ",每 5s 轮询)…");
|
|
9130
9316
|
const output = await pollToolTask(cfg, TASK_TYPE2, taskId, {
|
|
@@ -9150,7 +9336,7 @@ async function runLong2Short(input, opts) {
|
|
|
9150
9336
|
if (!/^(?:[A-Za-z]:[\\/]|[\\/])/.test(dest))
|
|
9151
9337
|
dest = join17(dirname5(inputAbs), dest);
|
|
9152
9338
|
try {
|
|
9153
|
-
await
|
|
9339
|
+
await mkdir7(dirname5(dest), { recursive: true });
|
|
9154
9340
|
await download(url, dest);
|
|
9155
9341
|
splitLanded++;
|
|
9156
9342
|
} catch (e) {
|
|
@@ -9186,20 +9372,40 @@ async function runLong2Short(input, opts) {
|
|
|
9186
9372
|
log.warn(`clip${i} 落地失败(不连坐其余):${errors[`clip${i}`]}`);
|
|
9187
9373
|
}
|
|
9188
9374
|
}
|
|
9189
|
-
if (draftRoot) {
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
}
|
|
9199
|
-
|
|
9200
|
-
}
|
|
9375
|
+
if (draftRoot && draftTarget) {
|
|
9376
|
+
const landing = await copyClipDraftsToRoot(clipResults.map((c3) => c3.dir), draftTarget, errors);
|
|
9377
|
+
landing.paths.forEach((p, i) => {
|
|
9378
|
+
clipResults[i].jianyingDraftPath = p;
|
|
9379
|
+
});
|
|
9380
|
+
const { complete, attempted } = landing;
|
|
9381
|
+
if (!attempted)
|
|
9382
|
+
log.warn("没有任何 clip 产出剪映草稿,草稿根未落地");
|
|
9383
|
+
else if (complete === attempted)
|
|
9384
|
+
log.info(`剪映草稿:<草稿根>/${outName}_clip{i} —— ${complete}/${attempted} 条两件套齐全(剪映里直接可见)`);
|
|
9385
|
+
else if (complete) {
|
|
9386
|
+
log.info(`剪映草稿:<草稿根>/${outName}_clip{i} —— ${complete}/${attempted} 条两件套齐全(剪映里可见)`);
|
|
9387
|
+
log.warn(`另有 ${attempted - complete} 条草稿两件套不全,剪映里不会显示(详见 result.json errors)`);
|
|
9388
|
+
} else
|
|
9389
|
+
log.warn(`${attempted} 条草稿两件套均不全,剪映里不会显示(详见 result.json errors)`);
|
|
9390
|
+
}
|
|
9391
|
+
for (const [i, cr] of clipResults.entries()) {
|
|
9392
|
+
try {
|
|
9393
|
+
await mkdir7(cr.dir, { recursive: true });
|
|
9394
|
+
await writeFile7(join17(cr.dir, "clip.md"), renderClipBrief(clips[i], i, cr.files));
|
|
9395
|
+
} catch (e) {
|
|
9396
|
+
errors[`clip${i}:brief`] = e instanceof Error ? e.message : String(e);
|
|
9201
9397
|
}
|
|
9202
|
-
|
|
9398
|
+
}
|
|
9399
|
+
try {
|
|
9400
|
+
await writeFile7(join17(outDir, "clips.md"), renderClipsOverview(clips, {
|
|
9401
|
+
source: inputAbs,
|
|
9402
|
+
jumpCut: opts.jumpCut !== false,
|
|
9403
|
+
splitMaterials: { landed: splitLanded, total: manifest.length },
|
|
9404
|
+
taskId
|
|
9405
|
+
}));
|
|
9406
|
+
log.info(`总览已生成:clips.md(逐条见 clip{i}/clip.md)`);
|
|
9407
|
+
} catch (e) {
|
|
9408
|
+
errors["clips.md"] = e instanceof Error ? e.message : String(e);
|
|
9203
9409
|
}
|
|
9204
9410
|
await writeFile7(join17(outDir, "report.json"), JSON.stringify(output.report ?? {}, null, 2));
|
|
9205
9411
|
const ok = Object.keys(errors).length === 0 && clipResults.some((c3) => c3.ok);
|
|
@@ -9360,7 +9566,7 @@ function registerRender(program2) {
|
|
|
9360
9566
|
// src/commands/split.ts
|
|
9361
9567
|
import { resolve as resolve9, join as join21, dirname as dirname8, basename as basename11 } from "node:path";
|
|
9362
9568
|
import { existsSync as existsSync18 } from "node:fs";
|
|
9363
|
-
import { readFile as readFile5, writeFile as writeFile8, mkdir as
|
|
9569
|
+
import { readFile as readFile5, writeFile as writeFile8, mkdir as mkdir8 } from "node:fs/promises";
|
|
9364
9570
|
import { createHash } from "node:crypto";
|
|
9365
9571
|
|
|
9366
9572
|
// src/lib/gtrk-writeback.ts
|
|
@@ -9481,7 +9687,7 @@ async function runView(baseDir, gtrkPath, transcriptPath, opts) {
|
|
|
9481
9687
|
const { gtrk } = readGtrk(gtrkPath);
|
|
9482
9688
|
const view = projectTranscript(transcript, gtrk, { words: opts.words });
|
|
9483
9689
|
const splitDir = join21(baseDir, "split");
|
|
9484
|
-
await
|
|
9690
|
+
await mkdir8(splitDir, { recursive: true });
|
|
9485
9691
|
const viewPath = join21(splitDir, "view.json");
|
|
9486
9692
|
await writeFile8(viewPath, JSON.stringify(view, null, 2));
|
|
9487
9693
|
const dropped = view.utterances.filter((u) => u.dropped).length;
|
|
@@ -9540,7 +9746,7 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
|
|
|
9540
9746
|
});
|
|
9541
9747
|
writeStructMetaSplit(gtrkPath, gtrk, landing.split, mtimeMs);
|
|
9542
9748
|
const splitDir = join21(baseDir, "split");
|
|
9543
|
-
await
|
|
9749
|
+
await mkdir8(splitDir, { recursive: true });
|
|
9544
9750
|
const dispatchPath = join21(splitDir, "dispatch.json");
|
|
9545
9751
|
await writeFile8(dispatchPath, JSON.stringify(landing.dispatch, null, 2));
|
|
9546
9752
|
let mdPath = null;
|
|
@@ -9579,7 +9785,7 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
|
|
|
9579
9785
|
// src/commands/matrix.ts
|
|
9580
9786
|
import { resolve as resolve11, join as join22, dirname as dirname9, basename as basename12 } from "node:path";
|
|
9581
9787
|
import { existsSync as existsSync20 } from "node:fs";
|
|
9582
|
-
import { readFile as readFile6, writeFile as writeFile9, mkdir as
|
|
9788
|
+
import { readFile as readFile6, writeFile as writeFile9, mkdir as mkdir9, rename } from "node:fs/promises";
|
|
9583
9789
|
|
|
9584
9790
|
// src/lib/solid-png.ts
|
|
9585
9791
|
import { deflateSync } from "node:zlib";
|
|
@@ -10359,8 +10565,8 @@ function collectMaterialRefs(gtrk) {
|
|
|
10359
10565
|
const id = c3[key];
|
|
10360
10566
|
if (typeof id !== "string" || id === "")
|
|
10361
10567
|
continue;
|
|
10362
|
-
const
|
|
10363
|
-
|
|
10568
|
+
const list2 = out.get(id) ?? [];
|
|
10569
|
+
list2.push({
|
|
10364
10570
|
track: group,
|
|
10365
10571
|
track_index: trackIndex,
|
|
10366
10572
|
clip_id: typeof c3.clip_id === "string" ? c3.clip_id : null,
|
|
@@ -10368,7 +10574,7 @@ function collectMaterialRefs(gtrk) {
|
|
|
10368
10574
|
track_ed: clipTrackEd(c3),
|
|
10369
10575
|
key
|
|
10370
10576
|
});
|
|
10371
|
-
out.set(id,
|
|
10577
|
+
out.set(id, list2);
|
|
10372
10578
|
}
|
|
10373
10579
|
}
|
|
10374
10580
|
}
|
|
@@ -10410,15 +10616,15 @@ function checkMaterialIntegrity(opts) {
|
|
|
10410
10616
|
}
|
|
10411
10617
|
if (present)
|
|
10412
10618
|
continue;
|
|
10413
|
-
const
|
|
10619
|
+
const list2 = refs.get(id) ?? [];
|
|
10414
10620
|
const entry = {
|
|
10415
10621
|
id,
|
|
10416
10622
|
path,
|
|
10417
10623
|
kind,
|
|
10418
10624
|
resolved,
|
|
10419
|
-
referenced:
|
|
10420
|
-
refCount:
|
|
10421
|
-
refs:
|
|
10625
|
+
referenced: list2.length > 0,
|
|
10626
|
+
refCount: list2.length,
|
|
10627
|
+
refs: list2
|
|
10422
10628
|
};
|
|
10423
10629
|
(kind === "relative" ? dangling : external).push(entry);
|
|
10424
10630
|
}
|
|
@@ -10567,9 +10773,9 @@ function dedupeBeatQueries(queries) {
|
|
|
10567
10773
|
const best = bestByClip.get(r.clip_id);
|
|
10568
10774
|
if (best.qi === qi && best.r === r)
|
|
10569
10775
|
return true;
|
|
10570
|
-
const
|
|
10571
|
-
if (!
|
|
10572
|
-
|
|
10776
|
+
const list2 = best.r.also_matched_queries ??= [];
|
|
10777
|
+
if (!list2.includes(q.query))
|
|
10778
|
+
list2.push(q.query);
|
|
10573
10779
|
return false;
|
|
10574
10780
|
});
|
|
10575
10781
|
}
|
|
@@ -10792,7 +10998,7 @@ async function runPlanMode(cfg, tier, broll, overrides, columnId, opts) {
|
|
|
10792
10998
|
beats
|
|
10793
10999
|
});
|
|
10794
11000
|
const splitDir = join22(baseDir, "split");
|
|
10795
|
-
await
|
|
11001
|
+
await mkdir9(splitDir, { recursive: true });
|
|
10796
11002
|
const planPath = join22(splitDir, "broll-plan.json");
|
|
10797
11003
|
await writeFile9(planPath, JSON.stringify(plan, null, 2));
|
|
10798
11004
|
log.ok(`候选清单已生成:${planPath}(${beats.length} beat · ${okCount}/${totalQueries} query 成功 · ${resultCount} 条候选)`);
|
|
@@ -10857,7 +11063,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
|
|
|
10857
11063
|
log.step(`▶ 候选铺轨(${layN} 轨 · 平铺 ${slotCount} 槽位 · ${clipIds.size} 个 clip,preview 代理)…`);
|
|
10858
11064
|
const gtrkDir = dirname9(gtrkPath);
|
|
10859
11065
|
const previewDir = join22(gtrkDir, ...BROLL_PREVIEW_DIR.split("/"));
|
|
10860
|
-
await
|
|
11066
|
+
await mkdir9(previewDir, { recursive: true });
|
|
10861
11067
|
const prevSource = new Map;
|
|
10862
11068
|
const prevBroll = gtrk.struct_meta?.broll;
|
|
10863
11069
|
for (const b of prevBroll?.beats ?? []) {
|
|
@@ -10918,8 +11124,8 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
|
|
|
10918
11124
|
forceRelay
|
|
10919
11125
|
});
|
|
10920
11126
|
if (summary.refused) {
|
|
10921
|
-
const
|
|
10922
|
-
log.err(`拒绝铺轨:${
|
|
11127
|
+
const list2 = summary.keptEditedTracks;
|
|
11128
|
+
log.err(`拒绝铺轨:${list2.length} 条候选轨已被你在客户端编辑过(track_index ${list2.join("/") || "-"})——` + "本次不剥它们、也不铺新轨,工程文件零改动。");
|
|
10923
11129
|
for (const w of warnings)
|
|
10924
11130
|
log.warn(w);
|
|
10925
11131
|
log.warn("下一步二选一:① 在客户端处置那条轨(删掉 / 移走 / 改用别的轨)后重跑本命令;" + "② 确知要丢弃那条轨上的编辑 → 加 --force-relay 强制剥离重铺" + "(会删掉已确认原片的 broll-raw-* 素材登记,盘上原片文件成孤儿,不可恢复)。");
|
|
@@ -10927,7 +11133,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
|
|
|
10927
11133
|
return {
|
|
10928
11134
|
lay: {
|
|
10929
11135
|
refused: true,
|
|
10930
|
-
keptEditedTracks:
|
|
11136
|
+
keptEditedTracks: list2,
|
|
10931
11137
|
laidTracks: [],
|
|
10932
11138
|
laidClips: 0,
|
|
10933
11139
|
removedTracks: [],
|
|
@@ -10945,7 +11151,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
|
|
|
10945
11151
|
const abs = join22(gtrkDir, ...rel.split("/"));
|
|
10946
11152
|
try {
|
|
10947
11153
|
if (!existsSync20(abs)) {
|
|
10948
|
-
await
|
|
11154
|
+
await mkdir9(dirname9(abs), { recursive: true });
|
|
10949
11155
|
const tmp = `${abs}.tmp-${process.pid}`;
|
|
10950
11156
|
await writeFile9(tmp, encodeSolidPng(spec));
|
|
10951
11157
|
await rename(tmp, abs);
|
|
@@ -11062,7 +11268,7 @@ function slugify3(name) {
|
|
|
11062
11268
|
// src/commands/mg.ts
|
|
11063
11269
|
import { resolve as resolve12, join as join23, dirname as dirname10, basename as basename13 } from "node:path";
|
|
11064
11270
|
import { existsSync as existsSync21 } from "node:fs";
|
|
11065
|
-
import { readFile as readFile7, mkdir as
|
|
11271
|
+
import { readFile as readFile7, mkdir as mkdir10, copyFile } from "node:fs/promises";
|
|
11066
11272
|
|
|
11067
11273
|
// src/lib/mg-lint.ts
|
|
11068
11274
|
var CDN_OK = [/lib\.baomitu\.com/i, /cdnjs\.cloudflare\.com/i, /unpkg\.com/i];
|
|
@@ -11926,9 +12132,9 @@ function detectPrimitiveLoops(html) {
|
|
|
11926
12132
|
}
|
|
11927
12133
|
const callsByFunction = new Map;
|
|
11928
12134
|
const pushCall = (fn, at) => {
|
|
11929
|
-
const
|
|
11930
|
-
|
|
11931
|
-
callsByFunction.set(fn.declStart,
|
|
12135
|
+
const list2 = callsByFunction.get(fn.declStart) ?? [];
|
|
12136
|
+
list2.push({ at, loops: executionLoopsAt(at) });
|
|
12137
|
+
callsByFunction.set(fn.declStart, list2);
|
|
11932
12138
|
};
|
|
11933
12139
|
const functionNames = new Set(functions.map((fn) => fn.name).filter((name) => name !== null));
|
|
11934
12140
|
for (const call of code.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
@@ -12129,21 +12335,27 @@ function filterDecls(styleDecls) {
|
|
|
12129
12335
|
out.push(m[1].trim());
|
|
12130
12336
|
return out;
|
|
12131
12337
|
}
|
|
12132
|
-
function
|
|
12338
|
+
function styleBlockRules(html) {
|
|
12133
12339
|
const out = [];
|
|
12134
12340
|
for (const sm of html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)) {
|
|
12135
12341
|
const css = sm[1].replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
12136
12342
|
const stack = [];
|
|
12343
|
+
const selStack = [];
|
|
12344
|
+
let segStart = 0;
|
|
12137
12345
|
for (let i = 0;i < css.length; i++) {
|
|
12138
12346
|
const c3 = css[i];
|
|
12139
12347
|
if (c3 === '"' || c3 === "'") {
|
|
12140
12348
|
i = skipString(css, i);
|
|
12141
12349
|
continue;
|
|
12142
12350
|
}
|
|
12143
|
-
if (c3 === "{")
|
|
12351
|
+
if (c3 === "{") {
|
|
12144
12352
|
stack.push(i);
|
|
12145
|
-
|
|
12353
|
+
selStack.push(css.slice(segStart, i));
|
|
12354
|
+
segStart = i + 1;
|
|
12355
|
+
} else if (c3 === "}") {
|
|
12146
12356
|
const open2 = stack.pop();
|
|
12357
|
+
const sel = selStack.pop() ?? "";
|
|
12358
|
+
segStart = i + 1;
|
|
12147
12359
|
if (open2 === undefined)
|
|
12148
12360
|
continue;
|
|
12149
12361
|
let own = css.slice(open2 + 1, i);
|
|
@@ -12151,8 +12363,9 @@ function styleBlockOwnDecls(html) {
|
|
|
12151
12363
|
prev = own;
|
|
12152
12364
|
own = own.replace(/\{[^{}]*\}/g, " ");
|
|
12153
12365
|
}
|
|
12154
|
-
out.push(own);
|
|
12155
|
-
}
|
|
12366
|
+
out.push({ selector: sel.trim(), own });
|
|
12367
|
+
} else if (c3 === ";")
|
|
12368
|
+
segStart = i + 1;
|
|
12156
12369
|
}
|
|
12157
12370
|
}
|
|
12158
12371
|
return out;
|
|
@@ -12212,21 +12425,138 @@ function tweenVarObjects(js) {
|
|
|
12212
12425
|
}
|
|
12213
12426
|
return out;
|
|
12214
12427
|
}
|
|
12428
|
+
var GSAP_TRANSFORM_KEY = /^(?:transform|x|y|z|xPercent|yPercent|scale|scaleX|scaleY|scaleZ|rotate|rotation|rotationX|rotationY|rotationZ|skew|skewX|skewY|translateX|translateY|translateZ)$/;
|
|
12429
|
+
var TWEENING_METHOD = /^(?:to|from|fromTo)$/;
|
|
12430
|
+
function selectorSubjectKeys(selector) {
|
|
12431
|
+
const keys = [];
|
|
12432
|
+
for (const alt of selector.split(",")) {
|
|
12433
|
+
const subject = alt.trim().split(/[\s>+~]+/).filter(Boolean).pop();
|
|
12434
|
+
if (!subject)
|
|
12435
|
+
continue;
|
|
12436
|
+
const core = subject.split(/[:\[]/)[0];
|
|
12437
|
+
for (const m of core.matchAll(/([.#])([A-Za-z_][\w-]*)/g))
|
|
12438
|
+
keys.push(`${m[1] === "#" ? "id" : "class"}:${m[2]}`);
|
|
12439
|
+
}
|
|
12440
|
+
return keys;
|
|
12441
|
+
}
|
|
12442
|
+
function tagIdentityKeys(tag) {
|
|
12443
|
+
const keys = [];
|
|
12444
|
+
const id = attr(tag, "id");
|
|
12445
|
+
if (id)
|
|
12446
|
+
keys.push(`id:${id.trim()}`);
|
|
12447
|
+
for (const cls of (attr(tag, "class") ?? "").trim().split(/\s+/))
|
|
12448
|
+
if (cls)
|
|
12449
|
+
keys.push(`class:${cls}`);
|
|
12450
|
+
return keys;
|
|
12451
|
+
}
|
|
12452
|
+
function tweenCalls(js) {
|
|
12453
|
+
const out = [];
|
|
12454
|
+
const re = /\.\s*(to|from|fromTo|set)\s*\(/g;
|
|
12455
|
+
let m;
|
|
12456
|
+
while (m = re.exec(js)) {
|
|
12457
|
+
const open2 = m.index + m[0].length - 1;
|
|
12458
|
+
const args = parenArg(js, open2);
|
|
12459
|
+
re.lastIndex = open2 + 1;
|
|
12460
|
+
if (args === null)
|
|
12461
|
+
continue;
|
|
12462
|
+
const target = readObjValue(args, 0);
|
|
12463
|
+
const bodies = [];
|
|
12464
|
+
for (let i = 0;i < args.length; i++) {
|
|
12465
|
+
const c3 = args[i];
|
|
12466
|
+
if (c3 === '"' || c3 === "'" || c3 === "`") {
|
|
12467
|
+
i = skipString(args, i);
|
|
12468
|
+
continue;
|
|
12469
|
+
}
|
|
12470
|
+
if (c3 !== "{")
|
|
12471
|
+
continue;
|
|
12472
|
+
const body = braceBlock(args, i);
|
|
12473
|
+
bodies.push(body);
|
|
12474
|
+
i += body.length + 1;
|
|
12475
|
+
}
|
|
12476
|
+
out.push({ method: m[1], target, bodies });
|
|
12477
|
+
}
|
|
12478
|
+
return out;
|
|
12479
|
+
}
|
|
12480
|
+
function selectorBindings(js) {
|
|
12481
|
+
const out = new Map;
|
|
12482
|
+
const re = /([A-Za-z_$][\w$]*)\s*=\s*(?:[A-Za-z_$][\w$]*\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\(\s*(["'])(.*?)\3\s*\)/g;
|
|
12483
|
+
for (const m of js.matchAll(re)) {
|
|
12484
|
+
const [, name, fn, , raw] = m;
|
|
12485
|
+
const keys = fn === "getElementById" ? [`id:${raw.trim()}`] : /^\s*[.#]/.test(raw) ? selectorSubjectKeys(raw) : [];
|
|
12486
|
+
if (!keys.length)
|
|
12487
|
+
continue;
|
|
12488
|
+
const prev = out.get(name) ?? [];
|
|
12489
|
+
out.set(name, [...new Set([...prev, ...keys])]);
|
|
12490
|
+
}
|
|
12491
|
+
return out;
|
|
12492
|
+
}
|
|
12493
|
+
function tweenTargetKeys(target, bindings) {
|
|
12494
|
+
const t = target.trim();
|
|
12495
|
+
const lit = stringLiteral(t);
|
|
12496
|
+
if (lit !== null)
|
|
12497
|
+
return /^\s*[.#]/.test(lit) ? selectorSubjectKeys(lit) : [];
|
|
12498
|
+
if (/^[A-Za-z_$][\w$]*$/.test(t))
|
|
12499
|
+
return bindings.get(t) ?? [];
|
|
12500
|
+
return [];
|
|
12501
|
+
}
|
|
12502
|
+
function topLevelObjKeys(body) {
|
|
12503
|
+
const keys = [];
|
|
12504
|
+
let depth = 0;
|
|
12505
|
+
let expectKey = true;
|
|
12506
|
+
for (let i = 0;i < body.length; i++) {
|
|
12507
|
+
const c3 = body[i];
|
|
12508
|
+
if (c3 === '"' || c3 === "'" || c3 === "`") {
|
|
12509
|
+
if (depth === 0 && expectKey) {
|
|
12510
|
+
const end = skipString(body, i);
|
|
12511
|
+
const name = body.slice(i + 1, end);
|
|
12512
|
+
const rest = body.slice(end + 1);
|
|
12513
|
+
if (/^\s*:/.test(rest)) {
|
|
12514
|
+
keys.push(name);
|
|
12515
|
+
expectKey = false;
|
|
12516
|
+
}
|
|
12517
|
+
}
|
|
12518
|
+
i = skipString(body, i);
|
|
12519
|
+
continue;
|
|
12520
|
+
}
|
|
12521
|
+
if (c3 === "(" || c3 === "[" || c3 === "{")
|
|
12522
|
+
depth++;
|
|
12523
|
+
else if (c3 === ")" || c3 === "]" || c3 === "}")
|
|
12524
|
+
depth = Math.max(0, depth - 1);
|
|
12525
|
+
else if (c3 === "," && depth === 0)
|
|
12526
|
+
expectKey = true;
|
|
12527
|
+
else if (depth === 0 && expectKey && /[A-Za-z_$]/.test(c3)) {
|
|
12528
|
+
const m = /^([A-Za-z_$][\w$]*)\s*:/.exec(body.slice(i));
|
|
12529
|
+
if (m) {
|
|
12530
|
+
keys.push(m[1]);
|
|
12531
|
+
expectKey = false;
|
|
12532
|
+
i += m[1].length;
|
|
12533
|
+
}
|
|
12534
|
+
}
|
|
12535
|
+
}
|
|
12536
|
+
return keys;
|
|
12537
|
+
}
|
|
12215
12538
|
function detectFilterCost(html) {
|
|
12216
12539
|
const src = maskHtmlComments(html);
|
|
12217
12540
|
const svgConvIds = svgConvolutionFilterIds(src);
|
|
12218
12541
|
const animated = [];
|
|
12219
12542
|
const staticFullBleed = [];
|
|
12543
|
+
const staticTransformed = [];
|
|
12220
12544
|
const indeterminate = [];
|
|
12221
12545
|
const add = (bucket, site) => {
|
|
12222
12546
|
const s = site.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
12223
12547
|
if (!bucket.includes(s))
|
|
12224
12548
|
bucket.push(s);
|
|
12225
12549
|
};
|
|
12226
|
-
|
|
12550
|
+
const staticFilterSites = new Map;
|
|
12551
|
+
for (const { selector, own } of styleBlockRules(src)) {
|
|
12227
12552
|
const hit = filterDecls(own).filter((val) => filterValueConvolves(val, svgConvIds));
|
|
12228
|
-
if (hit.length
|
|
12553
|
+
if (!hit.length)
|
|
12554
|
+
continue;
|
|
12555
|
+
if (isFullBleedStyle(own))
|
|
12229
12556
|
add(staticFullBleed, `<style> 规则:filter:${hit[0]}`);
|
|
12557
|
+
for (const key of selectorSubjectKeys(selector))
|
|
12558
|
+
if (!staticFilterSites.has(key))
|
|
12559
|
+
staticFilterSites.set(key, `<style> 规则 ${selector.slice(0, 60)}:filter:${hit[0]}`);
|
|
12230
12560
|
}
|
|
12231
12561
|
for (const m of src.matchAll(/<[a-zA-Z][^>]*>/g)) {
|
|
12232
12562
|
const tag = m[0];
|
|
@@ -12236,9 +12566,13 @@ function detectFilterCost(html) {
|
|
|
12236
12566
|
const byAttr = refAttr !== undefined && filterValueConvolves(refAttr, svgConvIds) ? refAttr : null;
|
|
12237
12567
|
if (!inline.length && byAttr === null)
|
|
12238
12568
|
continue;
|
|
12569
|
+
const site = inline.length ? `内联 style:filter:${inline[0]}` : `SVG 属性:filter="${byAttr}"`;
|
|
12570
|
+
for (const key of tagIdentityKeys(tag))
|
|
12571
|
+
if (!staticFilterSites.has(key))
|
|
12572
|
+
staticFilterSites.set(key, site);
|
|
12239
12573
|
if (!isFullBleedStyle(style))
|
|
12240
12574
|
continue;
|
|
12241
|
-
add(staticFullBleed,
|
|
12575
|
+
add(staticFullBleed, site);
|
|
12242
12576
|
}
|
|
12243
12577
|
const js = maskJsComments(scriptBodiesOnly(src));
|
|
12244
12578
|
for (const body of tweenVarObjects(js)) {
|
|
@@ -12253,7 +12587,22 @@ function detectFilterCost(html) {
|
|
|
12253
12587
|
if (svgConvIds.size && /(?:^|[{,])\s*["']?stdDeviation["']?\s*:/.test(body))
|
|
12254
12588
|
add(animated, "补间驱动 SVG 滤镜基元的 stdDeviation");
|
|
12255
12589
|
}
|
|
12256
|
-
|
|
12590
|
+
if (staticFilterSites.size) {
|
|
12591
|
+
const bindings = selectorBindings(js);
|
|
12592
|
+
for (const { method, target, bodies } of tweenCalls(js)) {
|
|
12593
|
+
if (!TWEENING_METHOD.test(method))
|
|
12594
|
+
continue;
|
|
12595
|
+
const driven = bodies.some((b) => topLevelObjKeys(b).some((k) => GSAP_TRANSFORM_KEY.test(k)));
|
|
12596
|
+
if (!driven)
|
|
12597
|
+
continue;
|
|
12598
|
+
for (const key of tweenTargetKeys(target, bindings)) {
|
|
12599
|
+
const site = staticFilterSites.get(key);
|
|
12600
|
+
if (site)
|
|
12601
|
+
add(staticTransformed, `${site} —— 而 \`.${method}(${target.trim().slice(0, 30)}, …)\` 驱动了它的 transform`);
|
|
12602
|
+
}
|
|
12603
|
+
}
|
|
12604
|
+
}
|
|
12605
|
+
return { animated, staticFullBleed, staticTransformed, indeterminate };
|
|
12257
12606
|
}
|
|
12258
12607
|
function lintParticle(html, opts = {}) {
|
|
12259
12608
|
const v = [];
|
|
@@ -12359,6 +12708,8 @@ function lintParticle(html, opts = {}) {
|
|
|
12359
12708
|
push("c-filter-animated", false, `时间线补间直接驱动了真卷积滤镜(${cost.animated.slice(0, 3).join(";")})——` + "blur / drop-shadow / feGaussianBlur 是**真卷积**,每帧成本 ∝「被覆盖面积 × 半径 × 帧数」;" + "一旦被补间驱动,卷积结果**每帧失效**、缓存彻底失灵,这是本组三项里成本机制最重的一项。" + "改法:用 `opacity` / `transform`(`scale` / 位移)表达同一叙事动作;" + "确需滤镜就做成**静态两态切换**(滤镜值不随时间连续变),并把被滤元素的几何覆盖面积收到真正需要的那块矩形上。" + "本项是**成本提示**,不影响 ok / 退出码 / 铺轨;**未报 ≠ 这颗便宜**——真判据是真渲染出片计时");
|
|
12360
12709
|
if (cost.staticFullBleed.length)
|
|
12361
12710
|
push("c-filter-static-fullbleed", false, `整幅静态真卷积滤镜(${cost.staticFullBleed.slice(0, 3).join(";")})——该声明自身即全幅` + "(`position:absolute|fixed` + `inset:0` 或等价铺满),于是每帧都要对整幅做一次卷积。" + "**首选改法:缩小被滤镜覆盖的几何面积**(把滤镜收到它真正需要的那块矩形上——面积一项同时压掉卷积与整幅逐帧合成两笔成本);" + "次选把静态滤镜结果**预烘成图**,且 MUST 用 **RGBA PNG**(透明叠加颗粒的 alpha 是成片合成的必需通道," + "烘成 JPEG 或任何无 alpha 格式会在成片里塌成不透明色块),但预烘只消得掉卷积、消不掉整幅逐帧合成,收益有上限、必有残差。" + "去不去滤镜属审美取值,判断权在作者:本项只给信息与改法,**恒非致命**、不拦铺轨;未报 ≠ 便宜,真判据是真渲染出片计时");
|
|
12711
|
+
if (cost.staticTransformed.length)
|
|
12712
|
+
push("c-filter-static-transformed", false, `静态真卷积滤镜的元素,其 transform 被补间驱动(${cost.staticTransformed.slice(0, 3).join(";")})——` + "滤镜值本身不变,但被滤子树**每帧换一个几何**,卷积结果每帧失效、必须重算," + "与 `c-filter-animated` 是**同一类缓存失效**,只是驱动源从滤镜值换成了 transform。" + "r69 真渲实测:同颗粒对照下这一档 **+42.7%**,比「补间驱动 blur」的 +36.2% **还贵**——它是本组里最容易被忽略的一档。" + "改法(按优先级):① 把滤镜移到**不参与位移/缩放**的那一层(滤镜层与运动层拆开,运动交给外层容器);" + "② 缩小被滤元素的几何覆盖面积;③ 把静态滤镜结果**预烘成 RGBA PNG** 再让它随 transform 走(预烘图平移是零卷积)。" + "⚠️ 射程只收 `transform`:`opacity` 被补间驱动**不在本项射程**(opacity 侧尚无对照实测,不凭推测扩)。" + "本项恒非致命、不拦铺轨;未报 ≠ 便宜,真判据是真渲染出片计时");
|
|
12362
12713
|
if (cost.indeterminate.length)
|
|
12363
12714
|
push("c-filter-indeterminate", false, `该处 filter 值无法静态判定是否含真卷积(${cost.indeterminate.slice(0, 3).join(";")})——` + "补间值不是字符串字面量(变量 / 模板串 / 函数返回),本 lint 不做表达式求值与常量折叠," + "故对该处**未作判定**:既不是「判过且通过」,也不是命中。请人工确认它是否会驱动 blur / drop-shadow;" + "若是,按 `c-filter-animated` 的改法处理。本项恒非致命、不拦铺轨");
|
|
12364
12715
|
return { ok: !v.some((x) => x.fatal), violations: v, opaque, compositionId: cid };
|
|
@@ -12781,7 +13132,7 @@ async function runLay(opts) {
|
|
|
12781
13132
|
if (opts.replaceAll && orphans.length > 0) {
|
|
12782
13133
|
log.warn(`--replace-all:已显式授权重置整轨,轨上其余 ${orphans.length} 颗已铺颗粒将被剥离(不走增量保留)。`);
|
|
12783
13134
|
}
|
|
12784
|
-
await
|
|
13135
|
+
await mkdir10(join23(gtrkDir, ...MG_ASSET_DIR.split("/")), { recursive: true });
|
|
12785
13136
|
for (const it of items) {
|
|
12786
13137
|
await copyFile(srcByComp.get(it.composition_id), join23(gtrkDir, ...it.html_rel.split("/")));
|
|
12787
13138
|
}
|
|
@@ -12898,12 +13249,12 @@ function done(opts, result) {
|
|
|
12898
13249
|
}
|
|
12899
13250
|
|
|
12900
13251
|
// src/lib/mad/mad.ts
|
|
12901
|
-
import { mkdir as
|
|
13252
|
+
import { mkdir as mkdir13, writeFile as writeFile11 } from "node:fs/promises";
|
|
12902
13253
|
import { existsSync as existsSync24, statSync as statSync3 } from "node:fs";
|
|
12903
13254
|
import { resolve as resolve13, join as join27 } from "node:path";
|
|
12904
13255
|
|
|
12905
13256
|
// src/lib/convert/types.ts
|
|
12906
|
-
function
|
|
13257
|
+
function num2(v, d = 0) {
|
|
12907
13258
|
return typeof v === "number" && isFinite(v) ? v : d;
|
|
12908
13259
|
}
|
|
12909
13260
|
function parseCubicBezier(e) {
|
|
@@ -12952,19 +13303,19 @@ function mergeChannelTracks(ta, tb, defA, defB) {
|
|
|
12952
13303
|
if (!track || track.length === 0)
|
|
12953
13304
|
return dflt;
|
|
12954
13305
|
if (t <= track[0].t)
|
|
12955
|
-
return
|
|
13306
|
+
return num2(track[0].v, dflt);
|
|
12956
13307
|
for (let i = 1;i < track.length; i++) {
|
|
12957
13308
|
if (t <= track[i].t) {
|
|
12958
13309
|
const p = track[i - 1];
|
|
12959
13310
|
const q = track[i];
|
|
12960
13311
|
const span = q.t - p.t;
|
|
12961
13312
|
if (span <= 0)
|
|
12962
|
-
return
|
|
13313
|
+
return num2(q.v, dflt);
|
|
12963
13314
|
const k = (t - p.t) / span;
|
|
12964
|
-
return
|
|
13315
|
+
return num2(p.v, dflt) + (num2(q.v, dflt) - num2(p.v, dflt)) * k;
|
|
12965
13316
|
}
|
|
12966
13317
|
}
|
|
12967
|
-
return
|
|
13318
|
+
return num2(track[track.length - 1].v, dflt);
|
|
12968
13319
|
};
|
|
12969
13320
|
const times = new Set;
|
|
12970
13321
|
for (const k of ta ?? [])
|
|
@@ -12985,17 +13336,17 @@ function sampleTrack(track, t, def) {
|
|
|
12985
13336
|
if (!track || track.length === 0)
|
|
12986
13337
|
return def;
|
|
12987
13338
|
if (t <= track[0].t)
|
|
12988
|
-
return
|
|
13339
|
+
return num2(track[0].v, def);
|
|
12989
13340
|
for (let i = 1;i < track.length; i++) {
|
|
12990
13341
|
const a = track[i - 1];
|
|
12991
13342
|
const b = track[i];
|
|
12992
13343
|
if (t <= b.t) {
|
|
12993
13344
|
const span = Math.max(b.t - a.t, 0.000001);
|
|
12994
13345
|
const k = (t - a.t) / span;
|
|
12995
|
-
return
|
|
13346
|
+
return num2(a.v, def) + (num2(b.v, def) - num2(a.v, def)) * k;
|
|
12996
13347
|
}
|
|
12997
13348
|
}
|
|
12998
|
-
return
|
|
13349
|
+
return num2(track[track.length - 1].v, def);
|
|
12999
13350
|
}
|
|
13000
13351
|
function phasePair(i) {
|
|
13001
13352
|
return [Math.sin(i * 2.399), Math.cos(i * 3.11)];
|
|
@@ -13023,13 +13374,13 @@ function baseRot(anim, t) {
|
|
|
13023
13374
|
return sampleTrack(anim.rot, t, 0);
|
|
13024
13375
|
}
|
|
13025
13376
|
function opWindow(fx, ly) {
|
|
13026
|
-
const t0 =
|
|
13027
|
-
const t1 =
|
|
13377
|
+
const t0 = num2(fx.t0, num2(ly.in, 0));
|
|
13378
|
+
const t1 = num2(fx.t1, num2(ly.out, t0 + 1));
|
|
13028
13379
|
return [t0, t1];
|
|
13029
13380
|
}
|
|
13030
13381
|
function bakeLayerOps(ly) {
|
|
13031
13382
|
const anim = ly.anim ?? {};
|
|
13032
|
-
const pos = [
|
|
13383
|
+
const pos = [num2(ly.pos?.[0], 0), num2(ly.pos?.[1], 0)];
|
|
13033
13384
|
const tracks = [];
|
|
13034
13385
|
const warnings = [];
|
|
13035
13386
|
for (const fx of ly.fx ?? []) {
|
|
@@ -13045,8 +13396,8 @@ function bakeLayerOps(ly) {
|
|
|
13045
13396
|
const times = [];
|
|
13046
13397
|
const values = [];
|
|
13047
13398
|
for (const k of tr) {
|
|
13048
|
-
const t =
|
|
13049
|
-
const v =
|
|
13399
|
+
const t = num2(k.t, 0);
|
|
13400
|
+
const v = num2(k.v, 1);
|
|
13050
13401
|
const [bx, by] = baseScale(anim, t);
|
|
13051
13402
|
times.push(round(t, 3));
|
|
13052
13403
|
values.push([round(bx * v * 100, 3), round(by * v * 100, 3)]);
|
|
@@ -13059,9 +13410,9 @@ function bakeLayerOps(ly) {
|
|
|
13059
13410
|
if (op === "flicker") {
|
|
13060
13411
|
if (t1 - t0 < 0.05)
|
|
13061
13412
|
continue;
|
|
13062
|
-
let freq =
|
|
13413
|
+
let freq = num2(fx.freq, 8);
|
|
13063
13414
|
freq = clamp(freq >= 0.5 ? freq : freq * 30, 0.5, 20);
|
|
13064
|
-
const mag = clamp(
|
|
13415
|
+
const mag = clamp(num2(fx.mag, 0.6), 0.05, 1);
|
|
13065
13416
|
const hi = 1 + mag * 0.8;
|
|
13066
13417
|
const lo = Math.max(0.3, 1 - mag * 0.6);
|
|
13067
13418
|
const n = Math.min(dn(Math.max(2, Math.floor((t1 - t0) * freq * 2))), 160);
|
|
@@ -13080,8 +13431,8 @@ function bakeLayerOps(ly) {
|
|
|
13080
13431
|
if (t1 - t0 < 0.05)
|
|
13081
13432
|
continue;
|
|
13082
13433
|
if (op === "shake") {
|
|
13083
|
-
const amp0 =
|
|
13084
|
-
const freq = clamp(
|
|
13434
|
+
const amp0 = num2(fx.mag ?? fx.amp, 18);
|
|
13435
|
+
const freq = clamp(num2(fx.freq, 20), 1, 40);
|
|
13085
13436
|
const n = Math.min(dn(Math.max(2, Math.floor((t1 - t0) * freq))), 160);
|
|
13086
13437
|
const tr = fx.tracks?.mag;
|
|
13087
13438
|
const seqX = [];
|
|
@@ -13111,9 +13462,9 @@ function bakeLayerOps(ly) {
|
|
|
13111
13462
|
}
|
|
13112
13463
|
if (op === "oscillate") {
|
|
13113
13464
|
if (fx.rot === true) {
|
|
13114
|
-
const a1 =
|
|
13115
|
-
const a2 =
|
|
13116
|
-
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);
|
|
13117
13468
|
const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 120);
|
|
13118
13469
|
const times = [round(t0, 3)];
|
|
13119
13470
|
const values = [[round(baseRot(anim, t0), 3)]];
|
|
@@ -13130,9 +13481,9 @@ function bakeLayerOps(ly) {
|
|
|
13130
13481
|
}
|
|
13131
13482
|
tracks.push({ prop: "rotation", times, values, window: [t0, t1] });
|
|
13132
13483
|
} else {
|
|
13133
|
-
const mag0 =
|
|
13134
|
-
const ang =
|
|
13135
|
-
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);
|
|
13136
13487
|
const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 160);
|
|
13137
13488
|
const tr = fx.tracks?.mag;
|
|
13138
13489
|
const times = [round(t0, 3)];
|
|
@@ -13164,13 +13515,13 @@ function bakeLayerOps(ly) {
|
|
|
13164
13515
|
let lo;
|
|
13165
13516
|
let hi;
|
|
13166
13517
|
if (fx.amp != null) {
|
|
13167
|
-
lo = 1 -
|
|
13168
|
-
hi = 1 +
|
|
13518
|
+
lo = 1 - num2(fx.amp, 0);
|
|
13519
|
+
hi = 1 + num2(fx.amp, 0);
|
|
13169
13520
|
} else {
|
|
13170
|
-
lo =
|
|
13171
|
-
hi =
|
|
13521
|
+
lo = num2(fx.min, 1);
|
|
13522
|
+
hi = num2(fx.max, 1.06);
|
|
13172
13523
|
}
|
|
13173
|
-
const freq = clamp(
|
|
13524
|
+
const freq = clamp(num2(fx.freq, 2), 0.2, 20);
|
|
13174
13525
|
const n = Math.min(dn(Math.max(4, Math.floor((t1 - t0) * freq * 4))), 160);
|
|
13175
13526
|
const tr = fx.tracks?.max;
|
|
13176
13527
|
const times = [round(t0, 3)];
|
|
@@ -13332,7 +13683,7 @@ function positionBaseKeys(anim, pos) {
|
|
|
13332
13683
|
function scaleBaseKeys(anim, coverExpr) {
|
|
13333
13684
|
const sc = (v) => coverExpr ? `${r37(v)}*${coverExpr}` : `${r37(v)}`;
|
|
13334
13685
|
if (anim.scale?.length) {
|
|
13335
|
-
return anim.scale.map((k) => ({ t: k.t, value: `[${sc(
|
|
13686
|
+
return anim.scale.map((k) => ({ t: k.t, value: `[${sc(num2(k.v, 1) * 100)}, ${sc(num2(k.v, 1) * 100)}]`, e: k.e }));
|
|
13336
13687
|
}
|
|
13337
13688
|
if (anim.sx?.length || anim.sy?.length) {
|
|
13338
13689
|
const merged = mergeChannelTracks(anim.sx, anim.sy, 1, 1);
|
|
@@ -13347,9 +13698,9 @@ function emitBakedOps(ctx, ly, layerVar, tf, coverExpr) {
|
|
|
13347
13698
|
if (tracks.length === 0)
|
|
13348
13699
|
return;
|
|
13349
13700
|
const anim = ly.anim ?? {};
|
|
13350
|
-
const pos = [
|
|
13351
|
-
const inn =
|
|
13352
|
-
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));
|
|
13353
13704
|
const sc = (v) => coverExpr ? `${r37(v)}*${coverExpr}` : `${r37(v)}`;
|
|
13354
13705
|
const brights = tracks.filter((t) => t.prop === "brightness");
|
|
13355
13706
|
if (brights.length) {
|
|
@@ -13396,7 +13747,7 @@ function emitBakedOps(ctx, ly, layerVar, tf, coverExpr) {
|
|
|
13396
13747
|
}
|
|
13397
13748
|
const rotBaked = tracks.filter((t) => t.prop === "rotation");
|
|
13398
13749
|
if (rotBaked.length) {
|
|
13399
|
-
const baseKeys = (anim.rot ?? []).map((k) => ({ t: k.t, value: `${r37(
|
|
13750
|
+
const baseKeys = (anim.rot ?? []).map((k) => ({ t: k.t, value: `${r37(num2(k.v, 0))}`, e: k.e }));
|
|
13400
13751
|
const bakedKeys = [];
|
|
13401
13752
|
const windows = [];
|
|
13402
13753
|
for (const bt of rotBaked) {
|
|
@@ -13414,10 +13765,10 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
|
|
|
13414
13765
|
const c3 = ir.canvas;
|
|
13415
13766
|
const id = ++ctx.layerSeq;
|
|
13416
13767
|
const v = `ly${id}`;
|
|
13417
|
-
const inn =
|
|
13418
|
-
const out = Math.max(inn + 0.01,
|
|
13419
|
-
const px =
|
|
13420
|
-
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);
|
|
13421
13772
|
const name = esc(`${ly.id || "L" + id} [${ly.type}]`);
|
|
13422
13773
|
L.push(``);
|
|
13423
13774
|
L.push(` // ---- 层 ${name} in=${r37(inn)} out=${r37(out)} ----`);
|
|
@@ -13439,9 +13790,9 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
|
|
|
13439
13790
|
const footageHit = ly.type === "image" || ly.type === "video" ? ctx.footage[ly.id] : undefined;
|
|
13440
13791
|
const fgVar = footageHit ? ctx.footageVars.get(footageHit.path) : undefined;
|
|
13441
13792
|
if (footageHit && fgVar) {
|
|
13442
|
-
const slotW = Math.max(2, Math.round(
|
|
13443
|
-
const slotH = Math.max(2, Math.round(
|
|
13444
|
-
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);
|
|
13445
13796
|
const cvv = `cv${id}`;
|
|
13446
13797
|
coverExpr = cvv;
|
|
13447
13798
|
L.push(` // 素材注入(${esc(String(ly.type))}): footage srcOffset=${r37(srcOffset)}`);
|
|
@@ -13472,14 +13823,14 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
|
|
|
13472
13823
|
L.push(` } catch (e) {}`);
|
|
13473
13824
|
} else if (ly.type === "shape") {
|
|
13474
13825
|
const col = colorArr(ly.fill, [0.2, 0.33, 0.67]);
|
|
13475
|
-
const w = Math.max(2, Math.round(
|
|
13476
|
-
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)));
|
|
13477
13828
|
if (ly.shape === "ellipse")
|
|
13478
13829
|
L.push(` // TODO: 原层为椭圆形状,固态占位,可手动换 shape layer`);
|
|
13479
13830
|
L.push(` var ${v} = ${cv}.layers.addSolid([${col.join(",")}], "${name}", ${w}, ${h}, 1, ${r37(c3.duration)});`);
|
|
13480
13831
|
} else {
|
|
13481
|
-
const w = Math.max(2, Math.round(
|
|
13482
|
-
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)));
|
|
13483
13834
|
L.push(` // 占位素材(${esc(String(ly.type))}${ly.asset ? ` asset=${esc(String(ly.asset))}` : ""}): 请替换为真实素材`);
|
|
13484
13835
|
L.push(` var ${v} = ${cv}.layers.addSolid([0.35,0.35,0.4], "${name} [占位]", ${w}, ${h}, 1, ${r37(c3.duration)});`);
|
|
13485
13836
|
}
|
|
@@ -13509,10 +13860,10 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
|
|
|
13509
13860
|
}
|
|
13510
13861
|
}
|
|
13511
13862
|
if (anim.rot?.length && !bakedProps.has("rotation")) {
|
|
13512
|
-
emitKeys(ctx, `${tf}.property("ADBE Rotate Z")`, anim.rot.map((k) => ({ t: k.t, value: `${r37(
|
|
13863
|
+
emitKeys(ctx, `${tf}.property("ADBE Rotate Z")`, anim.rot.map((k) => ({ t: k.t, value: `${r37(num2(k.v, 0))}`, e: k.e })));
|
|
13513
13864
|
}
|
|
13514
13865
|
if (anim.opacity?.length) {
|
|
13515
|
-
emitKeys(ctx, `${tf}.property("ADBE Opacity")`, anim.opacity.map((k) => ({ t: k.t, value: `${r37(Math.min(1, Math.max(0,
|
|
13866
|
+
emitKeys(ctx, `${tf}.property("ADBE Opacity")`, anim.opacity.map((k) => ({ t: k.t, value: `${r37(Math.min(1, Math.max(0, num2(k.v, 1))) * 100)}`, e: k.e })));
|
|
13516
13867
|
}
|
|
13517
13868
|
if (anim.ls?.length) {
|
|
13518
13869
|
L.push(` // TODO: letterspacing 轨未自动映射(AE 需 Animator>Tracking),共 ${anim.ls.length} 帧`);
|
|
@@ -13526,8 +13877,8 @@ function emitLayer(ctx, ir, ly, parentNullVar) {
|
|
|
13526
13877
|
}
|
|
13527
13878
|
function emitGroupAnim(ctx, v, ly) {
|
|
13528
13879
|
const anim = ly.anim ?? {};
|
|
13529
|
-
const px =
|
|
13530
|
-
const py =
|
|
13880
|
+
const px = num2(ly.pos?.[0], 0);
|
|
13881
|
+
const py = num2(ly.pos?.[1], 0);
|
|
13531
13882
|
if (anim.x?.length || anim.y?.length) {
|
|
13532
13883
|
const merged = mergeChannelTracks(anim.x, anim.y, px, py);
|
|
13533
13884
|
emitKeys(ctx, `${v}.property("ADBE Transform Group").property("ADBE Position")`, merged.map((k) => ({ t: k.t, value: `[${r37(k.a - px)}, ${r37(k.b - py)}]`, e: k.e })));
|
|
@@ -13611,10 +13962,10 @@ function madJsx(opts) {
|
|
|
13611
13962
|
const subVars = [];
|
|
13612
13963
|
windows.forEach((win, wi) => {
|
|
13613
13964
|
const c3 = win.ir.canvas;
|
|
13614
|
-
const sw = Math.max(4, Math.round(
|
|
13615
|
-
const sh = Math.max(4, Math.round(
|
|
13616
|
-
const sfps = Math.min(99, Math.max(1,
|
|
13617
|
-
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));
|
|
13618
13969
|
const subName = esc(`${win.uid}-${win.seq}`);
|
|
13619
13970
|
const subVar = `sub${wi}`;
|
|
13620
13971
|
L.push(``);
|
|
@@ -13870,7 +14221,7 @@ function beatQuantized(natLens, analysis) {
|
|
|
13870
14221
|
|
|
13871
14222
|
// src/lib/mad/data.ts
|
|
13872
14223
|
import { createHash as createHash2 } from "node:crypto";
|
|
13873
|
-
import { mkdir as
|
|
14224
|
+
import { mkdir as mkdir11, readFile as readFile8, rename as rename2, rm, writeFile as writeFile10, readdir as readdir2 } from "node:fs/promises";
|
|
13874
14225
|
import { existsSync as existsSync22 } from "node:fs";
|
|
13875
14226
|
import { join as join25 } from "node:path";
|
|
13876
14227
|
function madCacheDir() {
|
|
@@ -13925,7 +14276,7 @@ function validateManifest(obj) {
|
|
|
13925
14276
|
}
|
|
13926
14277
|
async function cleanupOldVersions(cacheRoot, keepVersion, warn) {
|
|
13927
14278
|
try {
|
|
13928
|
-
const entries = await
|
|
14279
|
+
const entries = await readdir2(cacheRoot);
|
|
13929
14280
|
for (const e of entries) {
|
|
13930
14281
|
const m = /^v(\d+)$/.exec(e);
|
|
13931
14282
|
if (m && Number(m[1]) !== keepVersion) {
|
|
@@ -13980,7 +14331,7 @@ async function ensureMadData(opts, deps) {
|
|
|
13980
14331
|
}
|
|
13981
14332
|
throw new Error("首次使用需联网下载技法数据。请连网后重跑 `gtrk tool mad`(数据会缓存到本地,之后可离线出片)。");
|
|
13982
14333
|
}
|
|
13983
|
-
await
|
|
14334
|
+
await mkdir11(verDir, { recursive: true });
|
|
13984
14335
|
warn(`下载技法池数据 mad_pool(版本 v${version},约 ${Math.round(poolMeta.size / 1024)} KB)…`);
|
|
13985
14336
|
const res = await deps.fetchFn(poolMeta.url);
|
|
13986
14337
|
if (!res.ok)
|
|
@@ -13993,7 +14344,7 @@ async function ensureMadData(opts, deps) {
|
|
|
13993
14344
|
warn(`技法池数据就绪(v${version})`);
|
|
13994
14345
|
}
|
|
13995
14346
|
if (online) {
|
|
13996
|
-
await
|
|
14347
|
+
await mkdir11(cacheRoot, { recursive: true });
|
|
13997
14348
|
await atomicWrite(snapshotPath, new Uint8Array(Buffer.from(JSON.stringify(manifest), "utf8")));
|
|
13998
14349
|
await cleanupOldVersions(cacheRoot, version, warn);
|
|
13999
14350
|
}
|
|
@@ -14010,7 +14361,7 @@ async function ensureMadData(opts, deps) {
|
|
|
14010
14361
|
|
|
14011
14362
|
// src/lib/mad/pool.ts
|
|
14012
14363
|
import { gunzipSync } from "node:zlib";
|
|
14013
|
-
import { mkdir as
|
|
14364
|
+
import { mkdir as mkdir12, readFile as readFile9 } from "node:fs/promises";
|
|
14014
14365
|
import { existsSync as existsSync23 } from "node:fs";
|
|
14015
14366
|
import { join as join26 } from "node:path";
|
|
14016
14367
|
function shardPath(verDir, shard) {
|
|
@@ -14049,7 +14400,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
|
|
|
14049
14400
|
throw new Error(`IR 分片下载失败 HTTP ${res.status}:${url}`);
|
|
14050
14401
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
14051
14402
|
const s = decodeShard(buf);
|
|
14052
|
-
await
|
|
14403
|
+
await mkdir12(join26(verDir, "ir"), { recursive: true });
|
|
14053
14404
|
await atomicWrite(path, buf);
|
|
14054
14405
|
memo.set(shard, s);
|
|
14055
14406
|
return s;
|
|
@@ -14262,7 +14613,7 @@ async function runMad(inputArg, opts, deps = {}) {
|
|
|
14262
14613
|
const bgm = level <= 2 && bgmForTrack ? { path: bgmForTrack, markers } : undefined;
|
|
14263
14614
|
const { jsx } = madJsx({ master, windows, header, bgm });
|
|
14264
14615
|
const outDir = opts.out ? resolve13(opts.out) : join27(process.cwd(), `mad-${timestamp4(now)}`);
|
|
14265
|
-
await
|
|
14616
|
+
await mkdir13(outDir, { recursive: true });
|
|
14266
14617
|
const jsxPath = join27(outDir, "mad.jsx");
|
|
14267
14618
|
await writeFile11(jsxPath, jsx);
|
|
14268
14619
|
const result = {
|
|
@@ -14409,7 +14760,7 @@ async function runMadInTool(inputArg, opts) {
|
|
|
14409
14760
|
|
|
14410
14761
|
// src/commands/transcript.ts
|
|
14411
14762
|
import { existsSync as existsSync25 } from "node:fs";
|
|
14412
|
-
import { mkdir as
|
|
14763
|
+
import { mkdir as mkdir14, rename as rename3, rm as rm2, stat as stat6, writeFile as writeFile12 } from "node:fs/promises";
|
|
14413
14764
|
import { basename as basename14, dirname as dirname11, extname as extname8, join as join28, resolve as resolve14 } from "node:path";
|
|
14414
14765
|
|
|
14415
14766
|
// src/lib/transcript.ts
|
|
@@ -14566,7 +14917,7 @@ function resolveTranscriptOutput(inputAbs, out) {
|
|
|
14566
14917
|
return output;
|
|
14567
14918
|
}
|
|
14568
14919
|
async function writeMarkdownAtomic(path, markdown) {
|
|
14569
|
-
await
|
|
14920
|
+
await mkdir14(dirname11(path), { recursive: true });
|
|
14570
14921
|
const temp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
14571
14922
|
try {
|
|
14572
14923
|
await writeFile12(temp, markdown, "utf8");
|
|
@@ -14644,7 +14995,7 @@ function registerTranscript(program2) {
|
|
|
14644
14995
|
|
|
14645
14996
|
// src/commands/music-visualizer.ts
|
|
14646
14997
|
import { resolve as resolve15, join as join29, dirname as dirname12, basename as basename15, extname as extname9 } from "node:path";
|
|
14647
|
-
import { mkdir as
|
|
14998
|
+
import { mkdir as mkdir15, writeFile as writeFile13 } from "node:fs/promises";
|
|
14648
14999
|
import { existsSync as existsSync26 } from "node:fs";
|
|
14649
15000
|
var TASK_TYPE5 = "music_visualizer";
|
|
14650
15001
|
var PRICE_KEY2 = "music_visualizer";
|
|
@@ -14809,7 +15160,7 @@ async function runMusicVisualizer(audio, opts) {
|
|
|
14809
15160
|
});
|
|
14810
15161
|
const { taskId } = submitted;
|
|
14811
15162
|
log.info(`task_id = ${taskId}`);
|
|
14812
|
-
await
|
|
15163
|
+
await mkdir15(outDir, { recursive: true });
|
|
14813
15164
|
await writeFile13(join29(outDir, "task.json"), JSON.stringify({ taskId, taskType: TASK_TYPE5, fileId: submitted.fileId, source: audioAbs, template, createdAt: new Date().toISOString() }, null, 2));
|
|
14814
15165
|
log.step("③ 云端处理中(每 5s 轮询)…");
|
|
14815
15166
|
const result = await pollTask(cfg, TASK_TYPE5, taskId, (status, progress) => {
|