@gitruck/cli 1.3.3 → 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 +53 -28
- package/CHANGELOG.md +30 -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-evidence.md +200 -196
- package/contracts/gsap-emit-v1.md +163 -163
- package/contracts/handoff-contracts.json +10 -10
- package/dist/index.js +445 -291
- package/docs/development.en.md +47 -47
- package/docs/development.md +47 -47
- package/docs/reference.en.md +77 -11
- package/docs/reference.md +88 -22
- 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 +75 -25
- package/skills/gtrk-long2short/SKILL.md +11 -2
- 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
|
@@ -3,9 +3,9 @@ name: gtrk-ai-drama
|
|
|
3
3
|
description: AI 情景动画分镜稿生成器——把成片 SOP 第③步(B-roll 底轨阶段)分到 AI_DRAMA 车道的 beat,逐个产**四段朴素描述(故事背景 / 角色 / 分镜 / 原文文稿)+ 独立视觉基调段 + 时长预算**,中英双版;Markdown 外壳默认严格兼容 gitruck-ai-drama-desk 解析器,可直接导入工作台,也可拿去任意外部 AI 漫剧管线(可灵 / 即梦 / Vidu / Veo / Runway / LTX / 本地开源,或 LibTV / OiiOii / TapNow 这类成片 agent 平台)上手用。创作与出片仍在 skill / 外部工作台;工作台导出的 return-v1 包用 `gtrk ai-drama lay` 确定性回填独立 AI 轨。当用户想「上 AI 情景动画 / 做 AI 分镜 / AI 动画描述 / 把这段做成 AI 视频 / 给这段配 AI 情景动画 / 弗洛伊德这段怎么做视频」时使用本 skill。通用拆镜 craft(蒙太奇段分层 / 四段描述结构 / 中英双语 / 时长预算)由本 skill 自持;栏目视觉 DNA(Style Lock)从栏目配置 style.skills 里 produces==AI_DRAMA 的条目解析注入,绝不硬编某栏目风格。凡把口播派单里 AI_DRAMA 段落转成可出片的描述稿,优先用本 skill,别手搓。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# AI 情景动画分镜稿生成器(gtrk-ai-drama)
|
|
7
|
-
|
|
8
|
-
用户要求间隔取关键镜头、剩余由实况补齐时,先读 [稀疏关键场景与角色参考](references/sparse-key-scenes.md)。其短 beat 窗口、时基、逐镜参考和留空规则优先于下文针对连续 AI 段落的时长预算;不转入通篇 AI 图纸。
|
|
6
|
+
# AI 情景动画分镜稿生成器(gtrk-ai-drama)
|
|
7
|
+
|
|
8
|
+
用户要求间隔取关键镜头、剩余由实况补齐时,先读 [稀疏关键场景与角色参考](references/sparse-key-scenes.md)。其短 beat 窗口、时基、逐镜参考和留空规则优先于下文针对连续 AI 段落的时长预算;不转入通篇 AI 图纸。
|
|
9
9
|
|
|
10
10
|
把口播派单里分到 **`AI_DRAMA` 车道**的 beat,逐个拆成分镜、为每个 beat 产 **四段朴素描述 + 独立视觉基调 + 时长预算**(中英双版),落到 `<project>/ai-drama/<beat_id>.md`,再交棒 AI Drama Desk 或任意外部平台出片;标准工作台导出包由 CLI 自动回填时间线。
|
|
11
11
|
|
|
@@ -24,7 +24,7 @@ return-v1 标准包由 `gtrk ai-drama lay` 确定性回填;其他平台没有
|
|
|
24
24
|
|
|
25
25
|
**你与 `matrix` 同属第 ③ 步,MUST 在 MG 之前落位。** 理由:**AI 情景片段属于底轨 B-roll 画面家族,不是叠加层**——整条管线里唯一的叠加层是 MG(含 ov)。MG 的排版决策是「因势象形避主体」、**依赖底轨的最终画面构图**;若你的片段在 MG 之后才回铺,底轨构图就变了,先产的 MG 是对着「还没有 AI 画面的底轨」做的避让,你的片段落位后必然被避让错位、甚至被 MG 盖住画面主体。
|
|
26
26
|
|
|
27
|
-
> ⚠️ **工序顺序**:AI 情景片段必须先落位;它属于底轨 B-roll,不是最后叠加的图层。之后再做全局构图检查与 MG。
|
|
27
|
+
> ⚠️ **工序顺序**:AI 情景片段必须先落位;它属于底轨 B-roll,不是最后叠加的图层。之后再做全局构图检查与 MG。
|
|
28
28
|
|
|
29
29
|
**异步等待的口子(你造成的那个)**:你产完描述后,用户要去外部平台出片、抽卡可能数天。严格串行会把 ④⑤ 无限期卡住,故约定:进 ⑤ 的硬门是「**AI 片段已回铺 ∨ 用户明示先跳过**」。走「明示跳过」时,下游 `/gtrk-mg` MUST 把与你的 beat 相邻/重叠区间的 MG 颗粒标记为「AI 回铺后待复查构图」并在收口时复述——**你在交代进度时也要把这个代价说清**,让用户知道跳过意味着将来可能返工那几颗。
|
|
30
30
|
|
|
@@ -37,11 +37,11 @@ return-v1 标准包由 `gtrk ai-drama lay` 确定性回填;其他平台没有
|
|
|
37
37
|
3. **中英双版地位平等,不是主辅。** ①视觉基调、②故事背景、③角色、④分镜四段每一段都给完整的中文版 + 英文版(不是把中文翻一句了事)。中文喂可灵 / 即梦 / Vidu / 通义,英文喂 Veo / Runway / LTX / Luma,用户按目标平台选。**唯独 ⑤ 原文文稿是中文口播原文、不翻译**(它是源真相 + 手动拼接的对位锚,翻译反而失真)。
|
|
38
38
|
4. **内容可移植,Markdown 外壳不可自由改写。** 默认产物 MUST 通过 `gitruck-ai-drama-desk` 的 Markdown 解析契约:标题必须含 `(beat BXX)`;两大块必须叫 `## 一、中文稿…` / `## 二、English Storyboard…`;角色必须用 `#### 角色名(备注)` 标题;中文镜头必须用 `#### 分镜 01 · …`,英文镜头必须用 `#### Shot 01 · …`;元信息必须含 `区间总时长:track_st … → track_ed … ≈ … 秒`。`镜头`、粗体行、项目符号角色、`## 中文` / `## English`、`工程时间位` 都不是等价写法。**人读友好不能牺牲机读契约。**
|
|
39
39
|
|
|
40
|
-
> **产物落点纪律(MUST · 全文见随包 `AGENT.md` 同名一节)**:
|
|
41
|
-
> 交付物 MUST NOT 先写 agent 自有目录再拷进工程;中转本身即违规,体积不是豁免理由。
|
|
40
|
+
> **产物落点纪律(MUST · 全文见随包 `AGENT.md` 同名一节)**:
|
|
41
|
+
> 交付物 MUST NOT 先写 agent 自有目录再拷进工程;中转本身即违规,体积不是豁免理由。
|
|
42
42
|
> 分镜稿只落**工程目录**(`<project>/ai-drama/<beat_id>.md`)或**用户显式指定处**;
|
|
43
43
|
> **MUST NOT** 把用户媒体(毛片 / 成片 / 预览 / 外部平台出的片段)或任何大媒体文件复制到 agent 自有工作目录(如用户文档目录下 agent 产品自建的目录、agent 家目录缓存、会话工作区)——需要引用媒体时**用原路径引用**,不做副本;
|
|
44
|
-
> 临时文件一律放系统 temp 且**用完即删**。交付物直接写入工程目录或用户显式指定的路径;引用媒体用原路径。
|
|
44
|
+
> 临时文件一律放系统 temp 且**用完即删**。交付物直接写入工程目录或用户显式指定的路径;引用媒体用原路径。
|
|
45
45
|
|
|
46
46
|
## 业务分离:栏目 Style Lock 从哪来(关键)
|
|
47
47
|
|
|
@@ -114,7 +114,7 @@ return-v1 标准包由 `gtrk ai-drama lay` 确定性回填;其他平台没有
|
|
|
114
114
|
|
|
115
115
|
**Step 4 · 写四段描述(中英分块)**:先出**中文稿**(② 故事背景末句锁调 → ③ 角色结构化 + 禁变项 + 质感落点,复现理论家 pin 复用同段文字 → ④ 分镜每镜前缀复述基调 + 建议秒数 + 对应原文句 + 蒙太奇级意图 → ⑤ 原文文稿中文逐句不翻译),再整块出 **English Storyboard**(① Style Lock + ②③④ 英文原生化,不含 ⑤)。中英**分两大块、不逐段交错**(craft C)。
|
|
116
116
|
|
|
117
|
-
**Step 5 · 校验后落盘**:先把每个 beat 的候选稿写到系统 temp,运行随 skill 分发的 `scripts/validate-storyboard.mjs <候选稿.md>`;退出码为 0 才写到 `<project>/ai-drama/<beat_id>.md`。失败就按输出逐项修正,MUST NOT 把未通过稿件落进工程。校验器不可用时,至少逐项执行下面「解析器硬契约」与交付前自检。用户直投单段时同样先校验,再落到用户指定的工程路径。临时稿无论成功失败都删掉。
|
|
117
|
+
**Step 5 · 校验后落盘**:先把每个 beat 的候选稿写到系统 temp,运行随 skill 分发的 `scripts/validate-storyboard.mjs <候选稿.md>`;退出码为 0 才写到 `<project>/ai-drama/<beat_id>.md`。失败就按输出逐项修正,MUST NOT 把未通过稿件落进工程。校验器不可用时,至少逐项执行下面「解析器硬契约」与交付前自检。用户直投单段时同样先校验,再落到用户指定的工程路径。临时稿无论成功失败都删掉。
|
|
118
118
|
|
|
119
119
|
## 输出契约(落盘 Markdown 结构 · 中英分块)
|
|
120
120
|
|
|
@@ -250,7 +250,7 @@ face / hair / eyes / wardrobe / signature prop + do-not-change + one line on how
|
|
|
250
250
|
|
|
251
251
|
命令自己解析 id → 导出包目录,走两条**确定性**路径:① 工作台服务可达就问它;② 服务没起就读 `~/.gitruck/ai-drama-desk.json` 的落脚点。两条都不通会报错并给出路。
|
|
252
252
|
|
|
253
|
-
> ⚠️ **拿到 id 之后 MUST NOT 去磁盘上找导出包**(`find` / `ls -R` / 通配一律禁止)。解析不出来就如实报,请用户启动工作台或直接给路径。
|
|
253
|
+
> ⚠️ **拿到 id 之后 MUST NOT 去磁盘上找导出包**(`find` / `ls -R` / 通配一律禁止)。解析不出来就如实报,请用户启动工作台或直接给路径。
|
|
254
254
|
> 零命中时命令会列出「已查过哪些数据根」:源码部署与打包部署的数据根不同,项目找不到多半是**它在另一个部署里**,不是丢了。
|
|
255
255
|
|
|
256
256
|
已经手上就有目录路径(或用的是非标准平台)时,仍可用 `--package "<exports/aidrama>"`,两者可混用、按路径去重。
|
|
@@ -258,7 +258,7 @@ face / hair / eyes / wardrobe / signature prop + do-not-change + one line on how
|
|
|
258
258
|
命令会先按当刻口播工程重投影 beat 窗口,再把所有镜头复制到 `<gtrk目录>/assets/ai-drama/<slug>/`,共用一条新的 `video_track`;每个 beat 从起点顺排,最后一镜吸收到 beat 终点。既有 A-roll、BGM 与 B-roll 轨零改动。若已铺 AI 轨被用户手调过,缺省拒绝覆盖;只有用户明确同意丢弃手调时才加 `--replace-all`。
|
|
259
259
|
3. **非标准平台才手动回铺**:外部平台没有 return-v1 清单时,才由用户把片段拖回 opencut 的 **AI_DRAMA 车道**,对齐该 beat 的 `track_st→track_ed`。AI 出片仍是外部异步、用户驱动;未拿到完整片段前要停下等用户,别替他假装出片 / 回轨完成。
|
|
260
260
|
4. **回铺齐 ≠ 收口,还有两步**:你的片段回铺齐只意味着 **③ B-roll 底轨阶段**完成。接着 MUST 走 **④ 全局抽帧检查画面构图**(对三源合并后的最终底轨抽帧,看主体位置 / 安全区 / 画面朝向 / 明暗,**停下等用户确认**)→ 过了才交棒 **⑤ `/gtrk-mg`** 把 MG(含 ov)叠上 → 最后才 `gtrk render` 收口。
|
|
261
|
-
⚠️ **MUST NOT 从你这里直接跳到 `render`**;先完成全局构图检查与 MG,再进入字幕和渲染。
|
|
261
|
+
⚠️ **MUST NOT 从你这里直接跳到 `render`**;先完成全局构图检查与 MG,再进入字幕和渲染。
|
|
262
262
|
|
|
263
263
|
> 原则:**agent 替用户跑 CLI / 接力 skill,用户只对话**。但 AI_DRAMA 车道天然有「用户去外部平台出片 + 手动拼回」的手工环节——那部分是外部的手,无法替跑;除此之外的解析、生成、落盘、交代,全由你(脑)一次做完,别让用户自己去拼描述。
|
|
264
264
|
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
# 稀疏关键场景与角色参考
|
|
2
|
-
|
|
3
|
-
用户要求“隔一段挑关键场景、其余自己补实况”时使用。它不是通篇 AI 成片图纸,不套用零缝、片头铺满或自动封缝规则。
|
|
4
|
-
|
|
5
|
-
## 选镜与时基
|
|
6
|
-
|
|
7
|
-
先通读正文及上下文,再按叙事转折选场景。间隔是密度目标,不是机械切秒。每镜登记原句、源音频起点、母带起点、预计画面时长、人物、年龄阶段、场景与关键道具。母带含 OP/ED 时显式登记正文范围与 offset;用对应音频片段核对,不能把正文从零开始的 ASR 时码直接当母带时码。
|
|
8
|
-
|
|
9
|
-
一处独立关键场景对应一个短 beat 窗口,窗口只覆盖这条片段的计划时长;不要把直到下一处场景的整个间隔当窗口让末镜吃满。CLI beat 使用 B 开头数字编号;其他展示编号应显式映射,不私改接口。其余时间留给实况,禁止自动 seal、循环或拉伸填满。用户指定只做正文时,OP/ED 不派视觉生成。
|
|
10
|
-
|
|
11
|
-
## 原作资产与逐镜判断
|
|
12
|
-
|
|
13
|
-
- 从叙事语义识别人物,不能靠“他、父亲、刀”等关键词自动加入主角。角色随时间改变时分别绑定年龄、发型、服装参考。
|
|
14
|
-
- 实际查看参考图。只带本镜需要的角色;物证或空镜不要夹带多人图作为角色参考。原作 CG 可以锁角色/画风,但画中事件不等于当前镜头事件。
|
|
15
|
-
- 参考路径逐镜登记,禁止按镜号取模轮换 CG。用户指定宿主 Image2 时使用该能力,不擅自换成本地生图或云模型。
|
|
16
|
-
- 图片提示词描述一个明确瞬间;I2V 提示词描述该瞬间里有剧情含义的小动作,以及须保持稳定的脸、手、道具与空间。实际时长、尺寸、帧率以模型配置及成品探测为准,不硬编码样片参数到全部项目。
|
|
17
|
-
|
|
18
|
-
## 分阶段审阅
|
|
19
|
-
|
|
20
|
-
已生成、已选中、已审阅、已回轨是不同状态。逐镜看实际输出,记录人物与年龄、场景关系、道具比例、手部接触和微动可行性。审阅记录绑定具体文件与内容哈希;替换图片、分镜或人物设定后重新审阅,不能整批写 accepted 代替目视检查。工具可以强制记录完整,却不能证明记录内容真实。
|
|
21
|
-
|
|
22
|
-
外部图片通过工作台的 keyframe import 接口进入候选池;角色 refs 上传不是关键帧导入。具体命令见工作台 skill。视频检查至少看首、中、尾及运动过程;仅看抽帧要如实说明,不写成逐帧播放验收。
|
|
23
|
-
|
|
24
|
-
## 分批回轨
|
|
25
|
-
|
|
26
|
-
首次按现有 pack/lay 契约回轨;新增 beat 使用:
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
gtrk ai-drama lay --project <工程> --package <新包> --append --json
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
`--append` 为本次新包新增独立轨,逐对象保留已有轨、素材与手调;拒绝重复 beat,不与 `--replace-all` 同用。需替换已有镜头时,等时长/尺寸/帧率素材用 `swap`;改变时序须重新设计并用 patch 编辑,不以“备份后重铺再猜着恢复”冒充保留手调。媒体由增量路径按 beat 和内容哈希隔离。
|
|
33
|
-
|
|
34
|
-
执行前查看当前 CLI 的 `ai-drama --help` 是否已有 `--append`。skill 更新不等于已安装二进制更新;旧版本缺参数时使用已核实仓库的源码入口或完成构建,不退回覆盖手调的项目脚本。
|
|
35
|
-
|
|
36
|
-
阅读 lay 回执的 reprojection:降级使用派单快照时核对它是否属于当前母带。doctor 检查结构与文件存在,不能代替语义审阅;稀疏轨的空档警告不自动触发封缝。
|
|
37
|
-
|
|
38
|
-
交付明确计划数、已生成数、已审阅数和已回轨数。用户说先看样片时只交付约定样片;其他镜头保持未完成状态,不因有场景表就宣称批量完成。
|
|
1
|
+
# 稀疏关键场景与角色参考
|
|
2
|
+
|
|
3
|
+
用户要求“隔一段挑关键场景、其余自己补实况”时使用。它不是通篇 AI 成片图纸,不套用零缝、片头铺满或自动封缝规则。
|
|
4
|
+
|
|
5
|
+
## 选镜与时基
|
|
6
|
+
|
|
7
|
+
先通读正文及上下文,再按叙事转折选场景。间隔是密度目标,不是机械切秒。每镜登记原句、源音频起点、母带起点、预计画面时长、人物、年龄阶段、场景与关键道具。母带含 OP/ED 时显式登记正文范围与 offset;用对应音频片段核对,不能把正文从零开始的 ASR 时码直接当母带时码。
|
|
8
|
+
|
|
9
|
+
一处独立关键场景对应一个短 beat 窗口,窗口只覆盖这条片段的计划时长;不要把直到下一处场景的整个间隔当窗口让末镜吃满。CLI beat 使用 B 开头数字编号;其他展示编号应显式映射,不私改接口。其余时间留给实况,禁止自动 seal、循环或拉伸填满。用户指定只做正文时,OP/ED 不派视觉生成。
|
|
10
|
+
|
|
11
|
+
## 原作资产与逐镜判断
|
|
12
|
+
|
|
13
|
+
- 从叙事语义识别人物,不能靠“他、父亲、刀”等关键词自动加入主角。角色随时间改变时分别绑定年龄、发型、服装参考。
|
|
14
|
+
- 实际查看参考图。只带本镜需要的角色;物证或空镜不要夹带多人图作为角色参考。原作 CG 可以锁角色/画风,但画中事件不等于当前镜头事件。
|
|
15
|
+
- 参考路径逐镜登记,禁止按镜号取模轮换 CG。用户指定宿主 Image2 时使用该能力,不擅自换成本地生图或云模型。
|
|
16
|
+
- 图片提示词描述一个明确瞬间;I2V 提示词描述该瞬间里有剧情含义的小动作,以及须保持稳定的脸、手、道具与空间。实际时长、尺寸、帧率以模型配置及成品探测为准,不硬编码样片参数到全部项目。
|
|
17
|
+
|
|
18
|
+
## 分阶段审阅
|
|
19
|
+
|
|
20
|
+
已生成、已选中、已审阅、已回轨是不同状态。逐镜看实际输出,记录人物与年龄、场景关系、道具比例、手部接触和微动可行性。审阅记录绑定具体文件与内容哈希;替换图片、分镜或人物设定后重新审阅,不能整批写 accepted 代替目视检查。工具可以强制记录完整,却不能证明记录内容真实。
|
|
21
|
+
|
|
22
|
+
外部图片通过工作台的 keyframe import 接口进入候选池;角色 refs 上传不是关键帧导入。具体命令见工作台 skill。视频检查至少看首、中、尾及运动过程;仅看抽帧要如实说明,不写成逐帧播放验收。
|
|
23
|
+
|
|
24
|
+
## 分批回轨
|
|
25
|
+
|
|
26
|
+
首次按现有 pack/lay 契约回轨;新增 beat 使用:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
gtrk ai-drama lay --project <工程> --package <新包> --append --json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`--append` 为本次新包新增独立轨,逐对象保留已有轨、素材与手调;拒绝重复 beat,不与 `--replace-all` 同用。需替换已有镜头时,等时长/尺寸/帧率素材用 `swap`;改变时序须重新设计并用 patch 编辑,不以“备份后重铺再猜着恢复”冒充保留手调。媒体由增量路径按 beat 和内容哈希隔离。
|
|
33
|
+
|
|
34
|
+
执行前查看当前 CLI 的 `ai-drama --help` 是否已有 `--append`。skill 更新不等于已安装二进制更新;旧版本缺参数时使用已核实仓库的源码入口或完成构建,不退回覆盖手调的项目脚本。
|
|
35
|
+
|
|
36
|
+
阅读 lay 回执的 reprojection:降级使用派单快照时核对它是否属于当前母带。doctor 检查结构与文件存在,不能代替语义审阅;稀疏轨的空档警告不自动触发封缝。
|
|
37
|
+
|
|
38
|
+
交付明确计划数、已生成数、已审阅数和已回轨数。用户说先看样片时只交付约定样片;其他镜头保持未完成状态,不因有场景表就宣称批量完成。
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { basename } from "node:path";
|
|
5
|
-
|
|
6
|
-
const files = process.argv.slice(2);
|
|
7
|
-
if (files.length === 0) {
|
|
8
|
-
console.error("用法:bun scripts/validate-storyboard.mjs <分镜稿.md> [...]");
|
|
9
|
-
process.exit(2);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function firstMatchIndex(text, re) {
|
|
13
|
-
const m = re.exec(text);
|
|
14
|
-
return m ? m.index : -1;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function validate(md, file) {
|
|
18
|
-
const errors = [];
|
|
19
|
-
const beat = md.match(/[((]\s*beat\s+([A-Za-z0-9_-]+)\s*[))]/i)?.[1] ?? "";
|
|
20
|
-
if (!beat) errors.push("H1 标题缺少「(beat BXX)」");
|
|
21
|
-
|
|
22
|
-
const meta = md.match(/^[-*]\s*区间总时长\s*[::]\s*track_st\s+(-?\d+(?:\.\d+)?)\s*→\s*track_ed\s+(-?\d+(?:\.\d+)?)\s*≈\s*(\d+(?:\.\d+)?)\s*秒\s*$/m);
|
|
23
|
-
if (!meta) errors.push("缺少单行元信息「区间总时长:track_st … → track_ed … ≈ … 秒」");
|
|
24
|
-
|
|
25
|
-
const cnStart = firstMatchIndex(md, /^##\s*一、中文稿.*$/m);
|
|
26
|
-
const enStart = firstMatchIndex(md, /^##\s*二、English Storyboard.*$/mi);
|
|
27
|
-
if (cnStart < 0) errors.push("缺少「## 一、中文稿…」区块");
|
|
28
|
-
if (enStart < 0) errors.push("缺少「## 二、English Storyboard…」区块");
|
|
29
|
-
if (cnStart >= 0 && enStart >= 0 && cnStart >= enStart) errors.push("中文稿必须位于 English Storyboard 之前");
|
|
30
|
-
|
|
31
|
-
const cn = cnStart >= 0 ? md.slice(cnStart, enStart >= 0 ? enStart : md.length) : "";
|
|
32
|
-
const en = enStart >= 0 ? md.slice(enStart) : "";
|
|
33
|
-
for (const n of ["①", "②", "③", "④", "⑤"]) {
|
|
34
|
-
if (!new RegExp(`^#{1,6}\\s*${n}`, "m").test(cn)) errors.push(`中文稿缺少 ${n} 区块`);
|
|
35
|
-
}
|
|
36
|
-
for (const n of ["①", "②", "③", "④"]) {
|
|
37
|
-
if (!new RegExp(`^#{1,6}\\s*${n}`, "m").test(en)) errors.push(`English Storyboard 缺少 ${n} 区块`);
|
|
38
|
-
}
|
|
39
|
-
if (!/^>\s*禁忌\s*[::]/m.test(cn)) errors.push("① 视觉基调缺少独立的「> 禁忌:…」行");
|
|
40
|
-
if (!/^>\s*Avoid\s*[::]/mi.test(en)) errors.push("English ① Style Lock 缺少独立的「> Avoid: …」行");
|
|
41
|
-
|
|
42
|
-
const cnCharBlock = cn.match(/^#{1,6}\s*③[^\n]*\n([\s\S]*?)(?=^#{1,6}\s*④)/m)?.[1] ?? "";
|
|
43
|
-
const enCharBlock = en.match(/^#{1,6}\s*③[^\n]*\n([\s\S]*?)(?=^#{1,6}\s*④)/m)?.[1] ?? "";
|
|
44
|
-
const cnChars = [...cnCharBlock.matchAll(/^####\s+(.+?)\s*$/gm)].map((m) => m[1].trim());
|
|
45
|
-
const enChars = [...enCharBlock.matchAll(/^####\s+(.+?)\s*$/gm)].map((m) => m[1].trim());
|
|
46
|
-
if (cnChars.length === 0) errors.push("③ 角色描述中没有「#### 角色名(备注)」标题");
|
|
47
|
-
if (enChars.length === 0) errors.push("English ③ Characters 中没有角色四级标题");
|
|
48
|
-
|
|
49
|
-
const cnShotRe = /^####\s*分镜\s*(\d+)\s*[·•..]\s*(.*?)\s*[||]\s*建议\s*≈?\s*(\d+(?:\.\d+)?)\s*s\b.*[||]\s*角色\s*[::]\s*([^||\n]+)/gim;
|
|
50
|
-
const enShotRe = /^####\s*Shot\s*(\d+)\s*[·•..]\s*(.*?)\s*[||]\s*≈?\s*(\d+(?:\.\d+)?)\s*s\b.*[||]\s*cast\s*[::]\s*([^||\n]+)/gim;
|
|
51
|
-
const cnShots = [...cn.matchAll(cnShotRe)].map((m) => Number(m[1]));
|
|
52
|
-
const enShots = [...en.matchAll(enShotRe)].map((m) => Number(m[1]));
|
|
53
|
-
if (cnShots.length === 0) errors.push("④ 中没有可解析的「#### 分镜 01 · … |建议 ≈Xs … |角色:…」标题");
|
|
54
|
-
if (enShots.length === 0) errors.push("English ④ 中没有可解析的「#### Shot 01 · … |≈Xs … |cast: …」标题");
|
|
55
|
-
if (cnShots.length > 0 && enShots.length > 0 && JSON.stringify(cnShots) !== JSON.stringify(enShots)) {
|
|
56
|
-
errors.push(`中英文镜头序号不一致:CN=${cnShots.join(",")},EN=${enShots.join(",")}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const cnShotBlock = cn.match(/^#{1,6}\s*④[^\n]*\n([\s\S]*?)(?=^#{1,6}\s*⑤)/m)?.[1] ?? "";
|
|
60
|
-
const enShotBlock = en.match(/^#{1,6}\s*④[^\n]*\n([\s\S]*)/m)?.[1] ?? "";
|
|
61
|
-
const cnHeads = [...cnShotBlock.matchAll(/^####\s+(.+?)\s*$/gm)];
|
|
62
|
-
const enHeads = [...enShotBlock.matchAll(/^####\s+(.+?)\s*$/gm)];
|
|
63
|
-
const extraCnHeads = cnHeads.filter((m) => !/^分镜\s*\d+/i.test(m[1]));
|
|
64
|
-
const extraEnHeads = enHeads.filter((m) => !/^Shot\s*\d+/i.test(m[1]));
|
|
65
|
-
if (extraCnHeads.length > 0) errors.push(`④ 内存在非分镜四级标题:${extraCnHeads.map((m) => m[1]).join("、")};请把蒙太奇名放进「段」字段`);
|
|
66
|
-
if (extraEnHeads.length > 0) errors.push(`English ④ 内存在非 Shot 四级标题:${extraEnHeads.map((m) => m[1]).join("、")};请把 montage 名放进 segment 字段`);
|
|
67
|
-
|
|
68
|
-
function checkPrefixes(block, heads, label) {
|
|
69
|
-
for (let i = 0; i < heads.length; i += 1) {
|
|
70
|
-
const start = (heads[i].index ?? 0) + heads[i][0].length;
|
|
71
|
-
const end = i + 1 < heads.length ? heads[i + 1].index : block.length;
|
|
72
|
-
const firstBodyLine = block.slice(start, end).split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
|
73
|
-
if (!firstBodyLine.startsWith("〔") || !firstBodyLine.includes("〕")) {
|
|
74
|
-
errors.push(`${label} ${heads[i][1].match(/\d+/)?.[0] ?? "?"} 的首个正文不是「〔视觉基调〕」前缀`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
checkPrefixes(cnShotBlock, cnHeads.filter((m) => /^分镜\s*\d+/i.test(m[1])), "中文分镜");
|
|
79
|
-
checkPrefixes(enShotBlock, enHeads.filter((m) => /^Shot\s*\d+/i.test(m[1])), "English Shot");
|
|
80
|
-
|
|
81
|
-
if (/^\s*[-*]\s*(男性|女性|男主|女主|父亲|母亲|角色)\s*[::]/m.test(cnCharBlock)) {
|
|
82
|
-
errors.push("③ 角色仍使用项目符号;请改成四级标题并把描述另起段落");
|
|
83
|
-
}
|
|
84
|
-
if (/^\s*\*\*(镜头|分镜|Shot)\s*\d+/mi.test(md)) {
|
|
85
|
-
errors.push("镜头仍使用粗体行;请改成「#### 分镜…」/「#### Shot…」四级标题");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (meta) {
|
|
89
|
-
const st = Number(meta[1]);
|
|
90
|
-
const ed = Number(meta[2]);
|
|
91
|
-
const total = Number(meta[3]);
|
|
92
|
-
if (!(ed > st)) errors.push("track_ed 必须大于 track_st");
|
|
93
|
-
if (Math.abs((ed - st) - total) > 0.11) errors.push(`区间总时长与 track_ed-track_st 不一致:${total} vs ${(ed - st).toFixed(3)}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
ok: errors.length === 0,
|
|
98
|
-
file: basename(file),
|
|
99
|
-
beatId: beat || null,
|
|
100
|
-
characters: cnChars.length,
|
|
101
|
-
shots: cnShots.length,
|
|
102
|
-
errors,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
let failed = false;
|
|
107
|
-
for (const file of files) {
|
|
108
|
-
try {
|
|
109
|
-
const md = await readFile(file, "utf8");
|
|
110
|
-
const result = validate(md, file);
|
|
111
|
-
console.log(JSON.stringify(result));
|
|
112
|
-
failed ||= !result.ok;
|
|
113
|
-
} catch (error) {
|
|
114
|
-
failed = true;
|
|
115
|
-
console.log(JSON.stringify({ ok: false, file: basename(file), errors: [String(error?.message ?? error)] }));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
process.exit(failed ? 1 : 0);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { basename } from "node:path";
|
|
5
|
+
|
|
6
|
+
const files = process.argv.slice(2);
|
|
7
|
+
if (files.length === 0) {
|
|
8
|
+
console.error("用法:bun scripts/validate-storyboard.mjs <分镜稿.md> [...]");
|
|
9
|
+
process.exit(2);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function firstMatchIndex(text, re) {
|
|
13
|
+
const m = re.exec(text);
|
|
14
|
+
return m ? m.index : -1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function validate(md, file) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
const beat = md.match(/[((]\s*beat\s+([A-Za-z0-9_-]+)\s*[))]/i)?.[1] ?? "";
|
|
20
|
+
if (!beat) errors.push("H1 标题缺少「(beat BXX)」");
|
|
21
|
+
|
|
22
|
+
const meta = md.match(/^[-*]\s*区间总时长\s*[::]\s*track_st\s+(-?\d+(?:\.\d+)?)\s*→\s*track_ed\s+(-?\d+(?:\.\d+)?)\s*≈\s*(\d+(?:\.\d+)?)\s*秒\s*$/m);
|
|
23
|
+
if (!meta) errors.push("缺少单行元信息「区间总时长:track_st … → track_ed … ≈ … 秒」");
|
|
24
|
+
|
|
25
|
+
const cnStart = firstMatchIndex(md, /^##\s*一、中文稿.*$/m);
|
|
26
|
+
const enStart = firstMatchIndex(md, /^##\s*二、English Storyboard.*$/mi);
|
|
27
|
+
if (cnStart < 0) errors.push("缺少「## 一、中文稿…」区块");
|
|
28
|
+
if (enStart < 0) errors.push("缺少「## 二、English Storyboard…」区块");
|
|
29
|
+
if (cnStart >= 0 && enStart >= 0 && cnStart >= enStart) errors.push("中文稿必须位于 English Storyboard 之前");
|
|
30
|
+
|
|
31
|
+
const cn = cnStart >= 0 ? md.slice(cnStart, enStart >= 0 ? enStart : md.length) : "";
|
|
32
|
+
const en = enStart >= 0 ? md.slice(enStart) : "";
|
|
33
|
+
for (const n of ["①", "②", "③", "④", "⑤"]) {
|
|
34
|
+
if (!new RegExp(`^#{1,6}\\s*${n}`, "m").test(cn)) errors.push(`中文稿缺少 ${n} 区块`);
|
|
35
|
+
}
|
|
36
|
+
for (const n of ["①", "②", "③", "④"]) {
|
|
37
|
+
if (!new RegExp(`^#{1,6}\\s*${n}`, "m").test(en)) errors.push(`English Storyboard 缺少 ${n} 区块`);
|
|
38
|
+
}
|
|
39
|
+
if (!/^>\s*禁忌\s*[::]/m.test(cn)) errors.push("① 视觉基调缺少独立的「> 禁忌:…」行");
|
|
40
|
+
if (!/^>\s*Avoid\s*[::]/mi.test(en)) errors.push("English ① Style Lock 缺少独立的「> Avoid: …」行");
|
|
41
|
+
|
|
42
|
+
const cnCharBlock = cn.match(/^#{1,6}\s*③[^\n]*\n([\s\S]*?)(?=^#{1,6}\s*④)/m)?.[1] ?? "";
|
|
43
|
+
const enCharBlock = en.match(/^#{1,6}\s*③[^\n]*\n([\s\S]*?)(?=^#{1,6}\s*④)/m)?.[1] ?? "";
|
|
44
|
+
const cnChars = [...cnCharBlock.matchAll(/^####\s+(.+?)\s*$/gm)].map((m) => m[1].trim());
|
|
45
|
+
const enChars = [...enCharBlock.matchAll(/^####\s+(.+?)\s*$/gm)].map((m) => m[1].trim());
|
|
46
|
+
if (cnChars.length === 0) errors.push("③ 角色描述中没有「#### 角色名(备注)」标题");
|
|
47
|
+
if (enChars.length === 0) errors.push("English ③ Characters 中没有角色四级标题");
|
|
48
|
+
|
|
49
|
+
const cnShotRe = /^####\s*分镜\s*(\d+)\s*[·•..]\s*(.*?)\s*[||]\s*建议\s*≈?\s*(\d+(?:\.\d+)?)\s*s\b.*[||]\s*角色\s*[::]\s*([^||\n]+)/gim;
|
|
50
|
+
const enShotRe = /^####\s*Shot\s*(\d+)\s*[·•..]\s*(.*?)\s*[||]\s*≈?\s*(\d+(?:\.\d+)?)\s*s\b.*[||]\s*cast\s*[::]\s*([^||\n]+)/gim;
|
|
51
|
+
const cnShots = [...cn.matchAll(cnShotRe)].map((m) => Number(m[1]));
|
|
52
|
+
const enShots = [...en.matchAll(enShotRe)].map((m) => Number(m[1]));
|
|
53
|
+
if (cnShots.length === 0) errors.push("④ 中没有可解析的「#### 分镜 01 · … |建议 ≈Xs … |角色:…」标题");
|
|
54
|
+
if (enShots.length === 0) errors.push("English ④ 中没有可解析的「#### Shot 01 · … |≈Xs … |cast: …」标题");
|
|
55
|
+
if (cnShots.length > 0 && enShots.length > 0 && JSON.stringify(cnShots) !== JSON.stringify(enShots)) {
|
|
56
|
+
errors.push(`中英文镜头序号不一致:CN=${cnShots.join(",")},EN=${enShots.join(",")}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cnShotBlock = cn.match(/^#{1,6}\s*④[^\n]*\n([\s\S]*?)(?=^#{1,6}\s*⑤)/m)?.[1] ?? "";
|
|
60
|
+
const enShotBlock = en.match(/^#{1,6}\s*④[^\n]*\n([\s\S]*)/m)?.[1] ?? "";
|
|
61
|
+
const cnHeads = [...cnShotBlock.matchAll(/^####\s+(.+?)\s*$/gm)];
|
|
62
|
+
const enHeads = [...enShotBlock.matchAll(/^####\s+(.+?)\s*$/gm)];
|
|
63
|
+
const extraCnHeads = cnHeads.filter((m) => !/^分镜\s*\d+/i.test(m[1]));
|
|
64
|
+
const extraEnHeads = enHeads.filter((m) => !/^Shot\s*\d+/i.test(m[1]));
|
|
65
|
+
if (extraCnHeads.length > 0) errors.push(`④ 内存在非分镜四级标题:${extraCnHeads.map((m) => m[1]).join("、")};请把蒙太奇名放进「段」字段`);
|
|
66
|
+
if (extraEnHeads.length > 0) errors.push(`English ④ 内存在非 Shot 四级标题:${extraEnHeads.map((m) => m[1]).join("、")};请把 montage 名放进 segment 字段`);
|
|
67
|
+
|
|
68
|
+
function checkPrefixes(block, heads, label) {
|
|
69
|
+
for (let i = 0; i < heads.length; i += 1) {
|
|
70
|
+
const start = (heads[i].index ?? 0) + heads[i][0].length;
|
|
71
|
+
const end = i + 1 < heads.length ? heads[i + 1].index : block.length;
|
|
72
|
+
const firstBodyLine = block.slice(start, end).split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
|
73
|
+
if (!firstBodyLine.startsWith("〔") || !firstBodyLine.includes("〕")) {
|
|
74
|
+
errors.push(`${label} ${heads[i][1].match(/\d+/)?.[0] ?? "?"} 的首个正文不是「〔视觉基调〕」前缀`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
checkPrefixes(cnShotBlock, cnHeads.filter((m) => /^分镜\s*\d+/i.test(m[1])), "中文分镜");
|
|
79
|
+
checkPrefixes(enShotBlock, enHeads.filter((m) => /^Shot\s*\d+/i.test(m[1])), "English Shot");
|
|
80
|
+
|
|
81
|
+
if (/^\s*[-*]\s*(男性|女性|男主|女主|父亲|母亲|角色)\s*[::]/m.test(cnCharBlock)) {
|
|
82
|
+
errors.push("③ 角色仍使用项目符号;请改成四级标题并把描述另起段落");
|
|
83
|
+
}
|
|
84
|
+
if (/^\s*\*\*(镜头|分镜|Shot)\s*\d+/mi.test(md)) {
|
|
85
|
+
errors.push("镜头仍使用粗体行;请改成「#### 分镜…」/「#### Shot…」四级标题");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (meta) {
|
|
89
|
+
const st = Number(meta[1]);
|
|
90
|
+
const ed = Number(meta[2]);
|
|
91
|
+
const total = Number(meta[3]);
|
|
92
|
+
if (!(ed > st)) errors.push("track_ed 必须大于 track_st");
|
|
93
|
+
if (Math.abs((ed - st) - total) > 0.11) errors.push(`区间总时长与 track_ed-track_st 不一致:${total} vs ${(ed - st).toFixed(3)}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
ok: errors.length === 0,
|
|
98
|
+
file: basename(file),
|
|
99
|
+
beatId: beat || null,
|
|
100
|
+
characters: cnChars.length,
|
|
101
|
+
shots: cnShots.length,
|
|
102
|
+
errors,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let failed = false;
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
try {
|
|
109
|
+
const md = await readFile(file, "utf8");
|
|
110
|
+
const result = validate(md, file);
|
|
111
|
+
console.log(JSON.stringify(result));
|
|
112
|
+
failed ||= !result.ok;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
failed = true;
|
|
115
|
+
console.log(JSON.stringify({ ok: false, file: basename(file), errors: [String(error?.message ?? error)] }));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
process.exit(failed ? 1 : 0);
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gtrk-composite
|
|
3
|
+
description: 合成层包装(合成颗粒)——粗剪、B-roll、字幕都定了之后,把选定时间窗做成「合成」:一段时间内的整个画面由一颗代码颗粒接管(画中画拼贴、逐句换画面、对照并排、并列卡片、字幕排版),讲者与 B-roll 作为槽位画进去。三种入口:整片铺满 / 让 AI 判定哪里值得做 / 用户指定区间;候选窗口显示在客户端大纲的 CP 行。当用户想「做合成 / 合成层 / 整片做一下合成包装 / 哪里适合做合成 / 这段做成画中画拼贴 / 11 秒到 32 秒做个合成 / 逐句换画面 / 把字幕排进画面里」时使用本 skill,驱动 `gtrk composite`(plan → review → scaffold → build → lint → lay → bake),别手搓 HTML 铺进工程。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 合成层包装(gtrk-composite)
|
|
7
|
+
|
|
8
|
+
把粗剪工程里选定的时间窗做成**合成颗粒**:在这段时间里,整个画面由一颗代码颗粒接管——它决定画面怎么排、何时换、字幕怎么出;讲者与 B-roll 作为**槽位**由客户端把视频画进去。**CLI 是手(定窗口、解析、校验、机检、铺轨),你是脑(判断这段该怎么排、选哪张画面、写场景)。**
|
|
9
|
+
|
|
10
|
+
> 本 skill 已含你需要的全部信息。命令参数以 `gtrk composite --help` 为准。规则正本:合成颗粒契约在 infra `composite-particle-profile`;`struct_meta.composite` 写方契约在本仓 `composite-command`。
|
|
11
|
+
|
|
12
|
+
## 你在成片 SOP 的哪一步
|
|
13
|
+
|
|
14
|
+
最后一层:**粗剪 → 拆分 → B-roll → MG → BGM → 字幕 → 合成 → 出片**。放在最后,是因为这时粗剪、B-roll、字幕都已定,合成颗粒的输入最稳定。但不写死:上游再改,对应颗粒在客户端变「需重渲」,重跑那几颗即可。
|
|
15
|
+
|
|
16
|
+
前置:
|
|
17
|
+
- 有粗剪工程(`<工程>/gtrk/project.gtrk`)和口播转写(`transcript/transcript.json`,新版 `gtrk oralcut` 恒出)。合成按**句**定窗口,没有转写就做不了。
|
|
18
|
+
- 字幕最好已上(`gtrk subtitle lay`):合成颗粒可以接管字幕、自己排。
|
|
19
|
+
- `gtrk composite --help` 能跑(CLI 版本含本命令族)。
|
|
20
|
+
|
|
21
|
+
⚠️ **出片口径要先说清**:合成颗粒出片靠本机烘焙(`gtrk composite bake`,见「出片:烘焙」一节)——首次要装烘焙浏览器(约 110 MB),某颗烘焙失败时那一段按粗剪出画。用户要交付成片的话,这件事 MUST 在开工时告诉他。
|
|
22
|
+
|
|
23
|
+
## 入口:三选一(用户已说明就不再问)
|
|
24
|
+
|
|
25
|
+
| 用户说 | 入口 | 命令 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| 「整片做一下合成」 | 整片铺满 | `gtrk composite plan --project <工程> --mode full` |
|
|
28
|
+
| 「看看哪里适合做」「帮我挑」 | AI 判定 | `gtrk composite plan --project <工程> --mode auto [--density 0.25]` |
|
|
29
|
+
| 「11 秒到 32 秒做一个」「u0004 到 u0009 那段」 | 用户指定 | `gtrk composite plan --project <工程> --range 11,32 --range u0020-u0024` |
|
|
30
|
+
|
|
31
|
+
- **整片铺满**:窗口首尾相接覆盖全片,单窗 5–25 秒、在句界(有拆分派单时在 beat 边界)断开。
|
|
32
|
+
- **AI 判定**:按信号打分(几秒内多个 B-roll 短镜头、列举、对照 / 转折、具体数字、章节转折、开场 / 收尾),总时长不超过密度上限(缺省全片 25%)。超出的记「超出上限」,**列出来让用户挑,不静默丢**。信号只是起点——你要读审稿稿里每个窗口「这段在说什么」,判断它是否真值得做,不值得的建议用户不要。
|
|
33
|
+
- **用户指定**:区间吸附到句界、投影到帧格,回执里写明两端各挪了多少毫秒;直接记为已采纳。
|
|
34
|
+
|
|
35
|
+
`plan` 把候选写进工程的 `struct_meta.composite`(只动这一个键),另出审稿稿 `<工程>/composite/plan.md`。重跑时已做好的窗口原样保留、用户不要的区间不再提。
|
|
36
|
+
|
|
37
|
+
### 检查点①(必停):审窗口
|
|
38
|
+
|
|
39
|
+
请用户在客户端**重新载入工程**后看大纲的 **CP 行**:虚线框 = 待审,虚线框加浅底 = 已采纳,悬停能看理由与建议。同时把审稿稿的表格摘给他。他在对话里说哪些要、哪些不要、哪些调起止,你用 `review` 写回:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
gtrk composite review --project <工程> --approve W01,W03 --reject W02 --adjust W04=80,90.9 --add 140,150
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`--approve-all` 采纳全部待审。没有采纳的窗口,后面的步骤一概不做。
|
|
46
|
+
|
|
47
|
+
## 逐窗口:设计说明 → 选片 → 场景 → 构建 → 机检
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
gtrk composite scaffold --project <工程> # 为已采纳窗口落场景骨架 composite/scenes/cp-wNN.html
|
|
51
|
+
# 与上下文 cp-wNN.context.md(这段在说什么、窗口内片段、已登记素材)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
对每个窗口:
|
|
55
|
+
|
|
56
|
+
1. **先读上下文、写设计说明**(写进对话给用户看,或写在场景文件开头的注释里):
|
|
57
|
+
- 这段话分几拍?每拍对应哪句、哪个关键词?
|
|
58
|
+
- 每拍画面是什么?为什么是一画 / 多画?(见下节规则 1)
|
|
59
|
+
- 字幕怎么处理(接管后自己排 / 屏上已写着同一句时隐藏)。
|
|
60
|
+
2. **选片**(需要 `own` 槽位时):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
gtrk composite shots --project <工程> --material <素材 id 或文件> --around 246 --span 20
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
看联系表(每格左上是源时间码)、切点列表、黑边实测。入点与在场时长 MUST 落在一个镜头内(规则 5),`data-crop` 按黑边实测取(规则 3)。工程里还没登记的素材写进 `<工程>/composite/materials.json`(`[{ "id", "path", "duration" }]`),铺轨时一并登记。
|
|
67
|
+
3. **写场景**(格式见「场景写法速查」)。
|
|
68
|
+
4. **构建**:`gtrk composite build --project <工程> [--only cp-w03]`——按 infra 规则解析(帧格窗口、轨身份、锚点、字幕输入、`inputs_hash`)并校验,产 `<工程>/gtrk/assets/composite/<id>.html`。回执里每颗应为 `fresh`。
|
|
69
|
+
5. **机检**:`gtrk composite lint --project <工程> --stills 26,86.6`——静态校验 + 逐帧排版(下层画面的前景描边压上层画面、前景线条穿过画面、画面越出舞台),顺带出静帧。**不过就改场景重跑,过了才往下走。**
|
|
70
|
+
- 退出码 3 = 本机找不到 Chrome / Edge,**排版没机检**:照实告诉用户,用静帧(或客户端预览)人眼逐颗查穿模,MUST NOT 当成通过。
|
|
71
|
+
|
|
72
|
+
### 检查点②:看静帧
|
|
73
|
+
|
|
74
|
+
每颗至少给用户 2–3 张关键时刻的静帧(换画面前后、拆分并排时、结尾)。快速成片模式下全部窗口做完后一次给。用户点头再铺:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
gtrk composite lay --project <工程> # 全量;--only cp-w03 只替换这颗
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
铺完请用户重新载入工程:CP 行上的提案块变成实色合成块,预览即合成效果。
|
|
81
|
+
|
|
82
|
+
## 出片:烘焙
|
|
83
|
+
|
|
84
|
+
铺完、用户确认后、出片前,先在本机把合成烤成视频(`gtrk render` / `gtrk project export` 也会自动先烤,单独跑便于先看结果):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
gtrk deps install --browser # 首次:装钉定版本的烘焙浏览器(约 110 MB)。MUST 先告诉用户再装
|
|
88
|
+
gtrk composite bake --project <工程> # 全部合成;--only cp-w03 只烤这颗
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- **缓存即复用**:没改过的颗粒二次运行全部命中;改了场景、素材文件、上游时间线或帧率才重烤。
|
|
92
|
+
- **没装钉定浏览器**也能烤(退回本机 Chrome / Edge,截图模式,更慢)——回执会提示,**MUST NOT 自作主张下载**;用户同意后再 `gtrk deps install --browser`,或本命令加 `--install-browser`。
|
|
93
|
+
- **某颗失败**(退出码 1):其余照常。回执给出原因(常见:素材丢失、窗口为零、未解析——重跑 `build`);修好再烤那一颗。不修的话出片时该段按粗剪出画,MUST 照实告诉用户。
|
|
94
|
+
- **出片时**:成功的那段以烘焙视频出画;**整段落在合成窗口内的 MG / OV 不再预渲、不计费**(被合成接管了)。
|
|
95
|
+
- 帧纯度(`lint` 的 `impure-frame`)不过的颗粒,烘焙段首帧会与连续播放不同——**lint 过了再烤**。
|
|
96
|
+
|
|
97
|
+
## 编排规则(每条都是测试工程走查时真犯过的)
|
|
98
|
+
|
|
99
|
+
1. **画面数由内容定,不照搬粗剪的轨结构。** 逐句看台词:一句话说到什么就换成什么;没有具体可看的,让讲者本人占画框或用纯排版;**只有内容本身是两个以上的东西才同时放多画**——关系(「你再指点妈妈」→ 女儿 / 妈妈并排)、对照(「她会化浓妆」VS「妈妈没化过妆」)、并列(「打耳钉 / 抽烟 / 喝酒」三张卡逐词亮起)。
|
|
100
|
+
- ❌ 反例:粗剪在这段有两条 B-roll 候选轨,就放一个大框 + 一个小框,两条轨各占一个——画面数由轨数决定,跟内容无关。
|
|
101
|
+
- 画面多用 `own` 槽位按内容另挑入点(粗剪轨上的片段只是候选之一);用 `ref-lane` 的前提是那条轨在这段的时序恰好就是你要的。
|
|
102
|
+
2. **任一时刻画面互不重叠。** 客户端先画全部画面、再画前景层,**下层画面的前景描边一定压在上层画面上**。同一位置交替换画面时,**等前一张的框到位、两框重合后再换**(`seq` 已按此写);版式切换(一画 → 并排)时,先让旧画面缩到位,新画面再进来。讲者不在同一时刻出现两次(大框里是讲者时不放讲者圆窗)。
|
|
103
|
+
- ❌ 反例:讲者圆窗与小画面叠在大画框的边线上,边线横穿它们;分叉曲线从一张画中画中间穿过去。
|
|
104
|
+
3. **裁掉画面里的黑边与烧录字幕。** 电影源常带信箱黑边,综艺源常有烧录字幕与人名条;按 `shots` 的实测写 `data-crop="上,右,下,左"`(比例)。
|
|
105
|
+
- ❌ 反例:画框里留一道黑带。
|
|
106
|
+
4. **人脸构图:眼睛落在上三分线附近,头顶留约一成余量。** 讲者大框、圆窗、素材里的人脸都一样。
|
|
107
|
+
- ❌ 反例:裁切把眼睛放在画框正中,头顶上方空出一大片窗户。
|
|
108
|
+
5. **选片不跨切点。** `own` 的取用段 `[入点, 入点 + 在场时长)` MUST 落在一个镜头内;镜头不够长就接受最后一帧定格或换一个镜头,MUST NOT 拉伸或变速。
|
|
109
|
+
6. **字幕:接管就自己排。** 场景声明 `takeover: ["subtitles","overlay"]` 后,窗口内原字幕与 MG 叠层不再显示,字幕由场景用 `caption()` 或对话气泡排出;屏上排版已经写着同一句话时可以在那段隐藏字幕。
|
|
110
|
+
7. **先求准,再求满。** AI 判定模式的密度上限是对成片吵闹度的约束;整片铺满时也要让「没有具体可看的段」安静(讲者 + 少量排版),别为了铺满每段都堆画面。
|
|
111
|
+
|
|
112
|
+
## 场景写法速查
|
|
113
|
+
|
|
114
|
+
场景文件四段:
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<script type="application/x-cp-spec">
|
|
118
|
+
{
|
|
119
|
+
"id": "cp-w03",
|
|
120
|
+
"window": [77.483, 90.85], // 时间线秒;build 会吸附到帧格
|
|
121
|
+
"takeover": ["subtitles", "overlay"],
|
|
122
|
+
"slots": {
|
|
123
|
+
"teacher": { "bind": { "kind": "ref-lane", "role": "a-roll" }, "label": "讲者", "pin": { "track": 0 } },
|
|
124
|
+
"heavy": { "bind": { "kind": "own", "facet": "化浓妆的少女近景", "starts_at": "浓妆" }, "label": "浓妆",
|
|
125
|
+
"pin": { "material": "broll-local-xxx", "src_in_ms": 116250, "dur_t": 3.8, "starts_at_f": "浓妆" } },
|
|
126
|
+
"subs": { "bind": { "kind": "text", "source": "subtitles" }, "label": "字幕" }
|
|
127
|
+
},
|
|
128
|
+
"anchors": { "浓妆": ["u0026", "特别会化浓妆"], "因为妈妈": ["u0028", "因为妈妈"] }
|
|
129
|
+
}
|
|
130
|
+
</script>
|
|
131
|
+
<style> /* 这颗的版式 */ </style>
|
|
132
|
+
<div class="bed dark"></div>
|
|
133
|
+
<div class="shadow" data-for="heavy" style="border-radius:16px"></div>
|
|
134
|
+
<div data-slot="heavy" data-z="11" data-fit="cover" data-shape="roundrect" data-radius="16" data-crop="0,0,0.2,0"></div>
|
|
135
|
+
<div class="frame light thin" data-for="heavy" data-layer="front" style="border-radius:16px"></div>
|
|
136
|
+
<div id="tg1" class="ptag warm" data-layer="front">浓妆</div>
|
|
137
|
+
<div id="cap" class="cap dark" data-layer="front"></div>
|
|
138
|
+
<script>
|
|
139
|
+
const { A, prog, at, mix, seq, place, reveal, caption, ease } = CP;
|
|
140
|
+
const R = [96, 120, 1160, 652];
|
|
141
|
+
CP.render = (t) => {
|
|
142
|
+
at("heavy", R, seq(t, A("浓妆")));
|
|
143
|
+
place("tg1", R[0] + 20, R[1] + 20, seq(t, A("浓妆")));
|
|
144
|
+
caption("cap", "subs", t, ["浓妆"]);
|
|
145
|
+
};
|
|
146
|
+
</script>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- **槽位绑定**:`ref-lane {track}`(某条轨在该时刻的画面,讲者常用);`ref {clip_id}`(某个片段,窗口外定格首末帧);`own {material, src_in_ms, dur_t | dur_f, starts_at: 锚点名 | starts_at_t: 时间线秒}`(自选素材的一段;之前首帧、之后末帧定格);`text`(字幕文本输入,无几何)。
|
|
150
|
+
- **锚点**:`[句 id, 关键词, 第几处(0 起)]`,取关键词首字在时间线上的时刻;场景里 `A("名字")` 得局部秒。关键词所在的字被剪掉时取其后第一个保留的字。
|
|
151
|
+
- **槽位元素**:`data-slot` 一槽一个;`data-z` 叠放次序;`data-fit` cover / contain;`data-shape` rect / roundrect / circle;`data-crop` 上右下左比例;`data-filter` 只收白名单滤镜。
|
|
152
|
+
- **装饰跟随**:`data-for="<槽位>"` 的元素与槽位同框(`data-pad` 外扩);`.shadow` 在下(背景层),`.frame` / `.ring` 加 `data-layer="front"` 在上——**只给最上层的画面加前景描边**(规则 2)。
|
|
153
|
+
- **分层**:缺省是背景层(画在视频之下);`data-layer="front"` 的子树在视频之上(文字、标签、描边、线条)。
|
|
154
|
+
- **工具箱 `CP`**:`prog(t, 起, 时长, ease)` 进度;`span` 进出;`at(id, [x,y,w,h], 透明度)` 定框;`mix(矩形A, 矩形B, p)` 版式插值;`seq(t, 到点, 下一张到点)` 同一位置逐句换画面的透明度;`place(el, x, y, o)`;`reveal / look / wipe / draw / typeOn`;`caption(el, "subs", t, 高亮词[], 隐藏区间[])`;`cues("subs")` 取字幕条目(局部帧);`fps()`。
|
|
155
|
+
- **常用样式**:`.bed`(底色)/ `.bed.dark`;`.kicker` `.h1` `.h2` `.h3` `.body` `.tag` `.num`;`.ptag` / `.ptag.warm`(画面标签);`.cap` / `.cap.dark`(字幕);`.bubble`(对话气泡);`.stamp`(印章);`.card` `.pill` `.rule`。配色与字体都是变量:`--bg --bg2 --ink --muted --line --accent --accent2 --accent3 --alert --font --font-serif`。
|
|
156
|
+
- **纯函数**:场景脚本 MUST NOT 用 `Date`、`Math.random`、`requestAnimationFrame`、`setInterval`、`setTimeout`(lint 会拦)——同一帧号永远画出同一画面,客户端是逐帧 seek 的。
|
|
157
|
+
|
|
158
|
+
## 风格
|
|
159
|
+
|
|
160
|
+
配色、字体、纹理不在工具箱里,写在 `<工程>/composite/theme.css`(构建时接在工具箱样式之后,覆盖上面那组变量):
|
|
161
|
+
|
|
162
|
+
- 当前栏目配置 `style.skills` 里有 `produces == "composite"` 的条目 → 触发它产 `theme.css`(与 `gtrk-mg`、`gtrk-ai-drama` 的栏目解析同一套);
|
|
163
|
+
- 没有 → 按这条片子的内容临场定一套(讲什么、什么调性 → 配色倾向、字体气质、动效克制度),**全片所有合成颗粒沿用同一套**。MUST NOT 每颗各自为政,也 MUST NOT 套一个与内容无关的固定模板。
|
|
164
|
+
|
|
165
|
+
## 与 `/gtrk-mg` 的分工
|
|
166
|
+
|
|
167
|
+
- 单一陈述的字卡、点题强调、透明叠层 → MG;
|
|
168
|
+
- 一段时间内的整个画面(画中画拼贴、逐句换画面、对照并排、字幕排版)→ 合成。
|
|
169
|
+
- 同一区间二者只取其一:合成声明 `takeover: overlay` 后,MG 叠层在窗口内不显示。已有满屏 MG 的段,AI 判定不会提。
|
|
170
|
+
|
|
171
|
+
## 排错
|
|
172
|
+
|
|
173
|
+
| 现象 | 处理 |
|
|
174
|
+
|---|---|
|
|
175
|
+
| build 报「素材 X 未登记」 | 写进 `composite/materials.json`(`id` + `path`),或换工程里已登记的素材 |
|
|
176
|
+
| build 报「锚点落在窗口外 / 找不到关键词」 | 关键词照句子原文抄(看 `context.md`),或换一个落在窗口内的词 |
|
|
177
|
+
| build 报「轨 N 在窗口内没有片段」 | 那条轨这段是空的:换轨,或改用 `own` |
|
|
178
|
+
| lint 报 `border-over-slot` | 两张画面重叠且下层有前景描边:错开时序(等前一张到位再换)或去掉下层的前景描边 |
|
|
179
|
+
| lint 报 `stroke-through-slot` | 前景线条穿过画面:挪画面或改线条走向 |
|
|
180
|
+
| lint 报 `slot-out-of-stage` | 画面出了舞台:收回到 1920×1080 内(满屏底图除外) |
|
|
181
|
+
| lint / lay 报「需重渲」 | 上游(粗剪 / 素材 / 字幕)改过:重跑 `build`(再 `lint`、`lay`) |
|
|
182
|
+
| lint 退出码 3 | 本机没 Chrome / Edge:排版未机检,用静帧人眼查,照实告诉用户 |
|
|
183
|
+
| lint 报 `impure-frame` | 同一帧号随渲染顺序画出不同画面:多半读了带变换元素的渲染后几何(如对正在缩放的元素用 `getBoundingClientRect` 量宽度)——改读 `offsetWidth` / `offsetLeft` 等不含变换的布局值 |
|
|
184
|
+
| bake 报「找不到可用的浏览器」(退出码 2) | 本机没有钉定浏览器也没有 Chrome / Edge:征得用户同意后 `gtrk deps install --browser` |
|
|
185
|
+
| bake 报「素材缺失:<路径>」 | 素材移动或删除了:恢复文件或重新登记路径后再烤这颗 |
|
|
186
|
+
| lay 报「没铺:时间线上已有同 id 的片段」 | 用户在客户端改过那颗:要重铺先请他在客户端删掉它 |
|
|
187
|
+
| 客户端 CP 行没出现提案块 | 请用户重新载入工程(外部写入要重载才读进来) |
|
|
188
|
+
|
|
189
|
+
## 双模式
|
|
190
|
+
|
|
191
|
+
| 模式 | 停点 |
|
|
192
|
+
|---|---|
|
|
193
|
+
| 快速成片 | 检查点①(审窗口)必停;全部窗口做完、lint 过后,检查点②一次给静帧,点头后一次铺完 |
|
|
194
|
+
| 逐步推进 | 检查点①;每个窗口的设计说明、静帧各停一次 |
|
|
@@ -19,11 +19,11 @@ description: 封面工作台——把一期视频的封面从「设计诊断 +
|
|
|
19
19
|
2. **两阶段不能合并。** 用户还没抽图就端出工作台 = 盲排(不看背景图排字,等于退回指令稿时代);工作台的价值就在「因势象形地就着背景图排字」。阶段一交付后**停下等图**。
|
|
20
20
|
3. **审美决策由本 skill 做,工作台只暴露微调面。** 工作台暴露给用户的只有:图层位置 / 字号 / 文案 / 显隐、显式声明的装饰件开关、T1 全局样式滑杆(描边 / 发光 / 既定字色选项)、**T1 字体与字重档位(字体列表读用户本机已装字体,默认字仍由本 skill 定)**。色板体系、字体选型、布局拓扑、装饰件组合是审美决策——由你按注入的风格资产做出、交付时说明理由;用户想改审美层,**经对话增量改 HTML**(不推倒重来),别让他在工作台里找不存在的控件。
|
|
21
21
|
|
|
22
|
-
> **产物落点纪律(MUST · 全文见随包 `AGENT.md` 同名一节)**:
|
|
23
|
-
> 交付物 MUST NOT 先写 agent 自有目录再拷进工程;中转本身即违规,体积不是豁免理由。
|
|
22
|
+
> **产物落点纪律(MUST · 全文见随包 `AGENT.md` 同名一节)**:
|
|
23
|
+
> 交付物 MUST NOT 先写 agent 自有目录再拷进工程;中转本身即违规,体积不是豁免理由。
|
|
24
24
|
> Prompt 文本与工作台 HTML 只落**用户显式指定目录**,或有工程上下文时落 `<project>/cover/`;
|
|
25
25
|
> **MUST NOT** 把用户媒体(背景图 / 导出的封面 PNG / 任何大文件)复制到 agent 自有工作目录(如用户文档目录下 agent 产品自建的目录、agent 家目录缓存、会话工作区)——压缩背景图时**从原路径读取**,压缩产物直接以 base64 写进工作台 HTML,不留中间副本;
|
|
26
|
-
> 压缩脚本等临时文件一律放系统 temp 且**用完即删**。交付物直接写入工程目录或用户显式指定的路径;引用媒体用原路径。
|
|
26
|
+
> 压缩脚本等临时文件一律放系统 temp 且**用完即删**。交付物直接写入工程目录或用户显式指定的路径;引用媒体用原路径。
|
|
27
27
|
|
|
28
28
|
## 业务分离:栏目封面风格从哪来(关键)
|
|
29
29
|
|