@gitruck/cli 1.3.4 → 1.3.5
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 +25 -0
- package/CHANGELOG.md +22 -0
- package/README.en.md +50 -50
- package/README.md +50 -50
- package/assets/composite-kit/kit.css +57 -0
- package/assets/composite-kit/kit.js +246 -0
- package/assets/composite-kit/stage.html +265 -0
- package/assets/readme/wordmark.svg +6 -6
- package/contracts/README.md +16 -16
- package/contracts/gsap-emit-v1.md +163 -163
- package/contracts/handoff-contracts.json +10 -10
- package/dist/index.js +440 -286
- package/docs/development.en.md +47 -47
- package/docs/development.md +47 -47
- package/docs/reference.en.md +59 -15
- package/docs/reference.md +59 -15
- package/docs/workflow.en.md +72 -72
- package/docs/workflow.md +72 -72
- package/package.json +2 -2
- package/skills/gtrk-ai-drama/SKILL.md +10 -10
- package/skills/gtrk-ai-drama/references/sparse-key-scenes.md +38 -38
- package/skills/gtrk-ai-drama/scripts/validate-storyboard.mjs +119 -119
- package/skills/gtrk-composite/SKILL.md +194 -0
- package/skills/gtrk-cover/SKILL.md +3 -3
- package/skills/gtrk-cover/references/layout-recipes.md +337 -337
- package/skills/gtrk-cover/references/prompt-templates.md +119 -119
- package/skills/gtrk-food-recap/SKILL.md +9 -9
- package/skills/gtrk-live-slicing/SKILL.md +2 -1
- package/skills/gtrk-long2short/SKILL.md +2 -1
- package/skills/gtrk-matrix/SKILL.md +20 -10
- package/skills/gtrk-mg/SKILL.md +391 -391
- package/skills/gtrk-music-visualizer/SKILL.md +3 -3
- package/skills/gtrk-narration/SKILL.md +23 -23
- package/skills/gtrk-oralcut/SKILL.md +5 -4
- package/skills/gtrk-splitter/SKILL.md +298 -298
- package/skills/gtrk-splitter/references/example-visual-split.json +645 -645
- package/skills/gtrk-splitter/references/example-visual-split.md +439 -439
- package/skills/gtrk-splitter/references/field-schema.md +171 -171
- package/skills/gtrk-style-maker/SKILL.md +1 -1
- package/skills/gtrk-style-maker/references/ammo.md +16 -16
- package/skills/gtrk-style-maker/references/contracts-ref.md +9 -9
- package/skills/gtrk-style-maker/references/seeds/README.md +4 -4
- package/skills/gtrk-style-maker/references/seeds/seed-ecom-home-goods.md +37 -37
- package/skills/gtrk-style-maker/references/seeds/seed-psych-humanities.md +35 -35
- package/skills/gtrk-talking-head/SKILL.md +15 -7
- package/skills/gtrk-tools/SKILL.md +3 -2
- package/skills/gtrk-transcript/SKILL.md +4 -4
- package/skills/gtrk-transcript/agents/openai.yaml +4 -4
- package/skills/gtrk-travel-recap/SKILL.md +52 -52
- package/skills/gtrk-vlog-docu/SKILL.md +6 -6
- package/skills/gtrk-voiceover/SKILL.md +2 -2
package/AGENT.md
CHANGED
|
@@ -398,6 +398,31 @@ gtrk pip lay --project <口播工程目录> --companion <屏录.mp4> [--shape el
|
|
|
398
398
|
|
|
399
399
|
---
|
|
400
400
|
|
|
401
|
+
## 2.6 合成层:`gtrk composite`(粗剪之上的整段包装 · 纯本地零计费)
|
|
402
|
+
|
|
403
|
+
字幕之后、出片之前:把选定时间窗做成**合成颗粒**——一段时间内的整个画面由一颗代码颗粒接管(逐句换画面、对照并排、并列卡片、字幕排版),讲者与 B-roll 作为槽位由客户端画进去。由 `/gtrk-composite` 驱动,**编排规则与两个检查点在 skill 里**,本节只列命令面。
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
gtrk composite plan --project <工程> [--mode full|auto|ranges] [--range 11,32 | --range u0004-u0009]... [--density 0.25]
|
|
407
|
+
gtrk composite review --project <工程> [--approve W01,W03] [--reject W02] [--adjust W04=80,90.9] [--add 140,150] [--approve-all]
|
|
408
|
+
gtrk composite scaffold --project <工程> [--only W03] [--force]
|
|
409
|
+
gtrk composite shots --project <工程> --material <素材 id 或文件> [--around <秒> --span <秒>]
|
|
410
|
+
gtrk composite build --project <工程> [--only cp-w03] [--theme <css>] [--materials <json>]
|
|
411
|
+
gtrk composite lint --project <工程> [--stills 26,86.6] [--no-browser]
|
|
412
|
+
gtrk composite lay --project <工程> [--only cp-w03] [--dry-run]
|
|
413
|
+
gtrk composite bake --project <工程> [--only cp-w03] [--fps 30] [--workers 4] [--install-browser] [--progress jsonl]
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
- **候选写 `struct_meta.composite`**(只动这一个键、原子写回),客户端大纲 CP 行画提案块——与 `gtrk split` 写 `struct_meta.split`、beat 色带呈现同一套做法。要用户看提案块,先请他在客户端**重新载入工程**。
|
|
417
|
+
- **解析规则不自造**:`build` 按 infra `composite-particle-profile` 解析(帧格窗口、`lane_clip_id`、锚点局部帧、字幕输入、`inputs_hash`),对拍 infra 金样逐字节一致;窗口不在帧格上会被吸附并报挪动量。
|
|
418
|
+
- **机检**:`lint` 退出码 0 通过 / 1 有问题 / 3 排版未机检(本机找不到 Chrome / Edge,可设 `GTRK_CHROME`)。**3 不是通过**:照实告诉用户,用静帧人眼查。`lint` 含帧纯度检查(`impure-frame`:同一帧号随渲染顺序画出不同画面),不过的颗粒烘焙段首会画错。
|
|
419
|
+
- **铺轨**:按 `producer=gtrk:composite@1` 认领自产轨(不按轨号);过期颗粒(上游改过)拒铺;用户在客户端改过的同 id 颗粒不覆盖。
|
|
420
|
+
- **出片**:`gtrk composite bake` 在本机把合成烤成不透明视频(缓存命中即复用,纯本地零计费);`gtrk render` / `gtrk project export` 会自动先烤,成功的段以烘焙视频出画,整段落在合成窗口内的 MG / OV 不再预渲、不计费。
|
|
421
|
+
- 浏览器优先用钉定版本 chrome-headless-shell,没有时退回本机 Chrome / Edge(更慢)。**MUST NOT 自作主张下载**:征得用户同意后 `gtrk deps install --browser`(约 110 MB)或 `bake --install-browser`。
|
|
422
|
+
- 某颗烤失败(退出码 1,回执给原因)时,出片该段按粗剪出画——照实告诉用户。
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
401
426
|
## 3. 产物结构 + 三端打开
|
|
402
427
|
|
|
403
428
|
```
|
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,22 @@
|
|
|
11
11
|
|
|
12
12
|
## 未发布
|
|
13
13
|
|
|
14
|
+
## 1.3.5(2026-10-17)
|
|
15
|
+
|
|
16
|
+
- 新增合成层命令族 `gtrk composite`:`plan` / `review` / `scaffold` / `shots` 把候选窗口写进 `struct_meta.composite`(客户端大纲 CP 行),支持整片铺满 / AI 判定 / 指定区间三种入口;`build` 把场景构建成自包含合成颗粒(随包工具箱 `composite-kit@1`);`lint` 做静态校验加本机 Chrome / Edge 逐帧排版机检(穿模 / 线条穿画面 / 越界);`lay` 按自产身份幂等铺进 beat 轨最上层。新增 `/gtrk-composite` skill,口播图纸挂可选合成步。
|
|
17
|
+
- `gtrk composite bake`:用钉定版本的 chrome-headless-shell(`gtrk deps install --browser`)逐帧确定性出帧,按段并发、带缓存;`render` / `project export` 接管已烘焙的合成颗粒,`lint` 增帧纯度检查;支持 `--progress jsonl`,关闭 stdin 即取消(退出码 130),取消或出错时连同浏览器遗留子进程一起收掉。
|
|
18
|
+
- 片段背景填充:`gtrk matrix` 的候选片段自带纯黑 `background_fill`,不再另铺黑底垫轨(`--black-bed` 回到旧形态);`ai-drama lay` 的 AI 片段同样带黑底;`gtrk project background` 缺省改为逐片段写底板;本地 `gtrk render` 承接片段底板;`project export` 承接云端物化,先写出纯色图再落工程(剪映 / Pr 的上层轨模糊为真模糊,FCPXML / OTIO 降级为纯黑,铺满画布的片段不垫底)。**需配合客户端 1.2.12 起**,旧客户端不认片段底板。
|
|
19
|
+
- `gtrk render` / `project export` 遇到工程里有客户端专属视觉状态(文字 / 贴纸 / 图形 / 特效、画布背景)时用人话提示一次,`--json` 恒带 `clientOnly`;`project export` 的 `clientVisualElementCount` 改按元素计数(原误按车道数)。
|
|
20
|
+
- 外网 B-roll 缺省加入 Pexels 第五平台:解析可选署名字段(`uploader_url` / `license`),平台 `disabled` 不算降级;旧服务端不认 pexels 时提示加 `--platforms` 继续;未指定平台时先回看四平台旧续跑记录,不重复扣费;`source_urls` 接受 Pexels 视频页。**服务端开关打开前 Pexels 只报 `disabled`、不出候选。**
|
|
21
|
+
- `matrix material --scope clip|image` 缺省附带 Pexels 补充结果:单列在 `--json` 顶层 `pexels_results`(另有 `pexels_status`、`counts.pexels`),不进 `results`、不算候选数,人读输出另起一段并在段首署名 Pexels;`--no-stock` 关闭,`audio` 不带。补充素材直接用 `download_url` 下载,`matrix fetch` 见 `pexels:` id 会整批拒绝。**需服务端 `add-pexels-stock-material-search` 上线并打开开关后才会出现补充结果**,老服务端下只提示一次、不报错。
|
|
22
|
+
- `tool video_highlight` 分析代理升至 448p/4fps(libx264 CRF 28;本地代理缓存按编码参数区分,参数变更后自动重新生成),缺省附单声道低码率原声,新增 `--no-audio`;上传前本地自检,旧版 224 代理不再复用。**需云端先升级,旧云端会以「短边必须224」拒绝新代理。**
|
|
23
|
+
- MAD 缓存目录不可写时给出权限提示与诊断类别。
|
|
24
|
+
- 交工文案补 Premiere 自动重链提醒:正本在 `gtrk-oralcut`「回给用户」的 PR / FCP 条,`long2short` / `live-slicing` / `tools` 引用同一条。
|
|
25
|
+
|
|
26
|
+
## 1.3.4(2026-10-14)
|
|
27
|
+
|
|
28
|
+
> 版本段补录于 2026-10-17:发版时这几条留在「未发布」里。归属按 npm 记录的 `gitHead`(1.3.4 `144ed89`)与写入这些条目的提交时间判定,未逐条核对包体。
|
|
29
|
+
|
|
14
30
|
- `long2short --camera-move`:云端按与精剪同一套调度把推 / 拉镜头写成主轨片段的 `clip_keyframes.scale_x / scale_y`,客户端与剪映可见可改;`clips.md` 标「运镜 N 处」,未能加运镜的切片单列;`/gtrk-live-slicing` 开工问询变五问(⑤ 运镜,缺省不加)。需配合服务端 `add-long2short-for-cli-camera-move` 与客户端承接 `clip_keyframes` 的版本。
|
|
15
31
|
- `gtrk patch` 的 trim / split / seal / snap 同步维护 `clip_keyframes`(入点平移、越界截断补插值端点、切分两段各自合法),写前校验拒绝任何非法关键帧并点名 clip 与通道;`audio tighten` 遇带音量关键帧的音频片段拒绝;本地 `gtrk render` 读到关键帧时提示按静态画面出片。
|
|
16
32
|
- `gtrk project background --blur [--intensity n] | --color #RRGGBB | --clear`:写客户端画布背景 `struct_meta.client_background`(零计费、不访问云端),横屏源进竖屏画布时用模糊底替代黑边;`/gtrk-live-slicing` 对横竖互转的入选切片缺省加模糊底。剪映 / PR 与本地 `gtrk render` 不带该设置。
|
|
@@ -19,6 +35,12 @@
|
|
|
19
35
|
- `subtitle lay --language <code>`:指定云端拆行语种(缺省仍为 zh-CN),非中文源上字幕不再按中文断句。
|
|
20
36
|
- `/gtrk-live-slicing` 选题确认后对入选切片缺省字幕上轨;调速只在用户主动要求时加。
|
|
21
37
|
|
|
38
|
+
## 1.3.3(2026-10-08)
|
|
39
|
+
|
|
40
|
+
> 版本段补录于 2026-10-17。npm 记录的 `gitHead` 为 `13d43e9`,但同步版本号的提交 `2f5c68d` 晚于发布约 4 分钟,推断与 1.2.7 一样是从带未提交改动的工作区发的;以下按该提交说明补记,未核包体。
|
|
41
|
+
|
|
42
|
+
- 完善 Vlog 分段组装、旁白归轨和颗粒检查。
|
|
43
|
+
|
|
22
44
|
## 1.3.2(2026-10-08)
|
|
23
45
|
|
|
24
46
|
- 本地视觉理解增加当前 Agent 审阅交接:`matrix describe --prepare/--review`,已完成的本地判断不调用豆包,明确无法完成的条目按需补齐。
|
package/README.en.md
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
<p>
|
|
11
11
|
<a href="#demo">Demo</a> ·
|
|
12
12
|
<a href="#quick-start">Quick start</a> ·
|
|
13
|
-
<a href="https://hocassian.feishu.cn/wiki/HCFpwoF7SivIFbkKosgcFMcEnxk">Tutorials</a> ·
|
|
14
|
-
<a href="https://github.com/Gitruck/ai-drama-desk">AI Drama Desk</a> ·
|
|
15
|
-
<a href="docs/development.en.md">Develop</a> ·
|
|
13
|
+
<a href="https://hocassian.feishu.cn/wiki/HCFpwoF7SivIFbkKosgcFMcEnxk">Tutorials</a> ·
|
|
14
|
+
<a href="https://github.com/Gitruck/ai-drama-desk">AI Drama Desk</a> ·
|
|
15
|
+
<a href="docs/development.en.md">Develop</a> ·
|
|
16
16
|
<a href="README.md">简体中文</a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
@@ -157,37 +157,37 @@ flowchart LR
|
|
|
157
157
|
|
|
158
158
|
**Conversations currently happen in your own agent; the desktop client has no built-in AI chat.** Review and refine the project in the client after your agent changes it, or render with `gtrk render`. Motion graphics that require cloud rendering are billed separately; cached renders can be reused.
|
|
159
159
|
|
|
160
|
-
The three-format rough-cut output and the export of a fully packaged project have different support boundaries. The [workflow guide](docs/workflow.en.md) explains where each deliverable opens and which effects need rendering.
|
|
161
|
-
|
|
162
|
-
After refining a `.gtrk` project in the client, use `gtrk project export --project <dir>` to produce Jianying, Premiere XML, FCPXML or OTIO while preserving the refined timeline; do not rerun `oralcut`, which rebuilds the rough cut. Export renders MG particles into the NLE package: structural export is billed per task, and particles missing from the shared cache are billed by the minute. `project init` remains zero-credit. When only `gtrk` is requested, the native `beat_track` is kept and particle rendering is skipped.
|
|
163
|
-
|
|
164
|
-
## gtrk + AI Drama Desk: from storyboard to editable clips
|
|
165
|
-
|
|
166
|
-
For AI-generated story scenes, connect [Gitruck AI Drama Desk](https://github.com/Gitruck/ai-drama-desk) to the same project workflow. The responsibilities stay separate:
|
|
167
|
-
|
|
168
|
-
| Component | What it does | Output |
|
|
169
|
-
| --- | --- | --- |
|
|
170
|
-
| `gtrk CLI` | Understands the request in your agent, orchestrates workflows and manages `.gtrk` projects | An editable video project |
|
|
171
|
-
| `AI Drama Desk` | Imports storyboards, manages characters and styles, generates keyframes / I2V clips and exports a handoff package | A `return-v1` clip package + `manifest` |
|
|
172
|
-
| `gtrk ai-drama lay` | Places exported clips back into the project by beat windows | A dedicated AI video track |
|
|
173
|
-
|
|
174
|
-
The typical path looks like this:
|
|
175
|
-
|
|
176
|
-
```text
|
|
177
|
-
storyboard.md → AI Drama Desk → references / keyframes / I2V → return-v1 package
|
|
178
|
-
↓
|
|
179
|
-
gtrk ai-drama lay → .gtrk project → client refinement
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
The desk supports mock, cloud and optional local ComfyUI engines. Start with a zero-GPU, zero-key rehearsal, then enable a real engine when needed. For an existing desk project, the CLI only needs the **API base and project ID**; it does not scan the repository:
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
gtrk ai-drama lay --project ./my-video --desk-project <project-id>
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
→ [AI Drama Desk README](https://github.com/Gitruck/ai-drama-desk) · [AI drama tutorial](https://hocassian.feishu.cn/wiki/FRAKwUvBWib2vrkqZ5XcLDRqnOe) · [command reference](docs/reference.en.md#command-reference)
|
|
189
|
-
|
|
190
|
-
## Use just one tool, too
|
|
160
|
+
The three-format rough-cut output and the export of a fully packaged project have different support boundaries. The [workflow guide](docs/workflow.en.md) explains where each deliverable opens and which effects need rendering.
|
|
161
|
+
|
|
162
|
+
After refining a `.gtrk` project in the client, use `gtrk project export --project <dir>` to produce Jianying, Premiere XML, FCPXML or OTIO while preserving the refined timeline; do not rerun `oralcut`, which rebuilds the rough cut. Export renders MG particles into the NLE package: structural export is billed per task, and particles missing from the shared cache are billed by the minute. `project init` remains zero-credit. When only `gtrk` is requested, the native `beat_track` is kept and particle rendering is skipped.
|
|
163
|
+
|
|
164
|
+
## gtrk + AI Drama Desk: from storyboard to editable clips
|
|
165
|
+
|
|
166
|
+
For AI-generated story scenes, connect [Gitruck AI Drama Desk](https://github.com/Gitruck/ai-drama-desk) to the same project workflow. The responsibilities stay separate:
|
|
167
|
+
|
|
168
|
+
| Component | What it does | Output |
|
|
169
|
+
| --- | --- | --- |
|
|
170
|
+
| `gtrk CLI` | Understands the request in your agent, orchestrates workflows and manages `.gtrk` projects | An editable video project |
|
|
171
|
+
| `AI Drama Desk` | Imports storyboards, manages characters and styles, generates keyframes / I2V clips and exports a handoff package | A `return-v1` clip package + `manifest` |
|
|
172
|
+
| `gtrk ai-drama lay` | Places exported clips back into the project by beat windows | A dedicated AI video track |
|
|
173
|
+
|
|
174
|
+
The typical path looks like this:
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
storyboard.md → AI Drama Desk → references / keyframes / I2V → return-v1 package
|
|
178
|
+
↓
|
|
179
|
+
gtrk ai-drama lay → .gtrk project → client refinement
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The desk supports mock, cloud and optional local ComfyUI engines. Start with a zero-GPU, zero-key rehearsal, then enable a real engine when needed. For an existing desk project, the CLI only needs the **API base and project ID**; it does not scan the repository:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
gtrk ai-drama lay --project ./my-video --desk-project <project-id>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
→ [AI Drama Desk README](https://github.com/Gitruck/ai-drama-desk) · [AI drama tutorial](https://hocassian.feishu.cn/wiki/FRAKwUvBWib2vrkqZ5XcLDRqnOe) · [command reference](docs/reference.en.md#command-reference)
|
|
189
|
+
|
|
190
|
+
## Use just one tool, too
|
|
191
191
|
|
|
192
192
|
| What you need | Start here |
|
|
193
193
|
| --- | --- |
|
|
@@ -197,7 +197,7 @@ gtrk ai-drama lay --project ./my-video --desk-project <project-id>
|
|
|
197
197
|
| A visual style for your show | [Style-maker Skill](skills/gtrk-style-maker/SKILL.md) |
|
|
198
198
|
| Third-party animation, typography or creative Skills | [Skill map](https://hocassian.feishu.cn/wiki/SxRtwJ0G7inTbQkwckFcZmClnrd) |
|
|
199
199
|
|
|
200
|
-
## FAQ
|
|
200
|
+
## FAQ
|
|
201
201
|
|
|
202
202
|
**Do I need to code?**
|
|
203
203
|
|
|
@@ -215,21 +215,21 @@ Talking-head and long-to-short project workflows keep the original locally and u
|
|
|
215
215
|
|
|
216
216
|
The CLI runs across platforms. The Windows client provides visual preview and refinement; the CLI can render existing projects. Jianying and Premiere support depends on the installed software and the project format.
|
|
217
217
|
|
|
218
|
-
**Skills are missing, or a task failed. What now?**
|
|
219
|
-
|
|
220
|
-
Open a new agent session and run `gtrk doctor`. Use `gtrk skills install` to refresh Skills. If a task already has an ID, recover its results before submitting it again. See [workflow and troubleshooting](docs/workflow.en.md).
|
|
221
|
-
|
|
222
|
-
## Star History
|
|
223
|
-
|
|
224
|
-
<a href="https://star-history.com/#Gitruck/cli&Date">
|
|
225
|
-
<picture>
|
|
226
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date&theme=dark" />
|
|
227
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date" />
|
|
228
|
-
<img alt="gtrk CLI Star History" src="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date" />
|
|
229
|
-
</picture>
|
|
230
|
-
</a>
|
|
231
|
-
|
|
232
|
-
## Documentation and contributions
|
|
218
|
+
**Skills are missing, or a task failed. What now?**
|
|
219
|
+
|
|
220
|
+
Open a new agent session and run `gtrk doctor`. Use `gtrk skills install` to refresh Skills. If a task already has an ID, recover its results before submitting it again. See [workflow and troubleshooting](docs/workflow.en.md).
|
|
221
|
+
|
|
222
|
+
## Star History
|
|
223
|
+
|
|
224
|
+
<a href="https://star-history.com/#Gitruck/cli&Date">
|
|
225
|
+
<picture>
|
|
226
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date&theme=dark" />
|
|
227
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date" />
|
|
228
|
+
<img alt="gtrk CLI Star History" src="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date" />
|
|
229
|
+
</picture>
|
|
230
|
+
</a>
|
|
231
|
+
|
|
232
|
+
## Documentation and contributions
|
|
233
233
|
|
|
234
234
|
[Full tutorials](https://hocassian.feishu.cn/wiki/HCFpwoF7SivIFbkKosgcFMcEnxk) · [Workflow](docs/workflow.en.md) · [Command reference](docs/reference.en.md) · [Development](docs/development.en.md) · [Changelog](CHANGELOG.md)
|
|
235
235
|
|
package/README.md
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
<p>
|
|
11
11
|
<a href="#demo">观看演示</a> ·
|
|
12
12
|
<a href="#quick-start">快速开始</a> ·
|
|
13
|
-
<a href="https://hocassian.feishu.cn/wiki/HCFpwoF7SivIFbkKosgcFMcEnxk">使用教程</a> ·
|
|
14
|
-
<a href="https://github.com/Gitruck/ai-drama-desk">AI Drama Desk</a> ·
|
|
15
|
-
<a href="docs/development.md">开发文档</a> ·
|
|
13
|
+
<a href="https://hocassian.feishu.cn/wiki/HCFpwoF7SivIFbkKosgcFMcEnxk">使用教程</a> ·
|
|
14
|
+
<a href="https://github.com/Gitruck/ai-drama-desk">AI Drama Desk</a> ·
|
|
15
|
+
<a href="docs/development.md">开发文档</a> ·
|
|
16
16
|
<a href="README.en.md">English</a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
@@ -157,37 +157,37 @@ flowchart LR
|
|
|
157
157
|
|
|
158
158
|
**当前对话入口在你自己的 Agent 中,客户端没有内置 AI 对话框。** Agent 修改工程后,在客户端查看效果、继续精修;也可以用 `gtrk render` 渲染成片。需要云端渲染的动态图会单独计费,缓存可复用。
|
|
159
159
|
|
|
160
|
-
粗剪阶段的三端工程,与完整包装后的导出支持范围不同;[工作流指南](docs/workflow.md)说明每种交付物在哪打开、哪些效果需要渲染。
|
|
161
|
-
|
|
162
|
-
客户端精修 `.gtrk` 后,用 `gtrk project export --project <目录>` 导出剪映、Premiere XML、FCPXML 或 OTIO,保留精修时间线;不要重跑 `oralcut`,否则会重新生成粗剪。导出会把 MG 颗粒预渲进 NLE 工程,结构导出按次计费,未命中共享缓存的颗粒按分钟计费;`project init` 仍是 0 积分。只选 `gtrk` 时会保留原生 `beat_track` 并自动跳过颗粒预渲。
|
|
163
|
-
|
|
164
|
-
## gtrk + AI Drama Desk:从分镜到可回轨片段
|
|
165
|
-
|
|
166
|
-
如果你的素材不是实拍,而是希望把一份分镜稿变成 AI 情景片段,可以把 [Gitruck AI Drama Desk](https://github.com/Gitruck/ai-drama-desk) 接到同一条工程链里。两者分工很清楚:
|
|
167
|
-
|
|
168
|
-
| 组件 | 负责什么 | 产物 |
|
|
169
|
-
| --- | --- | --- |
|
|
170
|
-
| `gtrk CLI` | 在 Agent 中理解需求、组织工作流、管理 `.gtrk` 工程 | 可继续精修的视频工程 |
|
|
171
|
-
| `AI Drama Desk` | 导入分镜稿、管理角色与画风、生成 keyframe / I2V 片段、导出回轨包 | `return-v1` 片段包 + `manifest` |
|
|
172
|
-
| `gtrk ai-drama lay` | 把工作台导出的片段按 beat 区间铺回工程 | 独立 AI 视频轨 |
|
|
173
|
-
|
|
174
|
-
典型路径是:
|
|
175
|
-
|
|
176
|
-
```text
|
|
177
|
-
分镜稿 md → AI Drama Desk → 角色参考图 / keyframe / I2V → return-v1 导出包
|
|
178
|
-
↓
|
|
179
|
-
gtrk ai-drama lay → .gtrk 工程 → 客户端精修
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
工作台支持 mock、云端出口和可选的本地 ComfyUI;你可以先零 GPU、零 Key 跑通流程,再按需要启用真实引擎。已有工作台项目时,CLI 只需要 **API 地址 + 项目 ID**,不需要扫描仓库目录:
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
gtrk ai-drama lay --project ./my-video --desk-project <project-id>
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
→ [AI Drama Desk README](https://github.com/Gitruck/ai-drama-desk) · [AI 漫剧教程](https://hocassian.feishu.cn/wiki/FRAKwUvBWib2vrkqZ5XcLDRqnOe) · [命令参考](docs/reference.md#命令参考)
|
|
189
|
-
|
|
190
|
-
## 成片之外,也能只做一件事
|
|
160
|
+
粗剪阶段的三端工程,与完整包装后的导出支持范围不同;[工作流指南](docs/workflow.md)说明每种交付物在哪打开、哪些效果需要渲染。
|
|
161
|
+
|
|
162
|
+
客户端精修 `.gtrk` 后,用 `gtrk project export --project <目录>` 导出剪映、Premiere XML、FCPXML 或 OTIO,保留精修时间线;不要重跑 `oralcut`,否则会重新生成粗剪。导出会把 MG 颗粒预渲进 NLE 工程,结构导出按次计费,未命中共享缓存的颗粒按分钟计费;`project init` 仍是 0 积分。只选 `gtrk` 时会保留原生 `beat_track` 并自动跳过颗粒预渲。
|
|
163
|
+
|
|
164
|
+
## gtrk + AI Drama Desk:从分镜到可回轨片段
|
|
165
|
+
|
|
166
|
+
如果你的素材不是实拍,而是希望把一份分镜稿变成 AI 情景片段,可以把 [Gitruck AI Drama Desk](https://github.com/Gitruck/ai-drama-desk) 接到同一条工程链里。两者分工很清楚:
|
|
167
|
+
|
|
168
|
+
| 组件 | 负责什么 | 产物 |
|
|
169
|
+
| --- | --- | --- |
|
|
170
|
+
| `gtrk CLI` | 在 Agent 中理解需求、组织工作流、管理 `.gtrk` 工程 | 可继续精修的视频工程 |
|
|
171
|
+
| `AI Drama Desk` | 导入分镜稿、管理角色与画风、生成 keyframe / I2V 片段、导出回轨包 | `return-v1` 片段包 + `manifest` |
|
|
172
|
+
| `gtrk ai-drama lay` | 把工作台导出的片段按 beat 区间铺回工程 | 独立 AI 视频轨 |
|
|
173
|
+
|
|
174
|
+
典型路径是:
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
分镜稿 md → AI Drama Desk → 角色参考图 / keyframe / I2V → return-v1 导出包
|
|
178
|
+
↓
|
|
179
|
+
gtrk ai-drama lay → .gtrk 工程 → 客户端精修
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
工作台支持 mock、云端出口和可选的本地 ComfyUI;你可以先零 GPU、零 Key 跑通流程,再按需要启用真实引擎。已有工作台项目时,CLI 只需要 **API 地址 + 项目 ID**,不需要扫描仓库目录:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
gtrk ai-drama lay --project ./my-video --desk-project <project-id>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
→ [AI Drama Desk README](https://github.com/Gitruck/ai-drama-desk) · [AI 漫剧教程](https://hocassian.feishu.cn/wiki/FRAKwUvBWib2vrkqZ5XcLDRqnOe) · [命令参考](docs/reference.md#命令参考)
|
|
189
|
+
|
|
190
|
+
## 成片之外,也能只做一件事
|
|
191
191
|
|
|
192
192
|
| 想做什么 | 从这里开始 |
|
|
193
193
|
| --- | --- |
|
|
@@ -197,7 +197,7 @@ gtrk ai-drama lay --project ./my-video --desk-project <project-id>
|
|
|
197
197
|
| 给自己的栏目建立视觉风格 | [风格体系 Skill](skills/gtrk-style-maker/SKILL.md) |
|
|
198
198
|
| 接入第三方动画、排版或创作 Skills | [Skill 地图](https://hocassian.feishu.cn/wiki/SxRtwJ0G7inTbQkwckFcZmClnrd) |
|
|
199
199
|
|
|
200
|
-
## 常见问题
|
|
200
|
+
## 常见问题
|
|
201
201
|
|
|
202
202
|
**需要会写代码吗?**
|
|
203
203
|
|
|
@@ -215,21 +215,21 @@ CLI 代码采用 MIT 许可证;同合云能力按对应规则计费,你使
|
|
|
215
215
|
|
|
216
216
|
CLI 可以跨平台执行。Windows 客户端用于可视化预览与精修;已有工程也可通过 CLI 渲染。剪映、Premiere 的使用取决于对应软件和工程格式的支持情况。
|
|
217
217
|
|
|
218
|
-
**安装后找不到 Skills,或任务出错怎么办?**
|
|
219
|
-
|
|
220
|
-
重新打开 Agent 会话,运行 `gtrk doctor` 检查环境;补装用 `gtrk skills install`。任务已有 ID 时优先恢复结果,避免重新提交云端任务。详见[工作流与排障](docs/workflow.md)。
|
|
221
|
-
|
|
222
|
-
## Star History
|
|
223
|
-
|
|
224
|
-
<a href="https://star-history.com/#Gitruck/cli&Date">
|
|
225
|
-
<picture>
|
|
226
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date&theme=dark" />
|
|
227
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date" />
|
|
228
|
-
<img alt="gtrk CLI Star History" src="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date" />
|
|
229
|
-
</picture>
|
|
230
|
-
</a>
|
|
231
|
-
|
|
232
|
-
## 文档与参与
|
|
218
|
+
**安装后找不到 Skills,或任务出错怎么办?**
|
|
219
|
+
|
|
220
|
+
重新打开 Agent 会话,运行 `gtrk doctor` 检查环境;补装用 `gtrk skills install`。任务已有 ID 时优先恢复结果,避免重新提交云端任务。详见[工作流与排障](docs/workflow.md)。
|
|
221
|
+
|
|
222
|
+
## Star History
|
|
223
|
+
|
|
224
|
+
<a href="https://star-history.com/#Gitruck/cli&Date">
|
|
225
|
+
<picture>
|
|
226
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date&theme=dark" />
|
|
227
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date" />
|
|
228
|
+
<img alt="gtrk CLI Star History" src="https://api.star-history.com/svg?repos=Gitruck/cli&type=Date" />
|
|
229
|
+
</picture>
|
|
230
|
+
</a>
|
|
231
|
+
|
|
232
|
+
## 文档与参与
|
|
233
233
|
|
|
234
234
|
[完整教程](https://hocassian.feishu.cn/wiki/HCFpwoF7SivIFbkKosgcFMcEnxk) · [工作流](docs/workflow.md) · [命令参考](docs/reference.md) · [开发文档](docs/development.md) · [更新日志](CHANGELOG.md)
|
|
235
235
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* 合成颗粒工具箱 composite-kit@1 的公共样式(构建时内联)。
|
|
2
|
+
* 中性:配色、字体、纹理一律走变量,由栏目 / 工程风格(composite/theme.css,构建时接在本文件之后)覆盖。
|
|
3
|
+
* 变量:--bg 底色 / --bg2 次底色 / --ink 正文 / --muted 次要文字 / --line 分隔线 /
|
|
4
|
+
* --accent 强调 / --accent2 / --accent3 辅助色 / --alert 警示 / --font 正文字体 / --font-serif 标题字体。 */
|
|
5
|
+
html, body { margin: 0; height: 100%; overflow: hidden; background: transparent; }
|
|
6
|
+
#stage {
|
|
7
|
+
position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; transform-origin: 0 0; overflow: hidden;
|
|
8
|
+
--bg: #F4F4F2; --bg2: #E6E6E2; --ink: #1D1D1F; --muted: #6B6B70; --line: #CFCFCB;
|
|
9
|
+
--accent: #2F6FDE; --accent2: #2E8B57; --accent3: #4A5568; --alert: #D14343;
|
|
10
|
+
--font: "Noto Sans SC", "Source Han Sans CN", "Microsoft YaHei", "PingFang SC", sans-serif;
|
|
11
|
+
--font-serif: "Noto Serif SC", "Source Han Serif SC", "Source Han Serif", "SimSun", serif;
|
|
12
|
+
font-family: var(--font); color: var(--ink);
|
|
13
|
+
}
|
|
14
|
+
#stage > *, #stage .abs { position: absolute; box-sizing: border-box; }
|
|
15
|
+
[data-slot] { visibility: hidden !important; }
|
|
16
|
+
.serif { font-family: var(--font-serif); }
|
|
17
|
+
.bed { left: 0; top: 0; width: 1920px; height: 1080px; background: var(--bg); }
|
|
18
|
+
.bed.dark { background: radial-gradient(ellipse at 30% 35%, #2A2A2E 0%, #141416 70%); }
|
|
19
|
+
/* 槽位装饰:阴影在下(back),描边在上(front)。描边只给最上层的画面——下层画面的 front 描边会压到上层画面上 */
|
|
20
|
+
.shadow { border-radius: 18px; box-shadow: 0 18px 50px rgba(0, 0, 0, .22), 0 3px 10px rgba(0, 0, 0, .12); background: #000; }
|
|
21
|
+
.shadow.round { border-radius: 50%; }
|
|
22
|
+
.frame { border: 3px solid var(--ink); border-radius: 18px; }
|
|
23
|
+
.frame.round { border-radius: 50%; }
|
|
24
|
+
.frame.thin { border-width: 2px; }
|
|
25
|
+
.frame.light { border-color: rgba(255, 255, 255, .85); }
|
|
26
|
+
.ring { border: 4px solid var(--ink); border-radius: 50%; }
|
|
27
|
+
.ring.light { border-color: rgba(255, 255, 255, .9); }
|
|
28
|
+
/* 字幕:颗粒接管字幕后自己排 */
|
|
29
|
+
.cap {
|
|
30
|
+
left: 160px; width: 1600px; top: 958px; height: 70px; text-align: center; white-space: nowrap;
|
|
31
|
+
font-size: 44px; font-weight: 600; letter-spacing: .02em; color: var(--ink); line-height: 70px;
|
|
32
|
+
}
|
|
33
|
+
.cap b { color: var(--accent); font-weight: 800; }
|
|
34
|
+
.cap.dark { color: #fff; text-shadow: 0 2px 12px rgba(0, 0, 0, .55); }
|
|
35
|
+
.cap.dark b { color: var(--accent); }
|
|
36
|
+
/* 文字 */
|
|
37
|
+
.tag { font-size: 26px; letter-spacing: .2em; color: var(--muted); white-space: nowrap; }
|
|
38
|
+
.kicker { font-size: 28px; font-weight: 700; letter-spacing: .28em; color: var(--accent); white-space: nowrap; }
|
|
39
|
+
.h1 { font-size: 96px; font-weight: 900; line-height: 1.12; white-space: nowrap; }
|
|
40
|
+
.h2 { font-size: 64px; font-weight: 900; line-height: 1.2; white-space: nowrap; }
|
|
41
|
+
.h3 { font-size: 44px; font-weight: 700; line-height: 1.3; white-space: nowrap; }
|
|
42
|
+
.body { font-size: 34px; line-height: 1.5; color: var(--ink); }
|
|
43
|
+
.num { font-size: 30px; font-weight: 800; color: var(--accent); letter-spacing: .1em; }
|
|
44
|
+
.pill { height: 54px; padding: 0 26px; border-radius: 27px; font-size: 28px; font-weight: 700; line-height: 54px; white-space: nowrap; }
|
|
45
|
+
.rule { height: 3px; background: var(--ink); }
|
|
46
|
+
.card { background: #FFFFFF; border-radius: 22px; box-shadow: 0 10px 30px rgba(0, 0, 0, .08); }
|
|
47
|
+
.stamp { font-size: 72px; font-weight: 900; color: var(--alert); border: 6px solid var(--alert); border-radius: 14px; padding: 6px 26px; white-space: nowrap; }
|
|
48
|
+
/* 对话气泡(字幕变成一串对话时用) */
|
|
49
|
+
.bubble {
|
|
50
|
+
height: 76px; padding: 0 30px; border-radius: 38px 38px 10px 38px; background: #FFFFFF; font-size: 32px; line-height: 76px;
|
|
51
|
+
white-space: nowrap; color: var(--ink); box-shadow: 0 6px 18px rgba(0, 0, 0, .08); border: 2px solid transparent;
|
|
52
|
+
}
|
|
53
|
+
.bubble b { color: var(--accent); font-weight: 700; }
|
|
54
|
+
.bubble.now { border-color: var(--accent); }
|
|
55
|
+
/* 画面标签:贴在画面左上角,说明这张画面对应台词里的哪个词 */
|
|
56
|
+
.ptag { height: 46px; padding: 0 20px; border-radius: 23px; background: rgba(20, 20, 22, .74); color: #fff; font-size: 26px; font-weight: 700; line-height: 46px; letter-spacing: .06em; white-space: nowrap; }
|
|
57
|
+
.ptag.warm { background: var(--accent); }
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/* 合成颗粒工具箱 composite-kit@1(html@1 引擎)——`gtrk composite build` 构建时内联进每颗颗粒,颗粒自包含、不外链。
|
|
2
|
+
* 随 @gitruck/cli 分发(assets/composite-kit/);改行为即升版本号(颗粒 <meta name="generator"> 与 CP.VERSION 记着它)。
|
|
3
|
+
*
|
|
4
|
+
* 场景脚本只写一件事:`CP.render = (t) => { ... }`(t = 局部秒)。其余由这里负责:
|
|
5
|
+
* - 宿主协议:hello → meta;init → 帧率 / 锚点 / 图层 / 字幕;apply(frame) → 画这一帧并回报槽位几何;lint → 排版自检;
|
|
6
|
+
* - 分层:舞台缺省属 back,`data-layer="front"` 的子树属 front(visibility 继承);
|
|
7
|
+
* - 槽位几何:`[data-slot]` 的包围盒 + data-z / data-fit / data-shape / data-radius / data-filter / data-crop(上右下左);
|
|
8
|
+
* - 装饰跟随:`data-for="<槽位>"` 的元素与槽位同框(data-pad 外扩),用于边框、阴影、角标;
|
|
9
|
+
* - 字幕:接管字幕后由颗粒自己排(`CP.caption`),文字来自 init.texts;
|
|
10
|
+
* - 逐句换画面与版式切换:`at` / `mix` / `seq` / `place`;
|
|
11
|
+
* - 排版自检 `lint`:下层画面的 front 描边压上层画面、front 线条穿过画面、画面越出舞台。
|
|
12
|
+
* 纯函数:同一帧号永远画出同一画面;不用 Date / Math.random / requestAnimationFrame / setInterval。
|
|
13
|
+
*/
|
|
14
|
+
(() => {
|
|
15
|
+
"use strict";
|
|
16
|
+
const META = JSON.parse(document.querySelector("script[data-gtrk-composite]").textContent);
|
|
17
|
+
const W = META.stage.width, H = META.stage.height;
|
|
18
|
+
const stage = document.getElementById("stage");
|
|
19
|
+
let FPS = (META.resolved && META.resolved.video_rate) || 60;
|
|
20
|
+
let ANCHORS = (META.resolved && META.resolved.anchors_f) || {};
|
|
21
|
+
let LAYER = "all";
|
|
22
|
+
let TEXTS = {};
|
|
23
|
+
|
|
24
|
+
const clamp = (x, a = 0, b = 1) => Math.min(b, Math.max(a, x));
|
|
25
|
+
const lerp = (a, b, p) => a + (b - a) * p;
|
|
26
|
+
const ease = {
|
|
27
|
+
out: (x) => 1 - Math.pow(1 - x, 3),
|
|
28
|
+
inOut: (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2),
|
|
29
|
+
back: (x) => { const c = 1.4; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); },
|
|
30
|
+
lin: (x) => x,
|
|
31
|
+
};
|
|
32
|
+
const prog = (t, t0, dur, e = ease.out) => e(clamp((t - t0) / Math.max(dur, 1e-6)));
|
|
33
|
+
/** 窗口内出现:t0 前 0,淡入 fi 秒到 1,t1 起淡出 fo 秒到 0。 */
|
|
34
|
+
const span = (t, t0, t1, fi = 0.5, fo = 0.4) => Math.min(prog(t, t0, fi), 1 - prog(t, t1, fo, ease.inOut));
|
|
35
|
+
const $ = (id) => document.getElementById(id);
|
|
36
|
+
const A = (key) => {
|
|
37
|
+
if (!(key in ANCHORS)) throw new Error("锚点「" + key + "」未解析");
|
|
38
|
+
return ANCHORS[key] / FPS;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function box(el, x, y, w, h) {
|
|
42
|
+
if (typeof el === "string") el = $(el);
|
|
43
|
+
el.style.left = x + "px"; el.style.top = y + "px"; el.style.width = w + "px"; el.style.height = h + "px";
|
|
44
|
+
return el;
|
|
45
|
+
}
|
|
46
|
+
/** 透明度 + 位移 / 缩放(不旋转——槽位与其装饰不得旋转)。 */
|
|
47
|
+
function look(el, o = 1, dx = 0, dy = 0, s = 1) {
|
|
48
|
+
if (typeof el === "string") el = $(el);
|
|
49
|
+
el.style.opacity = String(clamp(o));
|
|
50
|
+
el.style.transform = dx || dy || s !== 1 ? "translate(" + dx + "px," + dy + "px) scale(" + s + ")" : "";
|
|
51
|
+
return el;
|
|
52
|
+
}
|
|
53
|
+
/** 槽位与跟随它的装饰一起定框;o 同时作用于两者。 */
|
|
54
|
+
function slot(id, x, y, w, h, o = 1) {
|
|
55
|
+
const el = document.querySelector('[data-slot="' + id + '"]');
|
|
56
|
+
box(el, x, y, w, h); el.style.opacity = String(clamp(o));
|
|
57
|
+
for (const d of document.querySelectorAll('[data-for="' + id + '"]')) {
|
|
58
|
+
const p = Number(d.dataset.pad || 0);
|
|
59
|
+
box(d, x - p, y - p, w + 2 * p, h + 2 * p); d.style.opacity = String(clamp(o));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** 槽位按矩形 [x, y, w, h] 定框(slot 的数组写法)。 */
|
|
63
|
+
const at = (id, r, o = 1) => slot(id, r[0], r[1], r[2], r[3], o);
|
|
64
|
+
/** 两个矩形之间插值(版式切换:单画 → 并排)。 */
|
|
65
|
+
const mix = (a, b, p) => a.map((v, i) => lerp(v, b[i], p));
|
|
66
|
+
/**
|
|
67
|
+
* 逐句换画面的透明度:在 a 前 fade 秒淡入、a 时刻已全显;到 b(下一张已全显)之后直接关掉。
|
|
68
|
+
* 同一位置叠放的几张画面按 data-z 递增排,下一张盖住上一张再关上一张——任一时刻最多两张在解码。
|
|
69
|
+
*/
|
|
70
|
+
const seq = (t, a, b = null, fade = 0.3) => prog(t, a - fade, fade) * (b == null || t < b + 0.02 ? 1 : 0);
|
|
71
|
+
/** 把元素放到 (x, y) 并给透明度(标签、角标跟随画面用)。 */
|
|
72
|
+
function place(el, x, y, o = 1) {
|
|
73
|
+
if (typeof el === "string") el = $(el);
|
|
74
|
+
el.style.left = x + "px"; el.style.top = y + "px"; el.style.opacity = String(clamp(o));
|
|
75
|
+
return el;
|
|
76
|
+
}
|
|
77
|
+
/** 逐字显现(p ∈ [0,1]):未显现的字留占位但透明,排版不跳。 */
|
|
78
|
+
function typeOn(el, text, p) {
|
|
79
|
+
if (typeof el === "string") el = $(el);
|
|
80
|
+
const n = Math.round(clamp(p) * text.length);
|
|
81
|
+
const key = text + "|" + n;
|
|
82
|
+
if (el.dataset.typed === key) return;
|
|
83
|
+
el.dataset.typed = key;
|
|
84
|
+
el.innerHTML = "";
|
|
85
|
+
const a = document.createElement("span"); a.textContent = text.slice(0, n);
|
|
86
|
+
const b = document.createElement("span"); b.textContent = text.slice(n); b.style.opacity = "0";
|
|
87
|
+
el.append(a, b);
|
|
88
|
+
}
|
|
89
|
+
/** 字幕:取文本输入 slot 在第 f 帧的那一条;highlight 里的词加强调色。 */
|
|
90
|
+
function cueAt(slotId, t) {
|
|
91
|
+
const f = Math.floor(t * FPS + 1e-6);
|
|
92
|
+
const list = TEXTS[slotId] || [];
|
|
93
|
+
for (const c of list) if (f >= c.f0 && f < c.f1) return c;
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/** 显现:淡入 + 上浮(p ∈ [0,1])。 */
|
|
97
|
+
const reveal = (el, p, dy = 26) => look(el, p, 0, dy * (1 - p));
|
|
98
|
+
/** 从左往右擦出。 */
|
|
99
|
+
function wipe(el, p) {
|
|
100
|
+
if (typeof el === "string") el = $(el);
|
|
101
|
+
el.style.clipPath = "inset(-20% " + (100 - clamp(p) * 100).toFixed(2) + "% -20% 0)";
|
|
102
|
+
el.style.opacity = p > 0 ? "1" : "0";
|
|
103
|
+
}
|
|
104
|
+
/** SVG 路径描边动画。 */
|
|
105
|
+
function draw(el, p) {
|
|
106
|
+
if (typeof el === "string") el = $(el);
|
|
107
|
+
const L = el.getTotalLength();
|
|
108
|
+
el.style.strokeDasharray = String(L);
|
|
109
|
+
el.style.strokeDashoffset = String(L * (1 - clamp(p)));
|
|
110
|
+
}
|
|
111
|
+
/** 字幕;hide = [[t0, t1], …] 时段内不显示(屏上排版已经写着同一句话时用)。 */
|
|
112
|
+
function caption(el, slotId, t, highlight = [], hide = []) {
|
|
113
|
+
if (typeof el === "string") el = $(el);
|
|
114
|
+
if (hide.some(([a, b]) => t >= a && t < b)) { el.style.opacity = "0"; return null; }
|
|
115
|
+
const c = cueAt(slotId, t);
|
|
116
|
+
const text = c ? c.text : "";
|
|
117
|
+
const key = text + "|" + highlight.join(",");
|
|
118
|
+
if (el.dataset.cue !== key) {
|
|
119
|
+
el.dataset.cue = key;
|
|
120
|
+
el.innerHTML = "";
|
|
121
|
+
let rest = text;
|
|
122
|
+
while (rest) {
|
|
123
|
+
let hit = null, at = -1;
|
|
124
|
+
for (const w of highlight) {
|
|
125
|
+
const i = rest.indexOf(w);
|
|
126
|
+
if (i >= 0 && (at < 0 || i < at)) { at = i; hit = w; }
|
|
127
|
+
}
|
|
128
|
+
if (!hit) { el.append(document.createTextNode(rest)); break; }
|
|
129
|
+
if (at > 0) el.append(document.createTextNode(rest.slice(0, at)));
|
|
130
|
+
const b = document.createElement("b"); b.textContent = hit; el.append(b);
|
|
131
|
+
rest = rest.slice(at + hit.length);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const age = c ? t - c.f0 / FPS : 0;
|
|
135
|
+
el.style.opacity = c ? String(prog(age, 0, 0.12)) : "0";
|
|
136
|
+
return c;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function applyLayer() {
|
|
140
|
+
stage.style.visibility = LAYER === "front" ? "hidden" : "visible";
|
|
141
|
+
for (const el of stage.querySelectorAll("[data-layer]")) {
|
|
142
|
+
el.style.visibility = LAYER === "all" || el.dataset.layer === LAYER ? "visible" : "hidden";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function measure() {
|
|
147
|
+
const s = stage.getBoundingClientRect(), k = s.width / W;
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const el of stage.querySelectorAll("[data-slot]")) {
|
|
150
|
+
const r = el.getBoundingClientRect();
|
|
151
|
+
const opacity = parseFloat(getComputedStyle(el).opacity);
|
|
152
|
+
const crop = (el.dataset.crop || "0,0,0,0").split(",").map(Number);
|
|
153
|
+
out.push({
|
|
154
|
+
id: el.dataset.slot, z: Number(el.dataset.z || 0),
|
|
155
|
+
x: (r.left - s.left) / k, y: (r.top - s.top) / k, w: r.width / k, h: r.height / k,
|
|
156
|
+
opacity, visible: opacity > 0.001 && r.width > 0.5 && r.height > 0.5,
|
|
157
|
+
shape: el.dataset.shape || "rect", radius: Number(el.dataset.radius || 0),
|
|
158
|
+
fit: el.dataset.fit || "cover", filter: el.dataset.filter || "",
|
|
159
|
+
crop: crop.length === 4 && crop.every((c) => c >= 0 && c < 1) ? crop : [0, 0, 0, 0],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return out.sort((a, b) => a.z - b.z);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 排版自检(只给出片台 / 构建工具用,宿主协议不依赖它)。查三类:
|
|
167
|
+
* 1. 两张可见画面重叠,而下面那张有 front 层描边 —— 宿主先画全部画面、再画 front 镜像,
|
|
168
|
+
* 下面那张的边框会压在上面那张画面上(同框交替淡入时两框重合,不算);
|
|
169
|
+
* 2. front 层的线条(path / line / polyline,按已画出的长度取点)穿过某张可见画面;
|
|
170
|
+
* 3. 画面越出舞台(铺满舞台的底图除外)。
|
|
171
|
+
*/
|
|
172
|
+
function lint() {
|
|
173
|
+
const s = stage.getBoundingClientRect(), k = s.width / W;
|
|
174
|
+
const op = (el) => { let o = 1; for (let e = el; e && e !== stage; e = e.parentElement) o *= parseFloat(getComputedStyle(e).opacity); return o; };
|
|
175
|
+
// 铺满舞台的画面是底图,压在它上面的任何东西都是有意叠加,不算穿模
|
|
176
|
+
const slots = measure().filter((g) => g.visible && !(g.x <= 1 && g.y <= 1 && g.x + g.w >= W - 1 && g.y + g.h >= H - 1));
|
|
177
|
+
const same = (a, b) => ["x", "y", "w", "h"].every((q) => Math.abs(a[q] - b[q]) < 2);
|
|
178
|
+
const meet = (a, b) => Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x) > 2 && Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y) > 2;
|
|
179
|
+
const deco = (id) => [...stage.querySelectorAll('[data-for="' + id + '"][data-layer="front"]')].some((d) => op(d) > 0.01);
|
|
180
|
+
const issues = [];
|
|
181
|
+
for (const g of measure().filter((g) => g.visible)) {
|
|
182
|
+
if (g.x < -1 || g.y < -1 || g.x + g.w > W + 1 || g.y + g.h > H + 1) {
|
|
183
|
+
const full = g.x <= 1 && g.y <= 1 && g.x + g.w >= W - 1 && g.y + g.h >= H - 1;
|
|
184
|
+
if (!full) issues.push({ kind: "slot-out-of-stage", slot: g.id });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (let i = 0; i < slots.length; i++) {
|
|
188
|
+
for (let j = i + 1; j < slots.length; j++) {
|
|
189
|
+
const a = slots[i], b = slots[j];
|
|
190
|
+
if (!meet(a, b) || same(a, b)) continue;
|
|
191
|
+
if (deco(a.id)) issues.push({ kind: "border-over-slot", lower: a.id, upper: b.id });
|
|
192
|
+
else if (a.z === b.z && deco(b.id)) issues.push({ kind: "same-z-overlap", a: a.id, b: b.id });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const inside = (g, x, y) => {
|
|
196
|
+
if (g.shape === "circle") { const dx = (x - g.x - g.w / 2) / (g.w / 2), dy = (y - g.y - g.h / 2) / (g.h / 2); return dx * dx + dy * dy < 0.96; }
|
|
197
|
+
return x > g.x + 3 && x < g.x + g.w - 3 && y > g.y + 3 && y < g.y + g.h - 3;
|
|
198
|
+
};
|
|
199
|
+
for (const p of stage.querySelectorAll('[data-layer="front"] path, [data-layer="front"] line, [data-layer="front"] polyline')) {
|
|
200
|
+
if (op(p) < 0.01 || !p.getTotalLength) continue;
|
|
201
|
+
const L = p.getTotalLength();
|
|
202
|
+
const cs = getComputedStyle(p);
|
|
203
|
+
const drawn = cs.strokeDasharray && cs.strokeDasharray !== "none" ? L - (parseFloat(cs.strokeDashoffset) || 0) : L;
|
|
204
|
+
const m = p.getScreenCTM();
|
|
205
|
+
const hit = new Set();
|
|
206
|
+
for (let d = 0; d <= drawn; d += 6) {
|
|
207
|
+
const q = new DOMPoint(p.getPointAtLength(d).x, p.getPointAtLength(d).y).matrixTransform(m);
|
|
208
|
+
const x = (q.x - s.left) / k, y = (q.y - s.top) / k;
|
|
209
|
+
for (const g of slots) if (!hit.has(g.id) && inside(g, x, y)) hit.add(g.id);
|
|
210
|
+
}
|
|
211
|
+
for (const id of hit) issues.push({ kind: "stroke-through-slot", stroke: p.id || p.tagName, slot: id });
|
|
212
|
+
}
|
|
213
|
+
return issues;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function fitStage() { stage.style.transform = "scale(" + innerWidth / W + ")"; }
|
|
217
|
+
addEventListener("resize", fitStage); fitStage();
|
|
218
|
+
|
|
219
|
+
const CP = (window.CP = {
|
|
220
|
+
VERSION: "composite-kit@1",
|
|
221
|
+
META, W, H, $, A, has: (k) => k in ANCHORS, clamp, lerp, ease, prog, span, box, look, slot, at, mix, seq, place, typeOn, cueAt, caption,
|
|
222
|
+
reveal, wipe, draw, cues: (slotId) => TEXTS[slotId] || [], fps: () => FPS,
|
|
223
|
+
render: () => {},
|
|
224
|
+
/** init 之后调用一次(字幕等输入已就位):按输入建一次性的 DOM。必须幂等。 */
|
|
225
|
+
setup: () => {},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
addEventListener("message", (ev) => {
|
|
229
|
+
const m = ev.data;
|
|
230
|
+
if (!m || m.ch !== "gtrk-composite") return;
|
|
231
|
+
const reply = (x) => parent.postMessage(Object.assign({ ch: "gtrk-composite" }, x), "*");
|
|
232
|
+
try {
|
|
233
|
+
if (m.type === "hello") reply({ type: "meta", meta: META });
|
|
234
|
+
else if (m.type === "init") {
|
|
235
|
+
FPS = m.fps || FPS; ANCHORS = m.anchors || ANCHORS; LAYER = m.layer || "all"; TEXTS = m.texts || {};
|
|
236
|
+
CP.setup(); applyLayer(); reply({ type: "ready", layer: LAYER });
|
|
237
|
+
} else if (m.type === "apply") {
|
|
238
|
+
CP.render(m.frame / FPS);
|
|
239
|
+
reply({ type: "frame", seq: m.seq, frame: m.frame, slots: measure(), layer: LAYER });
|
|
240
|
+
} else if (m.type === "lint") {
|
|
241
|
+
// 回包类型不能也叫 lint:颗粒作为顶层页面跑(gtrk composite lint)时回包会回到自己的监听里,同名就会无限往返
|
|
242
|
+
reply({ type: "lint-result", seq: m.seq, issues: lint() });
|
|
243
|
+
}
|
|
244
|
+
} catch (e) { reply({ type: "error", seq: m.seq, message: String((e && e.message) || e) }); }
|
|
245
|
+
});
|
|
246
|
+
})();
|