@gitruck/cli 0.2.19 → 0.2.20
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/README.md +3 -0
- package/dist/index.js +129 -48
- package/package.json +1 -1
- package/skills/gtrk-tools/SKILL.md +1 -0
package/README.md
CHANGED
|
@@ -414,6 +414,7 @@ gtrk transcript "D:/素材/采访视频.mp4" --lang zh-CN --out "D:/文字稿/
|
|
|
414
414
|
| `video_motion_cut` | 单条本地视频 | 运镜/高光片段结构 `result-output.json`(结构化数据,非下载文件) | 运行前实时查询 | 已上线 |
|
|
415
415
|
| `video_speaker_detect` | 单条本地视频;可选 `--language`/`--max-faces-per-frame`/`--detect-body`/`--track-sample-fps`(重 GPU) | 可见说话人结构 `result-output.json`(时基以服务端输出为准) | 运行前实时查询 | 已上线 |
|
|
416
416
|
| `video_face_track` | 单条本地视频;可选 `--sample-fps`/`--max-faces`/`--min-face-ratio`/`--enable-body-match`/`--similarity-threshold`;`time_ranges` 走 `--params-json`(重 GPU) | 人物 ID/时间段/轨迹结构 `result-output.json`(时基以服务端输出为准) | 运行前实时查询 | 已上线 |
|
|
417
|
+
| `audio_tts_clone` | **无文件**:`--text`/`--text-file` 二选一(≤2000 字)+ `--speaker` 必填;可选语言/格式/语速/切分法 | 配音音频 wav/mp3(按文本字数折算分钟计费) | 运行前实时查询 | 已上线 |
|
|
417
418
|
| `video_ai_subtitle` | 单条视频或音频;`--language <码>` 必填;可选 `--translate-language`、`--need-render`、`--need-pure`、`--subtitle-type`、`--subtitle-color` | `.ass` 字幕 + 可选烧录/去字幕 `.mp4` + `result-output.json`(摘要 + 字级时间轴) | 运行前实时查询 | 已上线 |
|
|
418
419
|
| `audio_separation` | 单条音频;可选 `--mode fast|turbo` | 人声与伴奏音频(按实际返回可为一项或两项) | 运行前实时查询 | 已上线 |
|
|
419
420
|
| `audio_speaker_split` | 单条音频;可选 `--only-struct` | 各说话人 `.wav` 分轨 + `spoken_list` 时间线(`result-output.json`) | 运行前实时查询 | 已上线 |
|
|
@@ -467,6 +468,8 @@ gtrk transcript "D:/素材/采访视频.mp4" --lang zh-CN --out "D:/文字稿/
|
|
|
467
468
|
- `gtrk tool video_split_screen a.mp4 b.mp4 --clips-json '[{"input":0,"begin_time_ms":0,"end_time_ms":5000},{"input":1,"crop":{"x":0.1,"y":0,"width":0.8,"height":1}}]'` — 精确档:按 0 起索引指定每段毫秒区间与归一化裁剪框;同一文件可多条目出多窗口。
|
|
468
469
|
- `gtrk tool video_speaker_detect ./talk.mp4 --language zh-CN` — 检测画面里谁在何时说话,出结构化 JSON(重 GPU)。
|
|
469
470
|
- `gtrk tool video_face_track ./talk.mp4 --params-json '{"time_ranges":[{"begin_time":0,"end_time":30000}]}'` — 人脸追踪/身份聚类,可限定时间段(**单位毫秒**;重 GPU)。
|
|
471
|
+
- `gtrk tool audio_tts_clone --text "欢迎收听本期节目" --speaker narrator` — 文字转配音音频(音色列表见官网文档)。
|
|
472
|
+
- `gtrk tool audio_tts_clone --text-file 稿子.txt --speaker sweet_female --output-format mp3` — 长文合成,缺省跟随音色调好的语速与切分参数。
|
|
470
473
|
- `gtrk tool mad ./素材 [--bgm 歌.mp3] [--duration 20] [--seed 42] [--refresh] [--json]` — 一键剪 MAD:扫素材文件夹 → 自动选技法 → 单一 `.jsx`(AE 2020+ 跑一遍出母合成成片工程)。`--seed` 可复现;`result.json` 记 seed/数据版本/降级档位/选中技法。
|
|
471
474
|
- 通用:`--out <dir>` 覆盖产物目录、`--param k=v`(可重复)/`--params-json '<对象>'` 透传云端参数、`--reupload` 忽略上传缓存、`--json` 机读、`--ffmpeg-path <dir>` 指定 ffmpeg 目录。
|
|
472
475
|
- cloud 型工具缺 Key → 报错引导 `gtrk init`。产物下载失败(如链接过期 404)→ `result.json` 记 `errors`、`ok=false`、`task.json` 保留可凭 `taskId` 恢复。
|
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
|
|
@@ -7286,12 +7286,12 @@ async function runOralCut(input, opts) {
|
|
|
7286
7286
|
// src/commands/long2short.ts
|
|
7287
7287
|
import { resolve as resolve6, join as join17, dirname as dirname5, basename as basename8, extname as extname5 } from "node:path";
|
|
7288
7288
|
import { mkdir as mkdir6, writeFile as writeFile7, cp as cp2 } from "node:fs/promises";
|
|
7289
|
-
import { existsSync as
|
|
7289
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
7290
7290
|
|
|
7291
7291
|
// src/lib/tool-runner.ts
|
|
7292
7292
|
import { resolve as resolve5, join as join16, dirname as dirname4, basename as basename7, extname as extname4 } from "node:path";
|
|
7293
7293
|
import { mkdir as mkdir5, writeFile as writeFile6, stat as stat5 } from "node:fs/promises";
|
|
7294
|
-
import { createWriteStream, existsSync as
|
|
7294
|
+
import { createWriteStream, existsSync as existsSync15 } from "node:fs";
|
|
7295
7295
|
import { Readable as Readable2 } from "node:stream";
|
|
7296
7296
|
import { pipeline } from "node:stream/promises";
|
|
7297
7297
|
|
|
@@ -7375,7 +7375,8 @@ async function resolveToolPricing(priceKey, pricingContext, fetchFn = fetch) {
|
|
|
7375
7375
|
}
|
|
7376
7376
|
|
|
7377
7377
|
// src/lib/tool-descriptors.ts
|
|
7378
|
-
import {
|
|
7378
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
7379
|
+
import { extname as extname3, resolve as resolvePath } from "node:path";
|
|
7379
7380
|
var MULTI_INPUT_KINDS = new Set(["images", "videos"]);
|
|
7380
7381
|
var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif", ".tif", ".tiff", ".heic", ".heif", ".avif"];
|
|
7381
7382
|
var VIDEO_EXTS = [".mp4", ".mov", ".mkv", ".webm", ".avi", ".m4v", ".flv", ".wmv", ".mpg", ".mpeg", ".ts", ".m2ts"];
|
|
@@ -8191,7 +8192,7 @@ var audioStretch = {
|
|
|
8191
8192
|
taskType: "audio_stretch",
|
|
8192
8193
|
options: [
|
|
8193
8194
|
{ flag: "--semitones <n>", desc: "变调半音(升正降负;未传则不变调)" },
|
|
8194
|
-
{ flag: "--speed <n>", desc: "
|
|
8195
|
+
{ flag: "--speed <n>", desc: "变速/语速倍率(各工具范围与缺省见各自说明)" }
|
|
8195
8196
|
],
|
|
8196
8197
|
buildPayload(fileId, ctx) {
|
|
8197
8198
|
const payload = { file_id: fileId };
|
|
@@ -8549,6 +8550,66 @@ var videoSplitScreen = {
|
|
|
8549
8550
|
return url ? [{ url, filename: `${ctx.baseName}-splitscreen${extFromUrl(url, ".mp4")}` }] : [];
|
|
8550
8551
|
}
|
|
8551
8552
|
};
|
|
8553
|
+
var audioTtsClone = {
|
|
8554
|
+
name: "audio_tts_clone",
|
|
8555
|
+
title: "声音克隆配音",
|
|
8556
|
+
description: "输入一段文字,用指定音色合成配音音频(--text 短文本或 --text-file 文本文件二选一,≤2000 字)。按文本字数折算分钟计费。",
|
|
8557
|
+
kind: "cloud",
|
|
8558
|
+
input: { kind: "none" },
|
|
8559
|
+
priceKey: "audio_tts_clone",
|
|
8560
|
+
outputHint: "配音音频(wav/mp3)",
|
|
8561
|
+
enabled: true,
|
|
8562
|
+
taskType: "audio_tts_clone",
|
|
8563
|
+
options: [
|
|
8564
|
+
{ flag: "--text <text>", desc: "待合成的短文本(与 --text-file 二选一)" },
|
|
8565
|
+
{ flag: "--text-file <file>", desc: "待合成的文本文件(UTF-8;与 --text 二选一)" },
|
|
8566
|
+
{ flag: "--speaker <code>", desc: "音色代号(必填;可用列表见官网文档「可用音色」,传错时报错会列出全部可用项)" },
|
|
8567
|
+
{ flag: "--text-lang <lang>", desc: "文本语言(未传则服务端默认 zh)" },
|
|
8568
|
+
{ flag: "--output-format <fmt>", desc: "输出音频格式 wav/mp3(未传则服务端默认 wav)" },
|
|
8569
|
+
{ flag: "--split-method <m>", desc: "长文切分法 cut0~cut5(未传则跟随该音色调好的参数)" },
|
|
8570
|
+
{ flag: "--batch-size <n>", desc: "分段并发批大小 1~16(未传则服务端默认)" }
|
|
8571
|
+
],
|
|
8572
|
+
buildPayloadNone(ctx) {
|
|
8573
|
+
const o = ctx.opts;
|
|
8574
|
+
const hasText = typeof o.text === "string" && o.text.trim() !== "";
|
|
8575
|
+
const hasFile = typeof o.textFile === "string" && o.textFile.trim() !== "";
|
|
8576
|
+
if (hasText && hasFile)
|
|
8577
|
+
throw new Error("--text 与 --text-file 只能给其一");
|
|
8578
|
+
if (!hasText && !hasFile)
|
|
8579
|
+
throw new Error("需要 --text <短文本> 或 --text-file <文本文件>(UTF-8)之一");
|
|
8580
|
+
const text = hasText ? o.text : readFileSync4(resolvePath(o.textFile), "utf8");
|
|
8581
|
+
if (!text.trim())
|
|
8582
|
+
throw new Error("文本内容为空");
|
|
8583
|
+
if (!(typeof o.speaker === "string" && o.speaker.trim())) {
|
|
8584
|
+
throw new Error("audio_tts_clone 需要 --speaker <音色代号>(可用列表见官网文档;传错时服务端报错会列出可用项)");
|
|
8585
|
+
}
|
|
8586
|
+
const p = { text, speaker: o.speaker };
|
|
8587
|
+
if (typeof o.textLang === "string")
|
|
8588
|
+
p.text_lang = o.textLang;
|
|
8589
|
+
if (typeof o.outputFormat === "string")
|
|
8590
|
+
p.output_format = o.outputFormat;
|
|
8591
|
+
const speed = parseNumFlag(o.speed, "--speed");
|
|
8592
|
+
if (speed != null)
|
|
8593
|
+
p.speed = speed;
|
|
8594
|
+
if (typeof o.splitMethod === "string")
|
|
8595
|
+
p.text_split_method = o.splitMethod;
|
|
8596
|
+
const bs = parseNumFlag(o.batchSize, "--batch-size");
|
|
8597
|
+
if (bs != null) {
|
|
8598
|
+
if (!Number.isInteger(bs))
|
|
8599
|
+
throw new Error(`--batch-size 需要整数,拿到「${String(o.batchSize)}」`);
|
|
8600
|
+
p.batch_size = bs;
|
|
8601
|
+
}
|
|
8602
|
+
return p;
|
|
8603
|
+
},
|
|
8604
|
+
mapOutputs(out, ctx) {
|
|
8605
|
+
const url = pickUrl(out, ["download_url"]);
|
|
8606
|
+
if (!url)
|
|
8607
|
+
return [];
|
|
8608
|
+
const fallback = typeof ctx.opts.outputFormat === "string" ? `.${ctx.opts.outputFormat}` : ".wav";
|
|
8609
|
+
const speaker = typeof ctx.opts.speaker === "string" && ctx.opts.speaker.trim() ? ctx.opts.speaker : "voice";
|
|
8610
|
+
return [{ url, filename: `tts-${speaker}${extFromUrl(url, fallback)}` }];
|
|
8611
|
+
}
|
|
8612
|
+
};
|
|
8552
8613
|
var mad = {
|
|
8553
8614
|
name: "mad",
|
|
8554
8615
|
title: "一键剪 MAD",
|
|
@@ -8599,6 +8660,7 @@ var TOOL_REGISTRY = [
|
|
|
8599
8660
|
imageClassicTemplate,
|
|
8600
8661
|
imageVerticalStitch,
|
|
8601
8662
|
videoSplitScreen,
|
|
8663
|
+
audioTtsClone,
|
|
8602
8664
|
mad
|
|
8603
8665
|
];
|
|
8604
8666
|
function findTool(name, registry = TOOL_REGISTRY) {
|
|
@@ -8635,6 +8697,19 @@ function validateRegistry(registry = TOOL_REGISTRY) {
|
|
|
8635
8697
|
} else if (d.buildPayloadMulti) {
|
|
8636
8698
|
throw new Error(`非多文件工具不得声明 buildPayloadMulti:「${d.name}」`);
|
|
8637
8699
|
}
|
|
8700
|
+
if (d.buildPayloadNone) {
|
|
8701
|
+
if (d.input.kind !== "none")
|
|
8702
|
+
throw new Error(`非 none 工具不得声明 buildPayloadNone:「${d.name}」`);
|
|
8703
|
+
if (d.buildPayload || d.buildPayloadMulti) {
|
|
8704
|
+
throw new Error(`buildPayloadNone 与其他 payload 钩子互斥:「${d.name}」`);
|
|
8705
|
+
}
|
|
8706
|
+
}
|
|
8707
|
+
if (d.kind === "cloud" && d.input.kind === "none") {
|
|
8708
|
+
const paths = (d.preprocess ? 1 : 0) + (d.buildPayloadNone ? 1 : 0);
|
|
8709
|
+
if (paths !== 1) {
|
|
8710
|
+
throw new Error(`none 工具须声明 preprocess 或 buildPayloadNone 恰一:「${d.name}」`);
|
|
8711
|
+
}
|
|
8712
|
+
}
|
|
8638
8713
|
}
|
|
8639
8714
|
}
|
|
8640
8715
|
|
|
@@ -8685,7 +8760,7 @@ function validateToolInput(descriptor, inputAbs) {
|
|
|
8685
8760
|
return;
|
|
8686
8761
|
if (!inputAbs)
|
|
8687
8762
|
throw new Error(`${descriptor.name} 需要输入${spec.kind === "directory" ? "目录" : "文件"}`);
|
|
8688
|
-
if (!
|
|
8763
|
+
if (!existsSync15(inputAbs))
|
|
8689
8764
|
throw new Error(`输入不存在:${inputAbs}`);
|
|
8690
8765
|
if (spec.kind === "directory")
|
|
8691
8766
|
return;
|
|
@@ -8702,7 +8777,7 @@ function validateToolInputs(descriptor, inputAbsList) {
|
|
|
8702
8777
|
throw new Error(`${descriptor.name} 需要至少一个输入文件(可传多个,顺序即拼装顺序)`);
|
|
8703
8778
|
const exts = descriptor.input.exts ?? defaultExtsFor(descriptor.input.kind);
|
|
8704
8779
|
for (const p of inputAbsList) {
|
|
8705
|
-
if (!
|
|
8780
|
+
if (!existsSync15(p))
|
|
8706
8781
|
throw new Error(`输入不存在:${p}`);
|
|
8707
8782
|
if (exts && exts.length) {
|
|
8708
8783
|
const e = extname4(p).toLowerCase();
|
|
@@ -8849,11 +8924,13 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
|
|
|
8849
8924
|
const outDir = resolveOutDir(descriptor, inputAbs, opts.out);
|
|
8850
8925
|
if (isMulti)
|
|
8851
8926
|
descriptor.buildPayloadMulti(inputList.map(() => "__dry_run__"), ctx);
|
|
8927
|
+
const isNoneDirect = descriptor.input.kind === "none" && !!descriptor.buildPayloadNone;
|
|
8852
8928
|
let uploadPath = inputAbs;
|
|
8853
8929
|
if (!isMulti && descriptor.preprocess)
|
|
8854
8930
|
uploadPath = await descriptor.preprocess(ctx);
|
|
8855
|
-
if (!uploadPath)
|
|
8856
|
-
throw new Error(`${descriptor.name} 缺上传物(input=none 的 cloud 型工具需 preprocess
|
|
8931
|
+
if (!uploadPath && !isNoneDirect) {
|
|
8932
|
+
throw new Error(`${descriptor.name} 缺上传物(input=none 的 cloud 型工具需 preprocess 产上传物或声明 buildPayloadNone)`);
|
|
8933
|
+
}
|
|
8857
8934
|
let billingHint;
|
|
8858
8935
|
try {
|
|
8859
8936
|
billingHint = (await (deps.resolvePricing ?? resolveToolPricing)(descriptor.priceKey, descriptor.pricingContext)).billingHint;
|
|
@@ -8865,7 +8942,11 @@ async function runCloudTool(descriptor, inputArg, opts, deps) {
|
|
|
8865
8942
|
let taskId;
|
|
8866
8943
|
let fileId;
|
|
8867
8944
|
let fileIds;
|
|
8868
|
-
if (
|
|
8945
|
+
if (isNoneDirect) {
|
|
8946
|
+
const p = descriptor.buildPayloadNone(ctx);
|
|
8947
|
+
mergeParams(p, extraParams);
|
|
8948
|
+
taskId = await deps.submitTask(deps.cfg, taskType, p);
|
|
8949
|
+
} else if (isMulti) {
|
|
8869
8950
|
const buildMulti = (fids) => {
|
|
8870
8951
|
const p = descriptor.buildPayloadMulti(fids, ctx);
|
|
8871
8952
|
mergeParams(p, extraParams);
|
|
@@ -9006,7 +9087,7 @@ async function runLong2Short(input, opts) {
|
|
|
9006
9087
|
routeLogsToStderr();
|
|
9007
9088
|
const cfg = loadConfig();
|
|
9008
9089
|
const inputAbs = resolve6(input);
|
|
9009
|
-
if (!
|
|
9090
|
+
if (!existsSync16(inputAbs))
|
|
9010
9091
|
throw new Error(`毛片不存在:${inputAbs}`);
|
|
9011
9092
|
const projName = basename8(inputAbs, extname5(inputAbs));
|
|
9012
9093
|
const formats = opts.formats.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -9108,7 +9189,7 @@ async function runLong2Short(input, opts) {
|
|
|
9108
9189
|
if (draftRoot) {
|
|
9109
9190
|
for (const [i, cr] of clipResults.entries()) {
|
|
9110
9191
|
const src = join17(cr.dir, "jianying");
|
|
9111
|
-
if (!
|
|
9192
|
+
if (!existsSync16(src))
|
|
9112
9193
|
continue;
|
|
9113
9194
|
const dest = `${draftTarget}_clip${i}`;
|
|
9114
9195
|
try {
|
|
@@ -9245,13 +9326,13 @@ function registerUpgrade(program2) {
|
|
|
9245
9326
|
|
|
9246
9327
|
// src/commands/render.ts
|
|
9247
9328
|
import { resolve as resolve8, dirname as dirname6, join as join19, basename as basename9, extname as extname6 } from "node:path";
|
|
9248
|
-
import { existsSync as
|
|
9329
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
9249
9330
|
function registerRender(program2) {
|
|
9250
9331
|
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
9332
|
if (opts.json)
|
|
9252
9333
|
routeLogsToStderr();
|
|
9253
9334
|
const gtrkAbs = resolve8(gtrk);
|
|
9254
|
-
if (!
|
|
9335
|
+
if (!existsSync17(gtrkAbs))
|
|
9255
9336
|
throw new Error(`gtrk 工程不存在:${gtrkAbs}`);
|
|
9256
9337
|
const outMp4 = resolve8(opts.out ?? join19(dirname6(gtrkAbs), `${basename9(gtrkAbs, extname6(gtrkAbs))}.mp4`));
|
|
9257
9338
|
log.step(`▶ 本地渲染:${basename9(gtrkAbs)} → ${basename9(outMp4)}`);
|
|
@@ -9278,16 +9359,16 @@ function registerRender(program2) {
|
|
|
9278
9359
|
|
|
9279
9360
|
// src/commands/split.ts
|
|
9280
9361
|
import { resolve as resolve9, join as join21, dirname as dirname8, basename as basename11 } from "node:path";
|
|
9281
|
-
import { existsSync as
|
|
9362
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
9282
9363
|
import { readFile as readFile5, writeFile as writeFile8, mkdir as mkdir7 } from "node:fs/promises";
|
|
9283
9364
|
import { createHash } from "node:crypto";
|
|
9284
9365
|
|
|
9285
9366
|
// src/lib/gtrk-writeback.ts
|
|
9286
|
-
import { readFileSync as
|
|
9367
|
+
import { readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "node:fs";
|
|
9287
9368
|
import { dirname as dirname7, join as join20, basename as basename10 } from "node:path";
|
|
9288
9369
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
9289
9370
|
function readGtrk(path) {
|
|
9290
|
-
const raw =
|
|
9371
|
+
const raw = readFileSync5(path, "utf8");
|
|
9291
9372
|
let gtrk;
|
|
9292
9373
|
try {
|
|
9293
9374
|
gtrk = JSON.parse(raw);
|
|
@@ -9347,7 +9428,7 @@ function registerSplit(program2) {
|
|
|
9347
9428
|
});
|
|
9348
9429
|
}
|
|
9349
9430
|
function firstExisting(cands) {
|
|
9350
|
-
return cands.find((p) =>
|
|
9431
|
+
return cands.find((p) => existsSync18(p));
|
|
9351
9432
|
}
|
|
9352
9433
|
function resolvePaths(opts) {
|
|
9353
9434
|
const project = opts.project ? resolve9(opts.project) : undefined;
|
|
@@ -9359,7 +9440,7 @@ function resolvePaths(opts) {
|
|
|
9359
9440
|
} else {
|
|
9360
9441
|
throw new Error("需 --project <目录> 或显式 --gtrk <path>");
|
|
9361
9442
|
}
|
|
9362
|
-
if (!
|
|
9443
|
+
if (!existsSync18(gtrkPath))
|
|
9363
9444
|
throw new Error(`找不到工程文件:${gtrkPath}`);
|
|
9364
9445
|
let transcriptPath;
|
|
9365
9446
|
if (opts.transcript)
|
|
@@ -9393,7 +9474,7 @@ async function runSplit(splitdoc, opts) {
|
|
|
9393
9474
|
return splitdoc ? runLand(resolve9(splitdoc), baseDir, gtrkPath, transcriptPath, opts) : runView(baseDir, gtrkPath, transcriptPath, opts);
|
|
9394
9475
|
}
|
|
9395
9476
|
async function runView(baseDir, gtrkPath, transcriptPath, opts) {
|
|
9396
|
-
if (!transcriptPath || !
|
|
9477
|
+
if (!transcriptPath || !existsSync18(transcriptPath))
|
|
9397
9478
|
throw new Error(TRANSCRIPT_MISSING);
|
|
9398
9479
|
log.step("▶ 导出投影视图(transcript × 当刻 .gtrk)…");
|
|
9399
9480
|
const transcript = await loadTranscript(transcriptPath);
|
|
@@ -9419,9 +9500,9 @@ async function runView(baseDir, gtrkPath, transcriptPath, opts) {
|
|
|
9419
9500
|
return result;
|
|
9420
9501
|
}
|
|
9421
9502
|
async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
|
|
9422
|
-
if (!
|
|
9503
|
+
if (!existsSync18(splitdocPath))
|
|
9423
9504
|
throw new Error(`找不到拆分稿:${splitdocPath}`);
|
|
9424
|
-
if (!transcriptPath || !
|
|
9505
|
+
if (!transcriptPath || !existsSync18(transcriptPath))
|
|
9425
9506
|
throw new Error(TRANSCRIPT_MISSING);
|
|
9426
9507
|
log.step("▶ 校验拆分稿并落地…");
|
|
9427
9508
|
const doc = JSON.parse(await readFile5(splitdocPath, "utf8"));
|
|
@@ -9497,7 +9578,7 @@ async function runLand(splitdocPath, baseDir, gtrkPath, transcriptPath, opts) {
|
|
|
9497
9578
|
|
|
9498
9579
|
// src/commands/matrix.ts
|
|
9499
9580
|
import { resolve as resolve11, join as join22, dirname as dirname9, basename as basename12 } from "node:path";
|
|
9500
|
-
import { existsSync as
|
|
9581
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
9501
9582
|
import { readFile as readFile6, writeFile as writeFile9, mkdir as mkdir8, rename } from "node:fs/promises";
|
|
9502
9583
|
|
|
9503
9584
|
// src/lib/solid-png.ts
|
|
@@ -10234,7 +10315,7 @@ function layBrollTracks(opts) {
|
|
|
10234
10315
|
}
|
|
10235
10316
|
|
|
10236
10317
|
// src/lib/material-integrity.ts
|
|
10237
|
-
import { existsSync as
|
|
10318
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
10238
10319
|
import { resolve as resolve10 } from "node:path";
|
|
10239
10320
|
var INTEGRITY_LIST_CAP = 10;
|
|
10240
10321
|
var r35 = (n) => Math.round(n * 1000) / 1000;
|
|
@@ -10295,7 +10376,7 @@ function collectMaterialRefs(gtrk) {
|
|
|
10295
10376
|
return out;
|
|
10296
10377
|
}
|
|
10297
10378
|
function checkMaterialIntegrity(opts) {
|
|
10298
|
-
const exists = opts.exists ?? ((p) =>
|
|
10379
|
+
const exists = opts.exists ?? ((p) => existsSync19(p));
|
|
10299
10380
|
const materials = Array.isArray(opts.gtrk.materials) ? opts.gtrk.materials : [];
|
|
10300
10381
|
const refs = collectMaterialRefs(opts.gtrk);
|
|
10301
10382
|
const counts = { relative: 0, absolute: 0, remote: 0, noPath: 0 };
|
|
@@ -10645,7 +10726,7 @@ async function runPlanMode(cfg, tier, broll, overrides, columnId, opts) {
|
|
|
10645
10726
|
} else {
|
|
10646
10727
|
throw new Error('需 --project <目录> 或显式 --dispatch <path>(ad-hoc 检索用:gtrk matrix search "<query>")');
|
|
10647
10728
|
}
|
|
10648
|
-
if (!
|
|
10729
|
+
if (!existsSync20(dispatchPath))
|
|
10649
10730
|
throw new Error(`找不到派单清单:${dispatchPath}(先跑 gtrk split <拆分稿> 落地派单)`);
|
|
10650
10731
|
const dispatch = JSON.parse(await readFile6(dispatchPath, "utf8"));
|
|
10651
10732
|
const rawQueue = Array.isArray(dispatch.film_broll) ? dispatch.film_broll : [];
|
|
@@ -10761,7 +10842,7 @@ function parseScoreFloor(raw) {
|
|
|
10761
10842
|
}
|
|
10762
10843
|
function locateGtrk(baseDir) {
|
|
10763
10844
|
const cands = [join22(baseDir, "gtrk", "project.gtrk"), join22(baseDir, "project.gtrk")];
|
|
10764
|
-
return cands.find((p) =>
|
|
10845
|
+
return cands.find((p) => existsSync20(p));
|
|
10765
10846
|
}
|
|
10766
10847
|
async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRelay, reproj) {
|
|
10767
10848
|
const gtrkPath = locateGtrk(baseDir);
|
|
@@ -10799,7 +10880,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
|
|
|
10799
10880
|
continue;
|
|
10800
10881
|
const rel = `${BROLL_PREVIEW_DIR}/${clipId}.mp4`;
|
|
10801
10882
|
const abs = join22(gtrkDir, ...rel.split("/"));
|
|
10802
|
-
if (
|
|
10883
|
+
if (existsSync20(abs)) {
|
|
10803
10884
|
const prev = prevSource.get(clipId);
|
|
10804
10885
|
if (prev !== "raw") {
|
|
10805
10886
|
downloads.set(clipId, { rel, source: prev ?? "preview" });
|
|
@@ -10863,7 +10944,7 @@ async function layIntoProject(baseDir, plan, layN, scoreFloor, blackBed, forceRe
|
|
|
10863
10944
|
const rel = solidRelPath(spec);
|
|
10864
10945
|
const abs = join22(gtrkDir, ...rel.split("/"));
|
|
10865
10946
|
try {
|
|
10866
|
-
if (!
|
|
10947
|
+
if (!existsSync20(abs)) {
|
|
10867
10948
|
await mkdir8(dirname9(abs), { recursive: true });
|
|
10868
10949
|
const tmp = `${abs}.tmp-${process.pid}`;
|
|
10869
10950
|
await writeFile9(tmp, encodeSolidPng(spec));
|
|
@@ -10980,7 +11061,7 @@ function slugify3(name) {
|
|
|
10980
11061
|
|
|
10981
11062
|
// src/commands/mg.ts
|
|
10982
11063
|
import { resolve as resolve12, join as join23, dirname as dirname10, basename as basename13 } from "node:path";
|
|
10983
|
-
import { existsSync as
|
|
11064
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
10984
11065
|
import { readFile as readFile7, mkdir as mkdir9, copyFile } from "node:fs/promises";
|
|
10985
11066
|
|
|
10986
11067
|
// src/lib/mg-lint.ts
|
|
@@ -12506,18 +12587,18 @@ function resolveDispatch(opts) {
|
|
|
12506
12587
|
throw new Error("需 --project <目录> 或显式 --dispatch <path>");
|
|
12507
12588
|
}
|
|
12508
12589
|
function locateGtrk2(baseDir) {
|
|
12509
|
-
return [join23(baseDir, "gtrk", "project.gtrk"), join23(baseDir, "project.gtrk")].find((p) =>
|
|
12590
|
+
return [join23(baseDir, "gtrk", "project.gtrk"), join23(baseDir, "project.gtrk")].find((p) => existsSync21(p));
|
|
12510
12591
|
}
|
|
12511
12592
|
function locateSrcHtml(baseDir, compositionId) {
|
|
12512
12593
|
for (const d of MG_SRC_DIRS) {
|
|
12513
12594
|
const p = join23(baseDir, d, `${compositionId}.html`);
|
|
12514
|
-
if (
|
|
12595
|
+
if (existsSync21(p))
|
|
12515
12596
|
return p;
|
|
12516
12597
|
}
|
|
12517
12598
|
return;
|
|
12518
12599
|
}
|
|
12519
12600
|
async function readMgQueue(dispatchPath) {
|
|
12520
|
-
if (!
|
|
12601
|
+
if (!existsSync21(dispatchPath))
|
|
12521
12602
|
throw new Error(`找不到派单清单:${dispatchPath}(先跑 gtrk split 落地派单)`);
|
|
12522
12603
|
const dispatch = JSON.parse(await readFile7(dispatchPath, "utf8"));
|
|
12523
12604
|
const queue = dispatch.mg ?? dispatch.rrv_mg;
|
|
@@ -12751,7 +12832,7 @@ async function runLint(args, opts) {
|
|
|
12751
12832
|
let dispatchIds;
|
|
12752
12833
|
let slotDuration;
|
|
12753
12834
|
let compositionId;
|
|
12754
|
-
if (opts.dispatch &&
|
|
12835
|
+
if (opts.dispatch && existsSync21(resolve12(opts.dispatch))) {
|
|
12755
12836
|
const queue = await readMgQueue(resolve12(opts.dispatch));
|
|
12756
12837
|
dispatchIds = queue.map((q) => q.composition_id);
|
|
12757
12838
|
const byName = queue.find((q) => q.composition_id === nameId);
|
|
@@ -12818,7 +12899,7 @@ function done(opts, result) {
|
|
|
12818
12899
|
|
|
12819
12900
|
// src/lib/mad/mad.ts
|
|
12820
12901
|
import { mkdir as mkdir12, writeFile as writeFile11 } from "node:fs/promises";
|
|
12821
|
-
import { existsSync as
|
|
12902
|
+
import { existsSync as existsSync24, statSync as statSync3 } from "node:fs";
|
|
12822
12903
|
import { resolve as resolve13, join as join27 } from "node:path";
|
|
12823
12904
|
|
|
12824
12905
|
// src/lib/convert/types.ts
|
|
@@ -13790,7 +13871,7 @@ function beatQuantized(natLens, analysis) {
|
|
|
13790
13871
|
// src/lib/mad/data.ts
|
|
13791
13872
|
import { createHash as createHash2 } from "node:crypto";
|
|
13792
13873
|
import { mkdir as mkdir10, readFile as readFile8, rename as rename2, rm, writeFile as writeFile10, readdir } from "node:fs/promises";
|
|
13793
|
-
import { existsSync as
|
|
13874
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
13794
13875
|
import { join as join25 } from "node:path";
|
|
13795
13876
|
function madCacheDir() {
|
|
13796
13877
|
return homeFile("mad-cache");
|
|
@@ -13820,7 +13901,7 @@ async function atomicWrite(dest, data) {
|
|
|
13820
13901
|
await rename2(tmp, dest);
|
|
13821
13902
|
}
|
|
13822
13903
|
async function verifyFile(path, sha256) {
|
|
13823
|
-
if (!
|
|
13904
|
+
if (!existsSync22(path))
|
|
13824
13905
|
return false;
|
|
13825
13906
|
try {
|
|
13826
13907
|
const buf = await readFile8(path);
|
|
@@ -13872,7 +13953,7 @@ async function ensureMadData(opts, deps) {
|
|
|
13872
13953
|
warn(`manifest 拉取失败(${e instanceof Error ? e.message : String(e)}),回退本地缓存`);
|
|
13873
13954
|
}
|
|
13874
13955
|
if (!manifest) {
|
|
13875
|
-
if (!
|
|
13956
|
+
if (!existsSync22(snapshotPath)) {
|
|
13876
13957
|
throw new Error("首次使用需联网下载技法数据。请连网后重跑 `gtrk tool mad`(数据会缓存到本地,之后可离线出片)。");
|
|
13877
13958
|
}
|
|
13878
13959
|
try {
|
|
@@ -13894,7 +13975,7 @@ async function ensureMadData(opts, deps) {
|
|
|
13894
13975
|
const needDownload = !!opts.refresh || !cacheValid;
|
|
13895
13976
|
if (needDownload) {
|
|
13896
13977
|
if (!online) {
|
|
13897
|
-
if (
|
|
13978
|
+
if (existsSync22(poolPath)) {
|
|
13898
13979
|
throw new Error("本地技法数据缓存损坏且当前离线。请连网重跑,或加 --refresh 强制重新下载。");
|
|
13899
13980
|
}
|
|
13900
13981
|
throw new Error("首次使用需联网下载技法数据。请连网后重跑 `gtrk tool mad`(数据会缓存到本地,之后可离线出片)。");
|
|
@@ -13930,7 +14011,7 @@ async function ensureMadData(opts, deps) {
|
|
|
13930
14011
|
// src/lib/mad/pool.ts
|
|
13931
14012
|
import { gunzipSync } from "node:zlib";
|
|
13932
14013
|
import { mkdir as mkdir11, readFile as readFile9 } from "node:fs/promises";
|
|
13933
|
-
import { existsSync as
|
|
14014
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
13934
14015
|
import { join as join26 } from "node:path";
|
|
13935
14016
|
function shardPath(verDir, shard) {
|
|
13936
14017
|
return join26(verDir, "ir", `${shard}.json.gz`);
|
|
@@ -13950,7 +14031,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
|
|
|
13950
14031
|
if (cached)
|
|
13951
14032
|
return cached;
|
|
13952
14033
|
const path = shardPath(verDir, shard);
|
|
13953
|
-
if (
|
|
14034
|
+
if (existsSync23(path)) {
|
|
13954
14035
|
try {
|
|
13955
14036
|
const s2 = decodeShard(await readFile9(path));
|
|
13956
14037
|
memo.set(shard, s2);
|
|
@@ -13982,7 +14063,7 @@ function makeIrLoader(assetsBase, verDir, online, deps) {
|
|
|
13982
14063
|
return ir;
|
|
13983
14064
|
},
|
|
13984
14065
|
shardCached(shard) {
|
|
13985
|
-
return memo.has(shard) ||
|
|
14066
|
+
return memo.has(shard) || existsSync23(shardPath(verDir, shard));
|
|
13986
14067
|
}
|
|
13987
14068
|
};
|
|
13988
14069
|
}
|
|
@@ -14066,7 +14147,7 @@ async function runMad(inputArg, opts, deps = {}) {
|
|
|
14066
14147
|
if (!inputArg)
|
|
14067
14148
|
throw new Error("用法:gtrk tool mad <素材文件夹> [--bgm 歌.mp3]");
|
|
14068
14149
|
const dirAbs = resolve13(inputArg);
|
|
14069
|
-
if (!
|
|
14150
|
+
if (!existsSync24(dirAbs) || !statSync3(dirAbs).isDirectory()) {
|
|
14070
14151
|
throw new Error(`素材文件夹不存在或不是目录:${dirAbs}`);
|
|
14071
14152
|
}
|
|
14072
14153
|
const { videos, orientation, skipped } = scanFolder(dirAbs, {
|
|
@@ -14327,7 +14408,7 @@ async function runMadInTool(inputArg, opts) {
|
|
|
14327
14408
|
}
|
|
14328
14409
|
|
|
14329
14410
|
// src/commands/transcript.ts
|
|
14330
|
-
import { existsSync as
|
|
14411
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
14331
14412
|
import { mkdir as mkdir13, rename as rename3, rm as rm2, stat as stat6, writeFile as writeFile12 } from "node:fs/promises";
|
|
14332
14413
|
import { basename as basename14, dirname as dirname11, extname as extname8, join as join28, resolve as resolve14 } from "node:path";
|
|
14333
14414
|
|
|
@@ -14466,7 +14547,7 @@ async function validateTranscriptInput(input) {
|
|
|
14466
14547
|
throw new Error("视频转文字稿仅支持本地视频文件,不支持 URL、平台视频地址或远端下载");
|
|
14467
14548
|
}
|
|
14468
14549
|
const inputAbs = resolve14(input);
|
|
14469
|
-
if (!
|
|
14550
|
+
if (!existsSync25(inputAbs))
|
|
14470
14551
|
throw new Error(`本地视频不存在:${inputAbs}`);
|
|
14471
14552
|
const info = await stat6(inputAbs);
|
|
14472
14553
|
if (!info.isFile())
|
|
@@ -14564,7 +14645,7 @@ function registerTranscript(program2) {
|
|
|
14564
14645
|
// src/commands/music-visualizer.ts
|
|
14565
14646
|
import { resolve as resolve15, join as join29, dirname as dirname12, basename as basename15, extname as extname9 } from "node:path";
|
|
14566
14647
|
import { mkdir as mkdir14, writeFile as writeFile13 } from "node:fs/promises";
|
|
14567
|
-
import { existsSync as
|
|
14648
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
14568
14649
|
var TASK_TYPE5 = "music_visualizer";
|
|
14569
14650
|
var PRICE_KEY2 = "music_visualizer";
|
|
14570
14651
|
var HEX_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
@@ -14612,7 +14693,7 @@ function timestamp5() {
|
|
|
14612
14693
|
return `${p(d.getFullYear() % 100)}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
14613
14694
|
}
|
|
14614
14695
|
function assertExt(pathAbs, exts, label) {
|
|
14615
|
-
if (!
|
|
14696
|
+
if (!existsSync26(pathAbs))
|
|
14616
14697
|
throw new Error(`${label}不存在:${pathAbs}`);
|
|
14617
14698
|
const e = extname9(pathAbs).toLowerCase();
|
|
14618
14699
|
if (exts.length && !exts.includes(e)) {
|
|
@@ -14780,7 +14861,7 @@ try {
|
|
|
14780
14861
|
process.loadEnvFile?.();
|
|
14781
14862
|
} catch {}
|
|
14782
14863
|
migrateLegacyHome();
|
|
14783
|
-
var { version } = JSON.parse(
|
|
14864
|
+
var { version } = JSON.parse(readFileSync6(join30(packageRoot(), "package.json"), "utf8"));
|
|
14784
14865
|
var program2 = new Command;
|
|
14785
14866
|
program2.name("gtrk").description("同合云成片流水线 CLI —— agent 驱动云端任务、产物拉回本地、三方工程文件(客户端/剪映/PR)互通").version(version);
|
|
14786
14867
|
registerInstall(program2);
|
package/package.json
CHANGED
|
@@ -46,6 +46,7 @@ description: gtrk 单点工具与媒体转换能力的调用向导,覆盖 `gtr
|
|
|
46
46
|
| `image_to_live` | 「图片变 LivePhoto / 静图微动 / 让照片动起来」 | 单张图片 | 微动视频 `.mp4`(产物是视频) | 运行前实时查询 | 未开放(上游生成能力暂未开放,恢复后重新上架) |
|
|
47
47
|
| `image_classic_template` | 「做封面 / 智能拼图 / 标题配图出封面图」 | **多张图片**(可传多个路径,文件顺序即拼装顺序);**`--main-title <标题>` 必填**;可选 `--sub-title`、`--template-mode`、`--aspect`、`--quality`、`--output-pic-count`(服务端钳制 ≤20)、`--output-text-count`(≤20)、`--title-layout` | 封面/拼图成品(text/pic/render 三组、各组可空可多张) | 运行前实时查询 | 已上线 |
|
|
48
48
|
| `image_vertical_stitch` | 「拼长图 / 多图竖拼 / 截图接成一张长图」 | **多张图片**(可传多个路径,顺序=自上而下拼接顺序) | 一张垂直拼接长图 | 运行前实时查询 | 已上线 |
|
|
49
|
+
| `audio_tts_clone` | 「配音 / 文字转语音 / TTS / 用 xx 音色念这段 / 把这段稿子合成音频」 | **无文件**:`--text <短文本>` 或 `--text-file <txt>` 二选一(≤2000 字,服务端为真相)+ **`--speaker <音色代号>` 必填**(可用音色见官网文档,传错时报错会列出全部可用项);可选 `--text-lang`/`--output-format wav\|mp3`/`--speed`/`--split-method`/`--batch-size`(speed/切分法缺省跟随该音色调好的参数)。**计费按文本字数折算分钟(约 200 字/分钟)** | 配音音频(wav/mp3) | 运行前实时查询 | 已上线 |
|
|
49
50
|
| `mad` | 「一键剪 MAD / 素材文件夹自动出卡点成片 / 自动选技法 / 给这堆素材出个 AE 工程」 | 一个素材文件夹(3~10 条视频)+ 可选 `--bgm 歌.mp3` | AE 母合成工程 `.jsx`(仅支持 AE) | 仅 `--bgm` 触发实时查价 | 已上线 |
|
|
50
51
|
|
|
51
52
|
> 清单随 CLI 版本增补。**动手前先跑 `gtrk tool list --json` 拿最新清单**,别凭记忆——新工具、状态变化都在那里。
|