| name | text-motion-skill |
| description | Turn a topic or an SRT subtitle file into a themed text-animation web video — an independent HTML5 player with RAF-driven audio-synced scenes. First-class deliverable is the web player itself; MP4 is an optional export via CDP-automated headless Chrome (default) or OBS/FFmpeg (fallback). Use when the user asks to 根据主题生成口播视频, 从文案到视频, 根据SRT制作视频, 字幕生成视频, SRT分镜, 文字动画视频, text-motion-video, 或 improve/rebuild an existing project generated by this workflow. |
Text Motion Video(v2 · 独立网页 + 自动化 MP4)
🔀 导航入口:接到任务先看 SKILL_INDEX.md——总入口文件,按模式/阶段找"读什么、跑什么",不迷路。
一句话说清这个技能
给我一个主题(或 SRT),我给你一个"网页版视频":
- 一个能在浏览器里播放的独立网页(
index.html + assets/ + scenes/ + audio.mp3)
- 口播由 AI 克隆声音生成,画面由 AI 按详细口播稿设计
- RAF 咬合齿轮:每一帧读
audio.currentTime,画面元素跟着口播节奏出场
- 可选:一行命令导出 MP4(两种 CDP 方案:管道省磁盘 / 写文件更小)
核心原理:RAF 咬合齿轮
上齿轮:audio.mp3 的 currentTime ← 时间轴绝对真理
↓ 每帧咬合
下齿轮:CSS class 揭示(.reveal-XX) ← 画面跟着口播出场
main.js 每一帧读 currentTime,到点了就给 #mount 加上对应的 reveal-* class,CSS 里 #mount.reveal-XX .element { animation: ... } 命中,动画就跑。自成一体,不依赖任何外部引擎。
交付形态(按优先级)
| # | 交付物 | 状态 |
|---|
| 1 | 独立网页(永远产出) | 打开 index.html 就能播 |
| 2 | CDP 逐帧精确 MP4 | scripts/render-mp4-seek.mjs 逐帧 seek+截图(推荐,音画零漂移) |
| 3 | CDP 管道实时截帧 MP4 | scripts/render-mp4-pipe.mjs 实时截帧走管道(快速,长视频有漂移风险) |
| 4 | CDP 写文件 MP4 | scripts/render-mp4.mjs 截图写磁盘(同上原理,兜底) |
| 5 | FFmpeg 自动录屏 | scripts/render-mp4-gdigrab.mjs 弹窗录屏(需可见窗口) |
| 6 | OBS 手动录屏(兜底) | 见 references/RENDER_MP4_MANUAL.md |
入口流程(强制,技能触发后第一件事)
技能触发
↓
🔴 **CHECKPOINT · 第一步:选布局模式(必须问用户)**
├── 1. 全屏模式(fullscreen)— 纯内容画面,无人像
└── 2. 人像模式(portrait-center)— 有真人出镜视频,居中全屏背景 + 左右玻璃浮层
↓
🔴 **CHECKPOINT · 第二步:选风格主题(必须问用户;仅全屏模式,人像模式跳过)**
├── 自动匹配 — 根据内容关键词 AI 推荐
├── 用户指定 — 如"我要科技风""暖色系"
└── 候选展示 — 列出 2-3 个匹配主题让用户挑
(人像模式背景是用户视频、画面不固定,不套主题——文字颜色按背景自适应,辉哥 2026-08-04 定)
↓
第三步:内容来源(🛑 必须问用户"你手上有什么素材",不能默认选择)
├── 全屏模式:
│ ├── 🔴 **先问"你手上有什么素材?"(四选含其他,辉哥 2026-08-05 定——问素材比问"有没有稿"更准,因为 SRT 本身就是文字稿,有成品音频的人不该再走录音)**:
│ │ ① **只有文字稿**(口播稿/文案,无音频)→ 让用户提供(粘贴/指定路径)→ 确认口播稿 → Step 1b 录音
│ │ ② **有成品 SRT + 音频**(配音+时间线已就绪)→ 直接用 → 跳过写稿配音 → 从场景描述稿开始做画面(素材进 Step 2)
│ │ ③ **只有主题**(什么都没有)→ 🔴 **引导用户输入主题或相关内容** → 用口播稿生成技能(koubo-script-writer)按结构生成口播稿 → 🔴 **生成后必须问用户"是否要修改"**,用户改完确认后 → 才进 Step 1b 录音
│ │ - AI 不得自己定主题:用户没给主题时**必须问**"这条视频要什么主题",给候选 + OTHER(血训 2026-08-05:AI 自造主题写稿 → 辉哥纠正"应该先问我要什么主题" → 返工)
│ │ ④ **其他**(都不符合)→ 🔴 **停下问用户具体情况**(如:有视频没稿?有稿是别的格式?只有图片/PPT?),按用户说的判断该走哪条分支或另起流程,**不擅自归类**
└── 人像模式(辉哥 2026-08-05 详细定):
1. **自动创建项目文件夹**(按规则命名 `{YYYYMMDD}-1` 当天第一个 / `-2` 第二个 / `-3` 第三个,辉哥 2026-08-05 定)
2. 🔴 **提示用户导入素材**(文件拖入 / 指定路径),不默认自造
3. 🔴 **检查素材条件是否具备**:
- **视频**(`background.mp4` / `portrait.mp4`)→ ✅ 满足(视频含音频):ffmpeg 提取音频 → 音频转写字幕(**字幕由音频转写生成,非用户输入**)
- **音频**(`audio.mp3`)→ 需转字幕带时间线(Whisper / 百度识别 → `audio.srt`)
- **已有 SRT + 音频** → 直接用(跳过转写)
- 条件不足 → **提示用户补充对应素材**,不擅自替用户决定
两种布局的流程差异
| 步骤 | 全屏 | 人像居中 |
|---|
| 初始化 | --layout fullscreen | --layout portrait-center |
| 场景写法 | #mount 单挂载 | #mount-left + #mount-right + #mount-bottom 三挂载(底部大组件区可选) |
| 用户素材 | 不需要 | 放 项目根目录/background.mp4 |
| 预览注意 | 常规 | 检查玻璃面板在背景上可读 |
| 渲染 MP4 | 正常 | 正常(render-mp4-seek 直接渲染 HTML,background.mp4 视频元素自动播放;渲染方式见 Step 5 引导选择) |
预览地址约定
三种布局使用不同端口避免冲突:
- 全屏:
localhost:3009
- 人像居右:
localhost:3010
- 人像居中:
localhost:3011
Defaults
Read Only What Applies
失败模式速查表
工作流中每一步都可能出问题。以下按阶段列出常见失败、触发条件和修复动作。
| 阶段 | 失败信号 | 一线修复 | 仍失败兜底 |
|---|
| 写稿 | AI 生成的口播稿质量差/跑题 | 退回 Step 1a,补充更多主题关键词和示例 | 用户手动改写关键段落后再让 AI 润色 |
| 压缩 | 压缩后某场景时长 < 6s | compress-script.mjs --min-duration 6 自动补静音 | 退回 Step 1a 补充该场景文案(至少 26 字),或增加新场景 |
| TTS | 百度 TTS 返回错误或无响应 | 检查 BAIDU_API_KEY/BAIDU_SECRET 环境变量是否设置、网络是否通 | 用户自己录音,跳过 AI 配音 |
| SRT | SRT 时间戳呈固定间隔(如全部 X.671 → X+2.671) | 这是伪造 SRT,不能用。用 ffprobe silencedetect 按真实音频重建 | 跑 Whisper 重转写 |
| scene-timing | 实际时长 vs 预计偏差 > 50% | match-scene-timing.mjs 会标记 ⚠ 场景,人工检查 SRT 是否漏句 | 重新跑 build-timing-from-srt.mjs |
| 写场景 | 场景 HTML 中文字符出现 �(U+FFFD) | grep 搜 � 定位截断位置,补全被截断的中文字 | 整段重写该场景 |
| 写场景 | 元素停在 opacity:0 不出现 | 检查场景 script 花括号是否平衡(grep { 和 } 数量) | 检查 CSS reveal 选择器路径是否正确 |
| 写场景 | 所有中文变成乱码(如"璋冪爺")" | Python 脚本以 GBK 编码读写 UTF-8 文件导致。禁止用 Python 工具读写场景 HTML。用 Node.js 或手动编辑。恢复方法:从 SRT 重建中文内容 | 重写该场景 HTML |
| 写场景 | // 注释吞掉同一行后续代码 | 编码损坏导致 // 注释和代码合并到一行。用工具或手写分离注释和代码,或改用 /* */ 块注释 | 整段重写该场景脚本 |
| 预览 | 键盘切帧无效/进度条拖不动 | 用 Invoke-WebRequest 验证音频响应头含 Accept-Ranges: bytes | 换服务器(_serve.js / npx serve),不要改业务代码 |
| 渲染 | CDP 管道模式失败 | 降级到 CDP 写文件模式 render-mp4.mjs | 再降级到 gdigrab 自动录屏或 OBS 手动录屏 |
| 渲染 | |
references/CREATIVE_RULES.md — SRT storyboard、layout density、style、forbidden effects
references/AESTHETIC_GUARDRAILS.md — 硬禁用清单
references/AESTHETIC_THEMES.md — 主题美学
references/SYNC_ENGINE.md — RAF 揭示原理和 beat 时间控制
references/scene-creator.md — 写场景 HTML 的核心指引(v2 版)
references/components/INDEX.md — 组件库索引(优先读取)
references/RENDER_MP4_MANUAL.md — OBS 兜底流程
Optional:
references/THEME_SELECTOR.md / THEME_DEV_GUIDE.md — 主题调试和扩展
references/AUDIO_SYNC_PREVIEW.md — 音频驱动预览
references/MOTION-CANON.md — 自定义动画
references/optional/*.md — 外部素材分析和扩展主题
Project Paths
skillRoot:本文件所在目录
projectsRoot:{skillRoot}/projects/(技能目录下的 projects/)
projectName:自动生成 {YYYYMMDD}-1(当天第一个)、{YYYYMMDD}-2(第二个)、{YYYYMMDD}-3(第三个)…(辉哥 2026-08-05 定:第一个即带 -1,不用无后缀)
projectRoot:{projectsRoot}/{projectName}
扁平目录结构(v2,不再有 v2/ 子目录):
projects/{projectName}/
├── script.md ← 口播稿(配音用稿,定稿原文,无压缩)
├── script-detailed.md ← 场景描述稿(整个场景描述,不参与配音)
├── audio.mp3 ← AI 配音(从口播稿生成,带时间线)
├── audio.srt ← 转写字幕(每句时间戳 = 时间线真相)
├── scene-plan.json ← 场景规划(按 SRT 时间线分解场景片段)
├── scene-timing.json ← 场景时间轴
├── project.config.json ← 项目元数据
├── theme.json ← 主题元数据
├── index.html ← 播放器入口(网页交付物)
├── assets/
│ ├── main.js ← RAF 播放引擎(含键盘导航 seeking 锁)
│ ├── theme-defaults.css ← 主题变量兜底(--hf-info/--hf-pos/--hf-bg-surface 等)
│ ├── tokens.css ← 主题 token(会被 init-project.mjs 用主题覆盖)
│ ├── tokens-extra.css ← --tvp-* 独有变量(不随主题变)
│ ├── chrome.css ← 播放器壳
│ ├── shared.css ← 通用组件
│ └── components.css ← 组件样式
├── scenes/
│ ├── 01-hook.html ← 每个场景一个独立 HTML 片段
│ ├── 02-xxx.html
│ └── ...
└── output.mp4 ← MP4 交付物(可选)
Workflow
项目位置说明:初始化命令跑完后,项目自动创建在 {skillRoot}/projects/ 下。{projectRoot} 就是项目的完整路径,脚本会打印出来。用户的口播录音、人像视频等素材都放到 {projectRoot}/ 下。
全屏模式(fullscreen)— AI 写稿 + AI 配音
主题生成模式从 Step 1 开始。
Step 1. 生成口播稿 + 音频(音频先于场景规划,辉哥 2026-08-02 强制)
生产顺序(不可反,辉哥 2026-08-02 定):
Step 1a 口播稿(script.md 定稿,配音用稿)→ script-ok
↓
Step 1b 音频(从口播稿直接生成,SRT 每句真实时间戳 = 时间线真相)→ audio-ok
↓
Step 1c 场景描述稿(原"详细稿",整个场景描述,从口播稿扩展)
↓
Step 1d 场景规划(基于 SRT 时间线,把场景描述稿分解成场景片段)
↓
Step 1e 写场景 HTML → 双层检查 → 渲染
为什么音频先生成(辉哥 2026-08-02 定):音频是"口播稿配音 + 每句真实时间戳",是时间线真相。场景是匹配层,按音频时间段拆分。音频不生成,就没有时间线,场景规划没依据。以前"详细稿前置 + 按场景逐段生成音频"的逻辑已废弃——那是把场景切分放在音频之前,违背"音频固定、场景匹配"。
概念对齐(辉哥 2026-08-02 定):
- 不存在"精简稿"——以前压缩口播稿导致压缩过度、音频不完整。AI 配音永远用定稿口播稿原文,绝不压缩。
- 详细稿改名"场景描述稿"——只做场景规划的语义骨架,不参与配音;做场景切分时,才把整个场景描述稿分解成场景片段
Step 1a:确定口播稿(配音用稿)
🔴 入口素材分支(辉哥 2026-08-05 定,对应入口三选):
- 素材① 只有文字稿 → 让用户提供(粘贴文本 / 指定文件路径),保存为
script.md → 直接进"确认口播稿"(步骤 2)→ 进 Step 1b 录音
- 素材② 已有成品 SRT + 音频 → 跳过本步和 Step 1b(不写稿不配音)→ 从场景描述稿开始做画面(见 Step 2 入口;SRT 即时间线真相,直接基于它提炼)
- 素材③ 只有主题 → 🔴 引导用户输入主题或相关内容(问"这条视频要讲什么",给候选 + OTHER)→ 读
references/koubo-script-guide.md 按口播稿规则生成口播稿 → 🔴 生成后必须问用户"是否要修改",用户改完确认后才进 Step 1b
- 生成口播稿(
script.md)——配音/录音的最终文本。时长由内容结构自然决定(类型A 约 2-3 分钟,类型B 约 3-5 分钟),不设硬性字数目标。
🔴 口播稿格式整理(辉哥 2026-08-07 强制):确认口播稿前必须先整理格式——
- 每行一个完整句子(一个语义单元),禁止一行几个字的碎片排版(血训:碎片排版 + generate-full-audio 按标点断句 → 190 个碎句,TTS 念出来节奏很断)。碎片短句(如"找素材。""做动画。")合并成完整句(如"找素材、做动画、配字幕、想转场,一条3分钟口播视频,剪一天太正常了。")
- 标题/元数据行不进配音:
script.md 首行不要放"xxx 口播稿(最终版)"这类标题,AI 配音用稿从正文第一句开始。generate-full-audio 已内置"文件开头标题块跳过"逻辑兜底(2026-08-07 加),但写稿时仍应直接删掉标题
- 🔴 口播稿质量检查(去AI味 + 违禁词,2026-08-09 强制) — 生成后、展示给用户前,必须逐条执行
references/koubo-script-guide.md 的「去AI味检查清单」和「合规安全自检」:
- 去AI味:单字动词/排比句/缺主语/"其实"开头/"这事"/书面词/过度压缩成分/缺"可以·了",命中就改
- 违禁词:极限词(最/第一/最佳/绝对)、导流违禁(微信/链接/私信)、虚假夸大、AI 生成标注
- 口播稿不合规不允许展示给用户(血训 2026-08-09:口播稿没跑去AI味检查直接进配音 = 违规)
- 🔴 CHECKPOINT:确认口播稿(素材①③走这步) — 生成后向用户展示,用户说 OK 才继续,不满意就循环修改。用户确认后:
node "{skillRoot}/scripts/check-gates.mjs" "{projectRoot}" confirm script-ok
确认后才进 Step 1b 录音(血训 2026-08-05:口播稿未确认直接配音 = 违规)。
Step 1b:生成音频(带每句真实时间戳 = 时间线真相)
🔴 血训(2026-08-05 全屏实测):录音方式必须问用户,AI 不得默认——即使口播稿阶段已隐含"AI 配音",Step 1b 开始前仍必须用选择项问一次"自己录音还是 AI 配音",不能跳。血训实例:AI 直接跑 generate-full-audio 被辉哥拦截"这里是否也要问用户呢"。这条规则早已在文档,AI 漏执行 = 规则≠执行,故前置到步骤开头并标注"机器强制"。
Step 1b 强制流程:
-
🛑 CHOICE(第一步就做):录音方式 — 必须用 AskUserQuestion 问用户选"自己录音"还是"AI 配音",不能默认,选了再往下走:
选项 A:用户自己录音
"请录制口播音频,保存为 {projectRoot}/audio.mp3。按稿子念,每段自然停顿。"
选项 B:AI 自动配音(推荐) — 直接从定稿口播稿原文生成,不经过任何压缩/详细稿(无压缩稿概念):
node "{skillRoot}/scripts/generate-full-audio.mjs" ^
--script "{projectRoot}/script.md"
- 输入
script.md(口播稿定稿原文),逐字保留、不压缩(无压缩稿,不存在"精简稿")
- 逐句 TTS 生成 + ffprobe 实测每句时长 → SRT 每句带真实时间戳 = 时间线真相
- 生成
audio.mp3 + audio.srt。本阶段不生成 scene-timing.json(场景时间轴由场景规划阶段基于 SRT 分解生成)
- 自动安全增益:合并后测峰值并放大到 -1.5dB(约 +4~6dB 响度提升),不削波不损音质
- 音色读取自
.env.local 的 VOICE_ID(填入你自己的克隆音色 ID;克隆音色 ID 属隐私,勿上传/勿公开,可用 --per 覆盖)
-
🛑 CHOICE(选项 B 选 AI 配音后、生成前必须问):语速 — 必须用 AskUserQuestion 问用户选"正常速度"还是"倍速",不能默认。若选倍速,给建议区间 1x–1.5x(AI 默认 1.4x,辉哥 2026-08-05 实测:AI 原始语速偏慢)。引导用户时注明百度 spd 档位对应真实速率:
| 百度 spd | 真实速率 |
|---|
| 5 | 1.0x(正常) |
| 7 | 1.2x |
| 9 | 1.4x(推荐) |
| 11 | 1.6x |
| 13 | 1.8x |
| 15 | 2.0x |
用户选定倍速后,AI 配音命令加 --speed <x>(如 --speed 1.4),百度服务端直接按对应 spd 档位变速输出,不二次转码、音质保证: | |
node "{skillRoot}/scripts/generate-full-audio.mjs" --script "{projectRoot}/script.md" --speed 1.4
--speed → 百度 spd 换算:(实测 spd9=1.4x)。百度直接变速后 ffprobe 实测每句时长累积 SRT 时间戳 → SRT 时间线同步更新(时间线真相不破坏)
Step 1c:生成场景描述稿(语义切分好的场景清单)
🔴 血训(2026-08-05 全屏实测):场景描述稿 = 按语义切分好的场景清单,不是照 SRT 逐条排。AI 曾直接把 SRT 19 条逐条拆成 12 个场景,其中多个 <4s(2.41s/2.89s/3.14s),违反 4-12s 护栏。生成场景描述稿时就要保证每场景 4-12s(低于 4s = 视觉没展开,高于 12s = 空洞撑不满),低于 4s 的场景必须与相邻场景按语义合并。
🔴 血训(2026-08-05 全屏实测):锚点必须锚定场景的起始字幕。build-timing 的锚点语义是"场景起点锚点"——把"从本锚点匹配的字幕到下一个锚点之前"归为一个场景。锚点用场景内任意标志词 = 该词之前的字幕被吞进上一场景(实测:scene-09 锚点用末句"机会比谁都多"→ 句17/18 被吞进 scene-08 变 13s 超标、scene-09 只剩 2.3s)。每个场景的锚点必须是该场景第一条字幕中的短语(一字不差)。
-
基于口播稿 + SRT 时间线生成场景描述稿(script-detailed.md):
- 每场景 = 若干条 SRT 字幕按语义组合(几段字幕讲同一件事 → 合并成一个场景),不是一条字幕一个场景
- 🔴 每场景时长锁定 4-12s(护栏2):内容多/关键 → 8-12s;过渡/简短 → 4-6s。低于 4s 必须合并,高于 12s 必须拆(在 Step 1c 就完成,不等 Step 1d)
- 每场景:视觉类型 + 锚点词 + 画面细节 + 数字强调
- 视觉类型从预设清单选(钩子/痛点/数据/方案引出/演示/金句/对比/分类列举/场景切换/升华收尾)
- 锚点词必须提炼自口播稿原文,不能凭空造;且必须与 SRT 转写文本一字不差(不能意译/改字)——build-timing 用子串匹配 SRT,改一个字就找不到 → 静默吞场景错位(血训 2026-08-05:锚点错字 → 场景数减少、单场景时长爆炸吞掉相邻场景)
- 不参与配音,只做场景规划的语义骨架
- 保存到
{projectRoot}/script-detailed.md
-
🔴 CHECKPOINT:确认场景描述稿 — 向用户展示场景描述稿(语义骨架 + 锚点),用户说 OK 才继续。不清楚的先问用户,不要自己默认。用户确认后:
node "{skillRoot}/scripts/check-gates.mjs" "{projectRoot}" confirm detailed-ok
Step 1d:场景规划(基于 SRT 时间线,把场景描述稿分解成场景片段)
🔴 先读 references/SCENE_PLANNING.md(场景规划方法论,辉哥 2026-08-02 定)——它定义了规划的本质、流程、每场景 6 项要素、规划铁律。规划对了后面不修补(血训:35 碎片场景→18、声波动效漏规划→事后补)。不读就规划 = 违规。
🔴 规划前强制先读规范(两层,仿人像 Step 2c1,辉哥 2026-08-05 补) — 跑 generate-scene-plan 前必须读透以下文件,不读就规划 = 违规(血训:规划前不读规范 → 密度/动效/死区规划不完整,写完 HTML 才被 R2/S30 拦 → 大量修补返工。视觉密度(元素数/动效数/活动效)必须规划时定死,不等写完 HTML 才查):
第一层 规范文件(教 AI 怎么规划):
references/SCENE_PLANNING.md(方法论:6 项要素/规划铁律)
references/MOTION_LIBRARY.md(元素库 × 动效库:每场景元素 4-10 个、动效 ≥3 种、活动效 ≥1)
references/SCENE_PREFLIGHT.md(源头治理:深度理解口播稿 → expressionCore)
references/QUALITY_GATES.md(GATE 0:切分质量 / 视觉密度门禁)
第二层 项目内容文件(告诉 AI 要规划什么):
script.md(口播稿,理解语义源头)+ script-detailed.md(场景语义骨架 + 锚点)
audio.srt(时间线真相)+ scene-timing.json(场景边界/时长)
读完后逐场景自检视觉密度:元素数量达推荐(recommendedElements)、动效 ≥3 种、持续活动效 ≥1、无 >2.5s 死区——不达标就在 expressionCore 里设计补足,规划阶段就定死,不等写 HTML。
🔴 全屏无真人 → 内容展示为王,防"干巴巴"(辉哥 2026-08-11 强调):画面动态完全靠内容撑,三位一体防翻 PPT——① 布局分散铺满(generate-scene-plan 按类型自动分配:分栏/网格/卡片铺满画面,禁居中挤中间、禁大片留白,垂直空间有限→横向铺展)② 元素高密度(4-10 个/场景,铺满安全区,空背景也要加网格/标签/装饰线)③ 多动效(≥3 种 + 持续活动效 + check-plan-motion 机器强制)。居中(sparse)只留给钩子/金句/收尾,且背景必须铺满。
🔴 动效丰富度机器强制(辉哥 2026-08-10 定):expressionCore 补全后、确认前,必须跑 node scripts/check-plan-motion.mjs --project "{projectRoot}"——每场景 ≥2 种强动效(组件库/自创),禁止 elastic-in/typewriter/stagger/fadeUp 普通组合拼凑,通过(exit 0)才准写场景(血训:第一版全用基础动效被辉哥打回,MOTION_LIBRARY 规范是软要求时 AI 会偷懒,必须机器强制)。
-
打开 audio.srt,这是场景切分的唯一依据。字幕时间线很零碎,根据字幕+时间线组合判断切分(辉哥 2026-08-02 定):
场景总数锚定基准(护栏4,血训 35→18):5-6 分钟口播 ≈ 15-20 个场景(参考历史项目 373s/18 场景)。若切出 30+ 个 = 碎片化,重切。一段字幕 ≠ 一个场景。
核心逻辑(不是一段字幕一个场景!):
- 字幕是零碎的,场景是组合出来的——几段字幕拼起来表达一个完整意思 → 合并成一个场景
- "这一段的真实表达是:这一段是为了表达一个场景",先判断这几段字幕合起来在表达什么
- 表达核心够大/关键 → 单独成场景(8-12s);连续小句讲同一件事 → 合并(4-6s)
- 参考场景规范约束切分:元素数量(4-10)、动效数量(≥3)、动效时长必须在对应音频时间段内(元素出场+动效播放要赶在该场景的 SRT 时间窗里播完)
- 单场景时长锁 4-12s(防切太碎/切太长,保证动效有足够时间表现视觉)
10.5 🔴 写锚点配置 + 生成时间轴(generate-scene-plan 依赖 scene-timing.json,必须先有;辉哥 2026-08-05 补):
- 写 timing-config.json:anchors = 直接用 script-detailed.md 每场景的 [锚点: 词](同一来源两处复用),scenes = ["scene-01", "scene-02", ...]
- powershell node "{skillRoot}/scripts/build-timing-from-srt.mjs" --srt-path "{projectRoot}/audio.srt" --config "{projectRoot}/timing-config.json"
- 🔴 禁止 --segments 均分(会拦腰切断口播一句话,场景和内容对不上)
- 🔴 跑完必检:输出不能含 "⚠ 未匹配锚点"(有 = 锚点词与 SRT 转写不一致,回查修复再继续,否则静默吞场景错位);场景数 = timing-config anchors 数(不等 = 有锚点没匹配上)
10.6 🔴 GATE 0:时间轴质量门禁(提前到规划前!辉哥 2026-08-05 定:时间轴先验证可靠再进规划,避免规划建立在坏时间轴上白做) — 打开 references/QUALITY_GATES.md,逐条执行 GATE 0 检查清单:
- 每场景 start 锚定 SRT 语义断点(起始字幕),非等距切割
- 单场景时长 4-12s(护栏2;QUALITY_GATES 若写 ≥6s 以 SKILL.md 4-12s 为准——过渡/简短场景 4-6s 合法,这是护栏的明确区间,两者不冲突)
- 末场景 end ≥ audio.mp3 总长(ffprobe 实测)
- 边界不在语义断点 / 有 <4s 或 >12s / 等份切割 → 全部修完才准进规划
10.7 🔴 时间轴校验(规划前,辉哥 2026-08-05 提前:确认单给辉哥看之前时间轴必须已可靠):
- 场景边界锚定 SRT 字幕断点(不是等距切割)
- 实际时长 vs 预计时长偏差 > 50% 的场景(脚本用 ⚠ 标记),检查 SRT 是否有漏句或多句
- 校验通过 → 才进规划(步骤 11);不通过回 10.5 修锚点/切分
-
生成 {projectRoot}/scene-plan.json + scene-plan.md(人可读确认单,辉哥 2026-08-05 补):
- 每个场景 = 一组 SRT 字幕片段(scene → SRT 起止时间戳)
- 跑
generate-scene-plan.mjs 生成自动骨架(type/duration/ctAnchors/elementBlueprint/keywords/动效建议),keywords 已自动注入技能名全名(介绍+实操开头)
- 🔴 AI 深度读口播稿补全
expressionCore(必填,骨架为空):每场景一句话"这段在表达X(讲什么),传达Y感觉(情绪/重点)。动效——[关键元素的具体动效设计]"。基于关键词 + 帧内详细口播稿语义设计每个元素动效,不套模板(如 声波→跳动、进度条→随音频移动、对比→箭头反转)。expressionCore 空 = 规划不完整(generate-scene-plan 会提示),不填就写场景 = 违规
- 补全后重跑
generate-scene-plan.mjs 刷新确认单(确认单 scene-plan.md 是辉哥确认规划的人可读载体——JSON 没人会真读,确认看 md)
- 参考
references/SCENE_PLANNING.md(方法论)+ references/MOTION_LIBRARY.md(元素库×动效库)+ references/SCENE_PREFLIGHT.md(源头治理规范)
-
🔴 CHECKPOINT:辉哥确认规划(看 scene-plan.md,不看 JSON)— 每场景关键词/元素/动效/表达核心已定死,确认后才写场景,不满意循环修改(回到步骤 11 改 expressionCore)
Step 1e-0:🔴 规范阅读证明(写场景第一道机器门禁,辉哥 2026-08-05 强制)
🔴 血训根因:不读规范就写场景 → 反复修补(scene-03 数字停滞 / scene-08 typewriter 打在有子元素的标题上排版乱 / 5 处 addClass 变色违规 / scene-06 容器 opacity:0 代码块跳动——全是没读透 SCENE_CHECKLIST / MOTION_LIBRARY / scene-creator 等核心规范造成的)。
机器强制(不靠 AI 自觉):写场景前必须逐份读懂 references/SPEC_INDEX.md 索引清单里的全部核心规范,逐份填写 {projectRoot}/spec-read.json 必答问题答案,然后运行:
node "{skillRoot}/scripts/check-spec-read.mjs" --project "{projectRoot}"
通过(exit 0)才准进 Step 1e 初检;不通过(exit 1)禁止写场景——回到 SPEC_INDEX 对应文档重读、补答案,再跑直到通过。步骤:
-
读 references/SPEC_INDEX.md(必读清单 + 必答问题 + 核心规则关键词)
-
逐份读懂清单里的文档(SKILL.md 全屏写法 / scene-creator.md / SCENE_CHECKLIST / MOTION_LIBRARY / SYNC_ENGINE / 检查脚本规则等)
-
填 {projectRoot}/spec-read.json(每份文档:readAt 时间戳 + 每题用自己的话复述规则,答案必须覆盖该文档核心关键词)
-
跑 check-spec-read.mjs → 通过才继续
-
check-scene-single.mjs 每次运行也会前置 require 本检查(写场景过程中被拦截 = 回去重读,不是改场景糊弄)
-
🔴 门禁已全局化(2026-08-11 治本):统一门禁 lib/stage-gate.mjs 已挂到全部检查/生成/渲染脚本入口(check-scene-single / full-check / check-hard-rules / check-scene-prerequisites / check-layout-quality / check-viewport-overflow / check-component-diversity / check-plan-detail / check-plan-motion / 渲染脚本)。无论 AI 跑哪个脚本,都会被这道门禁拦——没有"省事通道"。门禁每次运行重跑 spec-read / 规划 / 关卡 / 产物验证(不读伪造 JSON),手写 .gates.json / spec-read.json 无法过关。单入口:正常流程只跑 node scripts/pipeline.mjs --project <root> --stage <plan|write|fullcheck|preview|render>。
Step 1e:项目初检(写场景前的前置检查门,全过才准写,辉哥 2026-08-05 定/加固)
🔴 场景规划确认后、写场景前,先做一次初检,全部通过才准进 Step 3。这不仅是"文件存在检查",更是执行顺序 + 检查逻辑 + 视觉密度的总闸门(血训 2026-08-05:初检只查文件会漏掉"顺序错了、检查没跑、密度不够",写场景后才返工。产物顺序可证明、检查逻辑必须有据)。
| # | 初检项 | 检查内容 |
|---|
| ① | 素材齐全 | audio.mp3 + audio.srt 存在(SRT 是时间线唯一真相),且 audio.srt 时间戳非等距(等距=伪造 SRT) |
| ② | 时间轴 | scene-timing.json 存在;场景数 = timing-config anchors 数(不等 = 锚点没匹配上);单场景 4-12s;无"未匹配锚点"输出记录 |
| ③ | 规划完整 | scene-plan.json + scene-plan.md 存在;每场景 expressionCore 已填(空 = 规划不完整) |
| ④ | 执行顺序 | .gates.json 确认顺序:script-ok → audio-ok → detailed-ok 全绿,且 confirm 时间戳递增(乱序 = 跳步,回查) |
| ⑤ | 检查逻辑跑过 | GATE 0(切分质量)、时间轴校验(步骤 14)必须已执行且通过——不跳过、不口头说"检查了",用脚本/清单留痕 |
| ⑥ | 视觉密度 | 每场景:元素数 ≥ recommendedElements、动效 ≥3 种、持续活动效 ≥1、相邻 beat 无 >2.5s 死区;动效丰富度 check-plan-motion.mjs 必须通过(机器强制,2026-08-10)——不达标回 Step 1d 补 expressionCore |
| ⑦ | 音频时长 | 末场景 end ≥ audio.mp3 总长(ffprobe 校验) |
🔴 CHECKPOINT:写场景前确认(辉哥 2026-08-05 加) — 初检 7 项全过后,向辉哥展示初检结果,辉哥说"开始写"才准进 Step 3。不经辉哥确认直接写 = 违规(血训:初检是机器检查,规划确认单是辉哥看场景前最后一次把关,跳过 = 白写返工)。展示内容:① 初检 7 项通过情况 ② 将按 scene-plan.md 写的 9 个场景清单 ③ 提示辉哥"OK 就开始写场景"。
Step 1f:🔴 RAF 锚点铁律(核心中的核心,辉哥 2026-08-07 强制,凌驾其他所有写场景规则)
这不是"写场景时注意一下"的软规则——这是整个系统的运行原理。违反 = 场景和口播脱节 = 辉哥肉眼一看就废。
RAF 咬合齿轮(main.js 运行原理):
audio.currentTime(字幕时间线 = 绝对真理)
↓ 每帧 RAF 读一次
runScene 的 beat 触发(ct >= beat.t → 执行 action)
↓
画面元素揭示/动画
音频 currentTime 是唯一时间来源。beat 的 t 值就是"口播讲到哪个词的时刻"。
写场景时 beats 的 t 必须满足(机器强制):
- t = 对应口播词在字幕时间线上的时刻(看
scene-plan.json 每场景的 wordAnchors 字段:[{t, text}])——不是随手写 4.6/7.6
- 内容元素(标签/列表行/卡片内容/步骤/消息/typewriter/countUp)逐个跟口播:每个并列内容项一个 beat,t 锚定它对应的那个口播词(找素材@7.64 / 做动画@8.26 / ...)——禁止容器 addClass 一次性出
- 装饰/框架(眉标/主标题/容器壳/下划线/箭头/进度条/addClass 强调)可一次性,但时间也尽量贴最近口播词
- 任何内容 beat 的 t 不得超前它对应口播词 >2.5s(R2d 硬拦);相邻视觉活动间隔 ≤4s(R12)
为什么必须这样(血训 2026-08-07):scene-01 标签手写 4.6s 出场,口播 7.64s 才念"找素材"——超前 3s,辉哥一看"动画和口播完全脱节"。scene-04 右卡片列表(动态文字模式)整组弹出,但口播全程在讲人像模式——内容脱节。这些全是我手写 beats 时间、没锚定字幕时间线造成的。
执行方式:写场景前必读 scene-plan.json 该场景的 wordAnchors(词级时刻表),beats 的 t 直接照抄对应词的 t。不准凭感觉定时间。
写场景流程(2026-08-11 定,血训:写一批查一批导致反复返工 + 生成1帧就检查):
- 🔴 写场景阶段 = 全部场景写完,中途不跑检查。只允许写完第一个场景跑一次
node scripts/pipeline.mjs --project <root> --stage write 验证格式/规则理解对(此后不再中途检查)。
- 写每个场景前对照
references/scene-creator.md 的「写场景硬约束清单」逐条自检(字号/布局/动效种类/间隔/多阶段/持续动效/变色/opacity/字体/keyframes/伪元素/双入场/beats递增/类名/wordAnchors/组件内部类名/照翻译实现细节蓝图)。
- 🔴 写 HTML = 照翻译 scene-plan 的"正确性底线"(4 类防错):类名/组件结构/布局照抄
implementationDetail、文案照抄 elementContent(句子级,禁编造口播外文案)、时间锚点照 wordAnchors。照翻译只管这 4 类防错底线,不管视觉表达——动效组合、装饰细节、排版层次、视觉风格由 AI 自由发挥(在硬规则内即可)。规划 expressionCore 只定"这段讲什么 + 传达感觉 + 强动效方向",不逐元素锁死动效(血训 2026-08-11:过度定死 → 画面千篇一律,辉哥明确要求灵活放开)。写场景前 check-plan-detail.mjs 必须已通过(含 D6 elementContent 完整)。照翻译由机器强制:check-scene-translation.mjs(已挂进 check-scene-single/full-check/pipeline)验证类名/文案/布局/组件结构照抄蓝图,不照抄直接拦。
- 🔴 写场景前决策确认(机器强制):每场景写前把确认行写进
{root}/scene-confirm.json——组件[照components] | 元素数[照recommendedElements] | 动效[照recommendedEffects] | 文案[照elementContent] | 布局[照layout] | 类名[私有.sN-xxx无组件内部类名] | 规则自检[scene-creator约束表]。check-scene-decision.mjs 逐字段比对 scene-plan,缺记录/填错直接拦(强制回答去读规则,生成时减少大部分问题)。
- 全部写完 → 单入口流水线(不要单独挑脚本跑,门禁已挂所有检查入口,挑哪个都会先被拦):
node scripts/pipeline.mjs --project <root> --stage write(自动跑全部场景 check-scene-single 全检查)→ 通过后 --stage fullcheck(full-check 第二层)。
- 不"写一个查一个"、不"生成 1 帧就检查"(血训 2026-08-10:中途检查浪费往返时间,且形成依赖检查兜底的坏习惯)。
🔴 自动修循环(流水线引擎机器强制,辉哥 2026-08-11 定):
全部场景写完 → node scripts/pipeline.mjs --stage write(自动跑 check-scene-single 全场景)
→ 有报错:AI 逐个真实修复(不改功能不造假,辉哥铁律)
· 修复 = 读报错 → 定位根因 → 改对应场景/规划 → 重跑 `pipeline --stage write --fix`
→ 轮数由 .pipeline-state.json 机器计数,≤3 轮(辉哥铁律三:3 次不成功强制停)
→ 3 轮不过 → pipeline 输出"风险汇报"段并 exit 2,AI 把该段原样呈给辉哥,不继续盲改
→ 全过 → pipeline --stage fullcheck(full-check 第二层,同上循环)
→ 修复全程记录进 .pipeline-state.json 的 fixLog → 交付报告
🔴 风险汇报清单("全自动+风险才汇报"落地,辉哥 2026-08-11 定):
以下情况必须出现在辉哥面前,其余静默通过(交付成品 + 报告)。pipeline 遇到 ④ 类会输出"风险汇报"段并 exit 2,AI 必须原样转发给辉哥,不得自行处置:
① 自动修 3 轮超限:场景 + 问题 + 已尝试的 3 轮修复
② 检查报错无法理解/无法修:标记跳过,说明原因
③ 渲染校验失败:时长偏差 / 黑帧 / 帧数不符
④ 机器判不了但明显矛盾:表达核心与场景类型/口播稿明显不符
交付报告固定含:改了什么(修复记录,来自 .pipeline-state.json fixLog)+ 检查结果(全绿/带 ⚠️)+ 风险清单(有则列)。
🔴 检查脚本改完必自测(辉哥 2026-08-11 定,防死规则):修改/新增 check-*.mjs 规则后,跑 node scripts/self-test-rules.mjs——验证关键规则活着(能拦已知违规),防假绿(血训 R19/R20/S37 曾全绿漏检)。新增规则时必须在 self-test-rules.mjs 的 cases 里加一条已知违规 case(规则全绿没拦过一个该报的 = 没检查)。已知 S37 只查 id 选择器 typewriter(class 选择器场景由引擎 preMeasure 兜底)。修改流水线/门禁(stage-gate / 各脚本门禁挂点 / check-gates confirm)后跑 node scripts/self-test-pipeline.mjs——验证伪造/绕过必被拦、真合格放行(防门禁有洞)。
人像模式(portrait-center)— 用户口播 + AI 画面
用户已录好真人出镜口播视频,AI 只做画面叠加。人像模式是独立逻辑,与全屏模式本质不同(辉哥 2026-08-04 定)。
🔴 人像不需要写口播稿、不需要 AI 配音(辉哥 2026-08-05 强调):用户视频转写的 SRT 就是文稿(带时间线),直接基于它提炼关键点做场景。写口播稿 + AI 配音只是全屏模式(无人像、纯 AI 生成)的流程。人像流程 = 用户给视频 → 提取音频 → 转写 SRT → 基于 SRT 做场景。
为什么整个文件体系完全独立(辉哥 2026-08-05 补充完整理由):
- 根本原因——表现方式本质不同:人像模式是真人口播为主,真人说话本身撑起画面动态,左右两侧只是辅助;全屏模式没有真人,必须靠元素堆叠/动画/场景切换撑视觉动态,否则像翻 PPT。两者的场景时长标准、动效密度、画面逻辑都不一样,不能混用规则
- 防污染:两者都靠代码生成,文件若混在一起容易互相污染(组件库/变量/检查脚本互相引用),出错后很难定位是哪个模式的代码导致的
- 易纠错:文件隔离后,每个模式单独跑自己的检查,报错能直接定位到该模式
- 内容差异大:很多内容只适用于其中一个模式(有些适用有些不适用),混在一起会互相干扰
- 执行效率高:独立后单模式的生成/检查/维护各自高效,互不拖累
- 组件库适用性差异(辉哥 2026-08-05):全屏组件库(mac-window / eyebrow / badge / big-title / 复杂网格等)部分完全不适用于人像——人像是轻量提示卡 + 左右栏辅助,不需要全屏窗口/高密度视觉组件。人像组件库
components-portrait.css 独立设计(p-* 前缀),与全屏 components.css 零交集(check-portrait-rules P4 已禁止引用全屏组件 class)
- 架构演进方向(辉哥 2026-08-05):独立结构为后续把两种模式拆成 2 个独立技能(全屏模式技能 + 人像模式技能)铺路,现在保持文件独立即为此做准备
- 所以模板/组件库(components-portrait.css)/配色(portrait-tokens.css)/规划(portrait-plan.json)/检查(check-portrait-rules + check-portrait-plan + portrait-full-check)/字体全部独立,与全屏零交集
- 不做全量字幕动画——只提炼关键点做轻量提示卡(金句/数据/转折/结论),视频本身是主体。场景少、内容轻,抽帧分析成本可控
- 不搞固定主题——背景是用户视频、画面不固定,无法套主题。文字颜色根据视频画面底色自适应(先量化展示内容 → 按展示时间点抽帧 → 分区分析亮度/主色调 → 生成场景配色)
- 透明背景——文字直接浮在视频上,无大面积色板,靠颜色对比 + 微阴影保证可读,文字靠上(不垂直居中)
- 可展示形态:文字提示、标签、管道流程图、高亮引用块、数据表、动态效果
Step 2a:素材条件检查 + 提取音频 + 转写(人像素材入口,辉哥 2026-08-05 详细定)
- 🔴 检查用户给的素材,分三种情况:
- 视频(
portrait.mp4 居右 / background.mp4 居中)→ ✅ 满足:视频含音频,ffmpeg 提取音频 → 转字幕
- 音频(
audio.mp3)→ 直接转字幕带时间线 → audio.srt
- 已有 SRT + 音频 → 直接用,跳过转写
- 视频时用 ffmpeg 提取音频:
ffmpeg -i video.mp4 -vn audio.mp3
- 🔴 转写字幕(引导用户选方式,辉哥 2026-08-05 定):AI 不擅自替用户定工具,提示用户选择转写方式,目标是拿到带时间线的字幕文件
audio.srt:
- 百度语音识别(video-image-reader-writer 技能)
- 阿里/其他云 ASR
- 本地 whisper
- 系统自带语音转文字
- 用户已有 SRT(直接用,跳过转写)
- 转写完成后回读 audio.srt 核对时间戳真实(不是等距),再继续
- SRT 是时间轴唯一真相,禁止估算等距切割
Step 2b:从 SRT 提炼关键点 + 写场景描述稿(内容精简,非全量切场景)
- 读 SRT 内容,提炼要展示的关键点(金句/数据/转折/结论),不是每句字幕都做场景
- 每个关键点 = 一个轻量提示卡场景(眉标 + 一句主标题 + 辅助元素),展示形态按内容选(文字/标签/流程图/数据表/引用块/动态)
- 场景数量少(几分钟视频约 4-8 个),每场景 4-12s
- 🔴 写
script-detailed.md(场景描述稿) — 这是从 SRT 提炼的类型表,不是重写口播稿(人像不写口播稿,辉哥 2026-08-05 强调)。每场景一行:
## 场景01 [钩子] [锚点: 从零散的记录里捋出来]
场景02 [痛点] [锚点: 憋到十二点]
...
- 类型从 7 种模板库选:钩子 / 痛点 / 方案引出 / 演示 / 数据 / 金句 / 升华收尾(决定生成规划时选哪套左右栏模板,见 P_TEMPLATES)
- 锚点 = 该场景 SRT 独有的标志词(4-12 字、优先实词),必须从 SRT 原文精确取词(一字不差,不能意译/改字)——build-timing 用子串匹配 SRT 文本,改一个字就找不到 → 静默吞场景错位(血训 2026-08-05 实测:锚点错字 → 场景数 7→6、单场景 27.7s 吞掉相邻场景)。用于 SRT 自动匹配场景边界
- 不写这份类型表 → 生成规划时所有场景 type=演示,左右栏模板全单调,风格轮换失效(血训 2026-08-05)
- 场景规划确认后生成
scene-timing.json
Step 2c:初始化项目 + 素材 + 时间轴 + 配色(项目文件夹一步到位,辉哥 2026-08-05 定)
🔴 初始化 = 复制人像模板全套文件,项目文件夹即完整(init-project.mjs 把 templates/portrait-center/ 整个复制到项目根):
projects/{projectName}/
├── index.html ← 播放器入口(引用 portrait-tokens / scene-colors / chrome / components-portrait / main.js)
├── assets/
│ ├── main.js ← 播放引擎(双挂载 #mount-left/#mount-right + runScene + AX)
│ ├── components-portrait.css ← 人像独立组件库(p-* 16 基础 + hero 主角组件 r-code/p-chart/p-flow/p-step-block)
│ ├── portrait-tokens.css ← 双档色板(--pc-* 暗档 + --pc-*-light 亮档)
│ ├── chrome.css ← 播放器壳
│ └── *.ttf ← 字体(Noto Sans SC 400-800 / JetBrains Mono 400/500/700)
├── scenes/
│ └── _layout.html ← 写场景骨架(对照它改,不自创轮子)
├── scene-timing.json ← 场景时间轴(模板占位,build-timing-from-srt 覆盖)
└── project.config.json ← 项目元数据(layout=portrait-center)
然后导入素材 + 生成配套文件(让人像项目文件夹完整):
# ① 初始化(复制模板全套文件到项目根)
node "{skillRoot}/scripts/init-project.mjs" --layout portrait-center
# ② 导入用户素材(提示用户放入 / 指定路径)
# 背景视频 background.mp4(居中)/ portrait.mp4(居右)
# 或音频 audio.mp3(需转字幕);或已有 audio.srt
# ③ 视频时提取音频
ffmpeg -i 视频.mp4 -vn audio.mp3
# 音频时直接转写:Whisper/百度 → audio.srt(字幕由音频生成,非用户输入)
# ④ 写锚点配置文件 + 按锚点生成时间轴(辉哥 2026-08-05 定:禁止均分)
# 先写 timing-config.json:anchors = 直接用 script-detailed.md 每场景的 [锚点: 词](同一来源,两处复用):
# { "anchors": ["从零散的记录里捋出来", "以前我写周报", ...], "scenes": ["scene-01", "scene-02", ...] }
node "{skillRoot}/scripts/build-timing-from-srt.mjs" --srt-path "{projectRoot}/audio.srt" --config "{projectRoot}/timing-config.json"
# 🔴 禁止 --segments 均分:会拦腰切断口播一句话,场景和内容对不上
# 🔴 跑完必检:输出不能含 "⚠ 未匹配锚点"(有 = 锚点词与 SRT 原文不一致,回查修复再继续,否则静默吞场景错位,血训 2026-08-05)
# ⑤ 配色分析(人像特有,必跑)
node "{skillRoot}/scripts/analyze-portrait-colors.mjs" --project "{projectRoot}"
# 生成 scene-colors.json + assets/scene-colors.css
🔴 配色分析规则:对每个场景在 start+0.3s 抽背景视频帧,按文字所在区域(左 30%/右 30%)分区分析亮度与主色调,生成 scene-colors.json + assets/scene-colors.css。配色规则:
- 亮度 L > 143 → 深色文字;L ≤ 143 → 浅色文字(实测校准值,可用
--light-threshold 调)
- 主文字用画面主色调,副文字用互补色(每场景至少 2 种 hue 组合)
- 强调色用互补/亮色;描边用同色系半透明(不用纯黑纯白、不用大光晕)
- 用
--video <path> 可对同一时间轴测不同视频
静态图背景(无真人视频时):复制图片为 background.png(main.js 已支持 background.mp4 缺失时回退静态图),或转成静态视频 background.mp4——时长必须 ≥ 音频总长:
ffmpeg -loop 1 -i bg.png -t <音频秒数> -c:v libx264 -pix_fmt yuv420p background.mp4
Step 2c0:项目初检(素材导入完成后必做,通过后才准写规划,辉哥 2026-08-05 定)
🔴 项目导入全部完成后,先做一次初检,全部通过才准进入场景规划:
| # | 初检项 | 检查内容 |
|---|
| ① | 项目文件齐全 | index.html / assets/main.js / components-portrait.css / portrait-tokens.css / chrome.css / 字体 / scenes/_layout.html 都在 |
| ② | 素材就绪 | 视频(background.mp4)或 音频(audio.mp3)+ audio.srt 存在 |
| ③ | 时间轴生成 | scene-timing.json 存在,场景数 = timing-config anchors 数(不等 = 有锚点没匹配上,回查锚点词与 SRT 原文);单场景 ≤20s(超 = 锚点错位信号) |
| ④ | 配色分析 | scene-colors.json + assets/scene-colors.css 都在(index.html 引用它),每场景 light/dark 档位已定 |
| ⑤ | 音频时长 | 末场景 end ≥ audio.mp3 总长(ffprobe 校验) |
初检通过 → 进入 Step 2c1(先强制读规范文件,再生成场景规划文件)。
Step 2c1:场景规划(人像独立体系,辉哥 2026-08-05 定)
🔴 写场景 HTML 之前必须先"读透规则 + 生成完整规划",全部定死,辉哥确认后才写场景。这样写场景只是执行规划,后期零修改(2026-08-05 血训:规划只定元素类型、文案/数量/动效靠写场景时临场发挥 → 后期反复打回)。
0. 🔴 强制先读规则(两层:规范文件 + 项目内容文件,AI 必须,防偷懒;初检通过后、生成规划文件前必读,不读就生成规划 = 违规) — 辉哥 2026-08-05 强制:这是决定后面成败、生成质量和效率的核心门禁。项目初检(Step 2c0)通过、准备写场景规划文件时,必须读透以下两层文件、内化后再生成规划(血训:不读检查脚本就写场景/规划,打回十几轮浪费一半时间;读透一次生成就合规,效率和质量都靠这一步):
第一层:规范文件(教 AI 怎么写,缺一不可)
references/PORTRAIT_SCENE_GUIDE.md(设计三原则 + 血训 #1-14 + 规划规范 + 组件清单 + 预览验收清单)
templates/portrait-center/assets/components-portrait.css(组件初始态/AX 对应/双档选择器)
scripts/check-portrait-rules.mjs(硬规则 M/I/P/S/T/A 组)
scripts/check-portrait-plan.mjs(规划合规 R1-R9)
第二层:项目内容文件(告诉 AI 要做什么,生成规划前必读)
audio.srt(SRT 转写文稿 = 内容来源,AI 深度读它理解口播说什么)
scene-timing.json(时间轴:每场景边界/时长,build-timing-from-srt 产出)
scene-colors.json(背景档位:每场景 left/right 的 light/dark,analyze-portrait-colors 产出)
1. 生成规划骨架:node scripts/generate-portrait-plan.mjs --project "{projectRoot}" → portrait-plan.json + portrait-plan.md(人可读确认单)
每场景含:类型 / 锚点 / 时长 / lightDark 档位 / leftColumn(count+元素+文案) / rightColumn(count+hero 主角组件+元素+文案) / motionPlan(动效数量+actions) / rules(该场景要满足的检查+血训清单) / ctAnchors 时间轴 / srtText
确认单 portrait-plan.md 是辉哥确认规划的人可读载体(每场景左右栏文案/动效/hero/时间锚点)——JSON 没人会真读,确认看 md(辉哥 2026-08-05 定)
2. AI 深度读该场景 SRT 转写文稿(人像不另写口播稿——用户视频转写的 SRT 就是文稿,辉哥 2026-08-05 强调;300-500 字等字数是测试数字不是规则),补全每场景 expressionCore — 必须定死(不是一句话表达核心):
- 左栏每元素具体文案(从该场景 SRT 取词)
- 右栏每元素具体文案(取词分配、左右不重复)+ 主角组件 hero
- 每元素动效(typewriter/countUp/progress 等,≥2 种)
- 入场时间锚定字幕断点(ct = 字幕绝对时间 - offset)
- 对照 rules 自检:数量 / 动效数 / 主角组件 / 左右不重复 / 时间锚定 / 禁变色 / nowrap
- 补全后重跑刷新确认单(保留已填 expressionCore,只刷新确认单):
node scripts/generate-portrait-plan.mjs --project "{projectRoot}"
3. 🔴 CHECKPOINT:辉哥确认规划(看 portrait-plan.md,不看 JSON)— 确认后才写场景 HTML,不满意循环修改(回到步骤 2 改 expressionCore)
4. 按规划写场景 HTML(执行规划,不临场发挥)→ 顶部写 SRT 注释逐句核对 beat.t
5. 全链路检查循环:node scripts/portrait-full-check.mjs --project "{projectRoot}"
(硬规则 check-portrait-rules + 规划合规 check-portrait-plan R1-R9 + 音频时长)——报错就修复,直到 0 错 0 警告
Step 2d:写场景 HTML → 预览 → 检查 → 渲染
- 场景根 class:
.s{序号}l(左挂载)/.s{序号}r(右挂载),配色用 var(--pc-main) / var(--pc-sub) / var(--pc-accent) / var(--pc-stroke)(由 scene-colors.css 注入)
- 透明背景:文字直接浮在视频上,无大面积色板;文字位置靠上(
justify-content:flex-start)
- 文字锐利:只用微小 text-shadow(chrome.css 全局
--pc-stroke 控制),禁止 14px+ 大光晕
- 人像居中场景 HTML 用
<!--R--> 分隔左右内容(左放 mount-left、右放 mount-right)
人像居中(portrait-center)
- 三挂载:
#mount-left + #mount-right + #mount-bottom(底部大组件区可选),场景 HTML 用 <!--R--> 分左右、<!--B--> 分底部
- 人像视频全屏背景(main.js 自动加载
background.mp4)
- 文字直接浮在视频上(透明),活性检查只查
mount-left(右侧可能为空)
- 预览
localhost:3010,肉眼确认文字在任何背景上可读
人像居中场景写法标准(v5 runScene,辉哥 2026-08-04 定)
🔴 时序铁律:场景用 runScene({root, offset, duration, beats})(main.js 内置),每个 beat 的 t 直接锚定 SRT 字幕断点(ct = 字幕绝对时间 - offset),禁止估算。场景文件顶部写 SRT 注释(逐句 #序号 绝对时间 "字幕原文"),写完对照注释核对每个 t。
🔴 字幕取词去重:左右栏内容必须从 SRT 取词分配,左右不重复——左栏放主信息(眉标/标题/正文),右栏放视觉强化(数字/清单/标签,是左栏的数据化)。字幕用完了宁缺毋滥,不编字幕里没有的词(血训:右栏 tag 编"每天重复"等字幕外的词 = 违规)。
🔴 动效:beat action 用 fadeUp/pop/popUp/scaleX/scaleY/countUp/typewriter/progress。装饰元素(竖线 scaleY、装饰线 scaleX)也要走 beat reveal,不能最早暴露(血训:装饰线 opacity:1 最先出现 = 时间点错乱)。typewriter 支持双色标记:txt 内 **关键词** → 生成 span.p-hl(accent2 色),标题打字机出场也能双色(辉哥 2026-08-05 检验:标题必须 2 种文字颜色)。
🔴 字号铁律:所有文字 ≥17px。本套:编号 64 / 标题 31 / 眉标 22 / 正文 20 / 标签 20(1920 基准)。
🔴 颜色规则(档位机制,辉哥 2026-08-04 定):
- 色度分析:
scripts/analyze-portrait-colors.mjs 逐场景抽帧分析左右区域亮度 → 输出 scene-colors.json 每场景 {mode: light|dark}(L≥120 → light)
- 档位 class:场景根元素加
.light(亮背景)或 .dark(暗背景)——生成器读 scene-colors.json 自动加
- 双套选择器:场景 CSS 写
.sNl.light .t{color:var(--pc-accent-light)} / .sNl.dark .t{color:var(--pc-accent)} 两套
- 精心色板(非算法混合):
portrait-tokens.css 每主题两档——--pc-*-light(亮背景深色档,同主题色相的干净深色版,如 KINE 爱马仕橙→#EA580C 饱和橙)、--pc-*(暗背景原色档)
- 玻璃面板恒白字:正文/清单/logo 等玻璃面板内文字永远白色(玻璃深底保证可读),玻璃透明度尽量低(0.3-0.55),白字可见即达标——亮背景玻璃稍深、暗背景玻璃更透
- 裸字按档:标题/眉标/编号/数字/tag(下划线式)亮背景用深色档、暗背景用原色档
- 禁止全部压成同一色:每元素保留搭配规则(标题=accent、关键词=accent2、眉标=eyebrow、正文=ink),双色标题两色要拉开对比(色相差距大)
- 换背景视频跑一次 analyze 自动选档;辉哥看哪个色不满意 → 直接改色板值迭代
🔴 组件库(2026-08-05 迭代):人像模式全部用独立组件库 assets/components-portrait.css(p-* 组件:p-stage/p-head/p-title/p-tline/p-sub/p-num/p-bar/p-tag/p-list/p-steps/p-quote/p-pill/p-track/p-step-idx/p-state/p-card + hero 主角组件 r-code/p-chart/p-flow/p-step-block),不引用全屏 components.css。组件清单 + AX 对应见 references/PORTRAIT_SCENE_GUIDE.md §组件库。
🔴 扁平混搭布局(2026-08-05 血训):右栏直接平铺 p-* 组件兄弟元素混搭(814 元素、58 种类型),禁止外层大卡片容器;全部左对齐(靠组件基类默认,不写 align-self/width/flex-direction 覆盖);自定义修饰类只改配色不改布局;beats selector 必须用唯一修饰类定位,禁止 :nth-of-type(会按标签类型计数落空 → 元素永不入场)。
🔴 防塌陷(2026-08-05 血训):.p-stage 固定高 flex 列 + overflow:hidden,内容溢出时带 overflow:hidden 的子元素(.p-bar/.r-code)被压到 0/塌陷 → 给不可压缩元素加 flex-shrink:0。长空档(相邻 beat >4s)要插填充(辉哥 2026-08-05 检验:禁用变色强调 + 严禁字重,改用下划线/装饰 / 元素错峰 / typewriter)。叙事顺序必须跟 SRT 语义顺序(先讲原因再讲结论,禁止先亮结论)。完整血训见 references/PORTRAIT_SCENE_GUIDE.md §血训与雷区。
🔴 检验沉淀规则(辉哥 2026-08-05 逐帧检验沉淀,必须遵守):
- 🚫 严禁文字变色强调(辉哥 2026-08-05 定:"变色"指文字本身的颜色变化,任何 addClass 让文字 color 变(橙→蓝、文字变灰、文字变白等)= 违规,很 LOW)。开场场景关键文字用 typewriter 出场。
- 🚫 严禁字重强调(辉哥 2026-08-05 补:font-weight 突变也很 LOW,视觉呆板)。
- ✅ 强调手段可选,且不是必须(辉哥 2026-08-05 补:下划线不是强制项)。下划线的用途 = 强调重点词 + 装饰:① 给关键词(如"抢饭碗")加下划线让眼睛聚焦 ② 标题下方独立横线做视觉分隔。看内容需要决定加不加,不是每个词都要加。禁止用 box-shadow 冒充下划线(细且无展开动效,辉哥 2026-08-05 检验)。边框高亮(border-color)、背景色块(background,文字颜色保持不动)也是可选强调手段。
- 📏 下划线技术实现统一用伪元素 + scaleX(辉哥 2026-08-05 定:技术实现层面优先稳定):
::after/::before 伪元素 {content:'';position:absolute;left:0;right:0;bottom:-Npx;height:3-5px;background:var(--hf-primary);transform:scaleX(0);transform-origin:left;transition:transform .5s ease-out},用 .on 类触发 transform:scaleX(1)(有展开动效)。伪元素 position:absolute 时对应元素必须加 position:relative(R23 兜底,否则线贴到页面角落)。禁止用 box-shadow 冒充下划线(无展开动效,辉哥 2026-08-05 检验:细且难看)。若目标元素被 JS 动效驱动(fadeUp/pop/popUp 有入场 transform),下划线伪元素加在它的子元素/独立 span 上,不与入场 transform 冲突。装饰线(标题下方独立横线)用独立元素 + scaleX。
- 🚫 下划线 vs 装饰线必须样式区分(辉哥 2026-08-05 检验:同一个画面里,下划线(跟词的)和分隔装饰线(独立横线)不能长得一样——否则叠在一起或错位平行,很难看)。同场景出现两种线时,必须明显区分:粗细不同(下划线 4-6px / 装饰线 2-3px 或更细)、颜色不同(下划线主色 / 装饰线 muted 淡色)、位置不同(下划线贴词底 / 装饰线独立留白)。判断标准:一眼能看出"这条是强调词、那条是分隔"。
- 🎨 标签/胶囊必须有底色分级(辉哥 2026-08-05 补:禁止透明底 + 只靠边框区分,视觉不明显):
- 负面/过渡/次要标签 → 浅色底:
background:color-mix(in srgb,var(--hf-bg) 92%,var(--hf-text-muted)) + muted 文字 + 淡边框(低存在感)
- 正向/核心/目标标签 → 主色底(可稍深):
background:color-mix(in srgb,var(--hf-bg) 88%,var(--hf-primary)) 或 color-mix(in srgb,var(--hf-primary) 14%,transparent) + primary 文字 + 主色边框(高存在感)
- 最深强调(关键目标/CTA)→ 主色实底:
background:var(--hf-primary) + 亮字(最高存在感)
- 原则:用底色深浅制造层级,浅色不突兀、深色做强调,同一屏标签底色有区分但不花哨
- 🎨 配色白名单(B,辉哥 2026-08-05 定):场景颜色全部走主题变量
var(--hf-*)(全屏模式有主题风格 tokens.css 兜底)。color-mix() 混色时第一个参数必须是 var(--hf-*),禁止写死 #hex/rgb()/rgba()(写死=换主题翻车,S2c 检查)。允许中性色:////。
🔴 参考骨架:templates/portrait-center/scenes/_layout.html(KINE 布局 + runScene 完整示例 + 双套选择器),写场景对照它改,不自创轮子。
两者最后都跑人像全链路检查 portrait-full-check.mjs(人像硬规则 check-portrait-rules + 规划合规 check-portrait-plan R1-R9 + 音频时长)→ 预览 → 渲染。注意:人像模式场景为轻量提示卡,检查规则用独立的人像 check-portrait-rules(S18 人像 ≥2 动效、禁变色、禁全屏组件等),不套全屏硬规则(辉哥 2026-08-05 定:两模式检查内容不同)。
Step 3. 写场景 HTML
🔴 强制第一步:深度理解口播稿(场景规划的源头,辉哥 2026-08-02 强制)
场景文件的核心源头是口播稿。只有深度理解口播稿内容和表达核心,才能写好场景文件。不深度理解 = 不写场景。
-
通读 script.md(口播稿)全文——理解整体结构、分几块、每块讲什么、情绪怎么走
-
场景↔口播对齐——按 scene-timing 时间轴,定位每个场景对应的口播段落
-
提炼每场景表达核心(一句话)——"这段在表达 X(讲什么),要传达 Y 感觉(情绪/重点)"
-
不明白就提问(不盲猜)——对某段表达核心拿不准/有歧义,列出疑问问辉哥,确认后再进视觉规划。提问用选择项方式(AskUserQuestion):给 2-4 个候选选项(含推荐的视觉方向)+ OTHER 供辉哥自定义,减少打字(辉哥交互偏好)
-
表达核心 → 视觉翻译——核心决定场景形态 + 动效/组件(内容驱动,不套模板;可自创动画)
-
写入 scene-plan.json 的 expressionCore 字段,写场景时按它执行
-
源头配套检查(规划时就位,防"牵一发动全身"):
| # | 配套 | 检查 | 缺了怎么办 |
|---|
| ① | 音频时间线 | SRT 锚点逐句对应场景 | 回场景描述稿/重新对齐 |
| ② | 场景描写 | 场景描述稿的视觉信息进规划 | 补充场景描述稿 |
| ③ | 元素规划 | 元素库(MOTION_LIBRARY §1)匹配 | 按表达核心选元素 |
| ④ | 组件核对 | components.css 有对应组件 → 用 | 没有 → 评估新建补库 |
| ⑤ | 动效匹配 | 强动效 Tier1,内容驱动 | 无匹配 → 自创(过 S18b/S32) |
| ⑥ | 字体规范 | 页眉页脚字体/字号/行距走 tokens | 用 tokens 变量,R14 |
🔴 强制:写场景前必须先读完以下文件(不可跳过):
references/SYNC_ENGINE.md — 理解 ct(音频时间) vs localT(挂钟时间)的分工
references/scene-creator.md — 完整阅读,尤其 §"⏱️ tick 契约" 和 §"🚫 ct 锚点必须按时间递增排列"
- 当前主题的
assets/tokens.css — 知道有哪些 --hf-* 变量可用
scripts/check-scene-prerequisites.mjs — 逐条读全部检查规则(R3动效种类/R10 DOM守卫/R12间隔≤2.5s/R14字号/R15对齐SRT/R18组件),写场景时一次满足,避免"写→被打回→修"反复
scripts/check-hard-rules.mjs — 重点读 S10 offset一致性 / S18动效≥3 / S22 beat递增 / S27多阶段≥2 / R2c至少1锚点
scripts/check-layout-quality.mjs — 读 R_LAYOUT2 布局判定逻辑,规划时就安排不同布局模式
references/LAYOUT_ENGINE.md — overload 规则:内容超 92 字或每秒超 16 字 → 必须拆分 scene 或强制分栏。规划时就按内容量决定布局,不要等渲染后溢出再改
references/components/LAYOUTS/ — 场景骨架优先用组件库(scene-two-column 分栏 / scene-demo 流程 / scene-stats 数据等),别手写竖排堆叠,最容易溢出视口
references/MOTION_LIBRARY.md — 元素库 × 动效库(辉哥 2026-08-02 强制)。每种元素(文字/数据/卡片/标签/流程/图形/结构/状态/转场/界面模拟)必须配对应强动效,规划时按 §3 匹配矩阵分配,禁止 fadeUp 应付
references/SCENE_PREFLIGHT.md — 源头治理规范(辉哥 2026-08-02 强制)。深度理解口播稿是场景规划源头:通读→场景对齐→提炼表达核心→不明用选择项提问→视觉翻译→写 expressionCore
references/SCENE_CHECKLIST.md — 写场景必过清单(辉哥 2026-08-02 强制)。把 R1-R18/S1-S37/R_LAYOUT 全部规则浓缩成可勾选清单。写第一个场景前逐条读内化,写时对着写,写完自查,一次写对。批量写 + 批量查,不逐个往返。
references/SCENE_PLANNING.md — 场景规划方法论(辉哥 2026-08-02 强制)。规划的本质(匹配层)、流程(深度读口播稿→语义切分→expressionCore 深度设计→布局)、每场景 6 项要素、规划铁律、动效匹配语义参考。Step 1d 必读,规划对了后面不修补。
血训(2026-08-02):不读检查脚本就写场景,被规则打回十几轮,浪费一半时间。写第一个场景前先通读三条检查脚本的规则定义 + SCENE_CHECKLIST,一次满足全部约束。
血训(2026-08-02)· 内容溢出:scene-10/14 规划时没按内容量选布局,生成后竖排堆叠溢出 16:9 视口,事后分栏改很麻烦。规划时内容多就分栏/加场景(LAYOUT_ENGINE overload 规则),别等预览后溢出再修。
写场景时禁止直接复制 _layout.html 或 _type-*.html 的 JS 部分作为标准写法。 这些文件是"机制说明书",不是模板。照抄 = 违规。
规则和脚本检查都是辅助措施,画面效果才是目的(辉哥 2026-08-02 定):检查脚本过 ≠ 画面好——脚本只是最低门槛,真正要的是"这段音频内容用视觉表达得够不够有力"。写场景核心永远是"这段内容怎么用视觉表达最有力",检查是最后的兜底确认。为画面效果做场景,不为过检查做场景。规则是参考不是束缚——内容表达需要时,规则之外可自创(符合强动效标准)。
写入后的硬性强制检查链(双层检查机制,辉哥 2026-08-02 强制,跳步=违规)
第一层:批量写完所有场景 → 全量检查统一修(高效节奏,辉哥 2026-08-09 改:取消单检逐个往返)
关键:写场景前先读 references/SCENE_CHECKLIST.md(写场景必过清单)内化全部规则,然后批量写完全部场景,直接跑全量检查统一修复。不要"写一个→打回→改一个"逐个往返,也不要用 check-scene-single 逐个检查(那是血训,18 个场景被打回十几轮 + 20260809 单检后 full-check 又暴露 R 系列 33 个错误,重复返工)。规则内化后一次写对率大幅提升,检查集中在全量一次做完。
# 批量写完所有场景后,直接跑全量检查(包含前置 R 系列 + 硬规则 S 系列 + 布局 + 视口 + 音频时长)
node "{skillRoot}/scripts/full-check.mjs" --project "{projectRoot}"
# 全量不通过 → 根据输出逐场景/逐规则统一修 → 再跑 full-check 直到全绿
布局提前拦截:full-check 的布局检查会读取全部场景,连续 3 个场景布局相同 → 报 R_LAYOUT2。批量写完一起修即可,不用边写边拦。
第二层:全场景汇总检查(全部场景完成后必跑,全过才进入音频/渲染)
所有场景写完 + 第一层全过后,跑完整汇总。不通过不准通知辉哥:
node "{skillRoot}/scripts/full-check.mjs" --project "{projectRoot}"
# 前置 + 硬规则全场景 + 布局 + 视口溢出 + 音频时长
# 输出场景级汇总表(每场景 ✅/❌ + 错误/警告数)
| 检查 | 检测什么 | 不通过的后果 |
|---|
| check-scene-single.mjs(写场景唯一入口) | 前置(R系列+R12间隔≤3s)+硬规则(S系列: S2色板/S27多阶段/S33 mockup/S26动画类等)+布局+视口 | 不修不能写下一个场景 |
| check-scene-prerequisites.mjs | 仅供 debug 用基础规则(--file 只查 R 系列,不能替代 check-scene-single,血训 2026-08-08 --file 假绿 S 系列全漏) | 不修不能写下一个场景 |
| check-layout-quality.mjs | 布局模式不重复/根容器居中/行距≥1.7/字号≥52px | 不修不能进入预览 |
| check-hard-rules.mjs | main.js正确性/索引结构/时间戳一致性 | 不修不能进入预览 |
| 浏览器预览 | 音画同步/内容匹配/活动效可见 | 不修不能通知用户 |
| check-viewport-overflow.mjs | 元素超出 16:9 视口(内容太多→分栏/加场景) | 不修不能确认 preview-ok |
🔴 GATE 1:批量写完所有场景后,直接跑 full-check.mjs 全量检查统一修(辉哥 2026-08-09 改:不是"写一个查一个",也不是单检逐个往返;改为批量写完全部 → 全量检查一次修完)
读 references/scene-creator.md 作为完整协议(含代码示例),然后对 scene-timing.json 中每个场景写 scenes/NN-name.html。
每个场景是一个独立片段:<style> + HTML + <script> 三段,不含 <html>/<head>/<body>。
scenes/ 下有类型参考文件(_type-*.html),可直接复制改名后改内容。
组件库定位(参考手册,不是死规矩)
- ✅ 有合适的组件 → 抄现成的(
references/components/MICRO|MACRO|LAYOUTS|DECORATIONS/*.md)
- ✅ 没合适的 → AI 自由发挥
- ✅ 发挥出好东西 → 反哺
components/,后续项目复用
- ✅ 用户指定 → 必须去
components/ 找
- ⚠️ 特定语义动效不强行通用化(齿轮咬合教训):动效是内容的表达不是套壳。从旧项目提炼动效看通用性 > 特定性——gear-spin(齿轮咬合)这类"AI 根据特定语义生成的动画"通用性低,只对匹配内容用,不归入通用 Tier 1。写场景先想"这段内容要表达什么动作",再从组件库匹配或自创。
场景切片原则(视频表现第一位 · 灵活但有护栏)
核心:场景是匹配层,按音频时间段拆分,用视觉表达每段音频内容。切分灵活但有限制(辉哥 2026-08-02 定,防 AI 乱切):
🔒 三护栏(缺一不可,违反=违规):
- 断点必须锚定 SRT 语义断点 — 场景边界必须是 SRT 字幕语义断点(一段表达核心的起止),禁止拍脑袋切(等距/固定间隔/凭感觉都是违规)。拿不准就锚定后继场景首条字幕的开始时间。
- 单场景时长锁定区间 4-12s — 灵活只发生在区间内(内容多/关键 → 偏 8-12s;过渡/简短 → 偏 4-6s)。低于 4s = 切太碎(视觉没展开就走),高于 12s = 切太长(视觉撑不满空洞)。区间外先调切片,不是硬撑。
- 视觉充实兜底(脚本强制) — 每场景视觉表达必须填满时长:元素不够 → 补辅助元素(图标/标签/装饰/状态);动效不够 → 补动态元素(强动效/流动/扫描)。写完后 S18b(强动效≥3)/ R12(间隔≤2.5s)/ 视口溢出检查拦截,不过就打回。
同一段音频(如 60s)可切 10 场景(5s/个,节奏快信息密)或 5 场景(10s/个,从容深入)或混合(5s/8s/10s) —— 切分依据 = 每段音频表达的核心意思,不是固定字数/固定条数。宁可 20 帧画面丰富,不要 8 帧每帧死长。
🔒 护栏4(场景总数锚定基准,血训 2026-08-02:35→18):
- 场景总数参考历史项目基准:5-6 分钟口播视频 ≈ 15-20 个场景(实测基准:20260802-de-ai-skills 项目 373s / 18 场景 ≈ 20.7s/场景)。
- 一段字幕 ≠ 一个场景:字幕时间线零碎,必须几段字幕组合成一个完整语义 → 一个场景。若 5-6 分钟切出 30+ 个场景 = 碎片化(血训:首次规划 35 个 → 辉哥"切换太频繁、画面元素不高密度" → 重切 18 个)。
- 切换对观众无感:靠场景内高密度元素 + 多动效撑满视觉、制造节奏,不靠频繁切场景。频繁切换反而显碎片化低质。
- 场景少 → 每个场景内容要更密:场景数量 15-20 时,每个场景必须 5-8 元素 + ≥3 种动效撑满时长(护栏3 视觉充实兜底),不是靠切场景补密度。
例外:金句收尾帧(quote/finale)可以少于常规元素密度,但时长仍受区间约束。
场景质量自检清单(写完后强制逐条检查,不通过不能提交)
写完每个场景 HTML 后,必须逐条确认:
写场景前的强制要求
写每个场景 HTML 前,必须:
- 读
_type-*.html 骨架 — 找到对应的视觉类型文件,读它的 CSS 结构和动画模式,基于它改编,不从头写
- 对照场景描述稿的元素清单 — 场景描述稿写了几条元素,场景里就必须做几个,不能少
- 对照参考场景 — 从最近一个已验证通过的项目中找同类型场景,看它的密度和动画丰富度
- 打开 QUALITY_GATES.md 逐条对照 GATE 1 清单 — 不凭记忆写,必须逐条看
- 打开 audio.srt,列出当前场景的每句字幕时间戳 — 每个
if(ct>=X) 必须对应一条 SRT 时间戳,写在注释里
- 布局规划 — 连续 3 个场景不能使用相同布局模式(grid/compare/timeline/cards-row/sparse 至少轮换)
写场景时的硬禁则
- 🚫 严禁用 sed 修改场景 JS — sed 多行替换会破坏 JS 语法。改 JS 一律用 Read + Write 完整覆盖文件(已坏 3 次,浪费数小时)"
- 🚫 禁止用 Python 读写场景 HTML 文件 — Python 的 GBK 默认编码会彻底破坏 UTF-8 中文字符,导致 HTML 内容乱码、JS 注释吞代码、typewriter 字符串显示乱码。一律用 Node.js 脚本或手动编辑。
- 🚫 严禁在 JS 中使用含中文的
// 单行注释 — 编码损坏时 // 注释可能丢失换行符,同一行后续代码被静默注释掉,造成花括号不匹配的语法错误。所有注释改用 /* */ 块注释,或使用英文。
- 每个
if(ct>=X) 必须对应 SRT 时间戳 — 在 tick 函数上方注释中写明 SRT 映射。禁止按固定间隔(0, 0.5, 1.5...)拍脑袋
- 禁止硬编码
background:#fff 或任何色值 — 所有颜色走 var(--hf-*),否则换主题后白底白字不可见
写场景后的双重检查
写完每个场景后:
- 开发者自查(执行者做)— 逐条过自检清单,不满足重写
- 编码完整性检查 — 搜以下模式确认无乱码:
grep -P "[^\x00-\x7F]" scenes/scene-NN.html 检查非 ASCII 字符是否都是正确的中文
- `grep "�" scenes/scene-NN.html" 检查 U+FFFD 替换字符
grep "txt:'" scenes/scene-NN.html" 检查 typewriter 参数字符串是否为正确中文(如发现 txt:'鍙欓潰...'` 则是乱码,需替换)
- 用户验收前不可提交 — 自查通过后才能让用户看,不能让用户当测试员
不满足任一条件 → 必须重写该场景,不能跳过进入下一步。
⚠️ 强制:普通人听得懂检测
口播稿不是文章,是让人说的,也是让普通人听的。 任何专业术语、书面表达、AI 腔必须清零。
写完后必须过 references/koubo-script-guide.md 中的"普通人听得懂检测"清单。但凡有一句让不做这行的人皱眉头,重写。
重点清洗词: 节奏卡点、特效动效、版式镜头、工具链、结构化的、可执行的、可量化的、提炼、提取、复刻、对标、视觉风格——全部换成大白话。"我妈妈能不能听懂?"听不懂就换。
硬规则(详见 references/scene-creator.md)
- 元素初始状态:所有需要动画揭示的元素必须 CSS 写
opacity:0
- CSS reveal 选择器:fullscreen 用
#mount.reveal-XX .element;portrait-center 用 #mount-left.reveal-XX .element 或 #mount-right.reveal-XX .element
- 视觉领先:元素出现时间控制在对应口播 beat 起始前 0.08–0.18s,最大 0.35s
- 动画时长:入场 0.5s–0.7s(不短于 0.35s),infinite 全片不超过 2 处
- 同时运动元素不超过 2 个
- 长场景(>5s):必须加 JS 驱动的持续进度/状态(数字计数、进度条、播放头走位)
- 🚫 position:absolute;inset:0 覆盖父级 padding(v2 血训):场景内子元素用
position:absolute;inset:0 做全容器覆盖时,父容器的 padding 对绝对定位子元素完全无效。错误写法:.wrap{padding:60px} → .x{position:absolute;inset:0} → 子元素覆盖到 padding 区域外。正确写法:.x{position:absolute;inset:0;padding:60px} 或在中间加一层 position:relative 容器。每次写 inset:0 后必须在浏览器肉眼确认元素位置。
- 严禁:GSAP / Anime.js 等外部库、呼吸动效、抖动、场景内用
id="mount"、内嵌 <audio>
- 🎨 主题可切换性(v2 硬规则):场景
<style> 内禁止硬编码颜色值。任何 #RRGGBB / rgb() / rgba() 必须来自 var(--hf-*) 或 color-mix(in srgb, var(--hf-primary) 20%, transparent)。唯一例外:纯黑 #000 / 纯白 #fff / transparent 用于中性阴影/描边可豁免。违反 = 切主题后白底白字,必须重写。
- 🎨 开场(opener)颜色规则(v4):index.html 内的开场动画 CSS 颜色必须用
var(--hf-*) + color-mix(),JS 粒子颜色必须从 AX.theme.pri / AX.theme.sec 读取(main.js 已加载 loadThemeColors()),需半透明用 AX.hexRgba(AX.theme.pri, 0.3) 转换。禁止 JS 里写 '#00e5ff' / 'rgba(0,229,255,0.3)' 等硬编码。示例:
// 正确
spawnParticles(cx, cy, 60, AX.theme.pri, 8);
spawnParticles(cx, cy, 40, AX.theme.sec, 6);
spawnParticles(cx, cy, 2, AX.hexRgba(AX.theme.pri, 0.3), 2);
// 错误
spawnParticles(cx, cy, 60, '#00e5ff', 8);
🔴 GATE 1:场景质量门禁(批量写完统一执行)
批量写完全部场景后,打开 references/QUALITY_GATES.md,逐条执行 GATE 1 检查清单。结构正确性/主题安全性/RAF安全性/视觉效果 四组全部通过,才算写完。不是"写一个查一个"(逐个往返浪费时间,血训 18 场景打回十几轮;批量写→批量查→统一过 GATE 1,效率高且一次写对率靠规则内化保证)。
硬约束:连续 3 次不通过 → 停,反思方案假设,重读 QUALITY_GATES.md 的"血训记录"章节。
Step 4. 预览
⚠️ 绝对禁止用 python -m http.server:Python 内置服务器不响应 Accept-Ranges,浏览器无法对 audio/video 做 seek。表现为:键盘切帧无效、进度条拖不动、audio.currentTime = X 静默失败、"最后一帧看不到"。必须用支持 HTTP Range 请求的服务器。
推荐用项目根目录里的 _serve.js(本技能模板已内置):
cd {projectRoot}
node _serve.js 3009
或用其他支持 Range 的静态服务器:npx serve、npx http-server -p 3009、Nginx。验证方法:
Invoke-WebRequest -Uri "http://localhost:3009/audio.mp3" -Method Head |
Select-Object -ExpandProperty Headers
响应头必须包含 Accept-Ranges: bytes。没有 = 换服务器。
浏览器打开 http://localhost:3009/,验收网页交付物。
这就是"网页交付形态"——独立可播放,永远存在。
🔴 GATE 2 + GATE 3:main.js 正确性 + 音画同步(预览前执行)
预览前,打开 references/QUALITY_GATES.md:
- 逐条执行 GATE 2(main.js 正确性)— 主 tick 不死的验证路径、
started 不是 kill switch、show() 不杀 tick
- 运行
node scripts/check-hard-rules.mjs --project "{projectRoot}",结果必须 0 error
- 逐条执行 GATE 3(音画同步)— 浏览器逐帧检查内容匹配、播放模式验证
GATE 2 或 GATE 3 任一不通过 → 修复,重新运行检查,不能跳过。
Step 5. 渲染 MP4(可选)
🔴 渲染前先引导用户选择渲染方式(辉哥 2026-08-05 定):
流程:
- 渲染前向用户展示几种渲染方式 + 各自效果和用时,让用户选择(AI 不擅自替用户定)
- 默认 = 标准质量 MP4(FFMPEG 逐帧精确)——质量与体积均衡
- 用户不选择 → 等待 30 秒 → 用浏览器录屏方式渲染(兜底,先出片给用户看)
- 渲染后效果不满意 → 用户选择更高质量方式重渲
| 方式 | 质量 | 用时(5 分钟视频) | 适用 |
|---|
| 标准质量 FFMPEG(默认) | 标准 | 20-30 分钟 | 默认推荐,质量与体积均衡 |
| 高质量 | 近乎无损 | 更慢 | 追求画质、正式交付 |
| 浏览器录屏 | 一般 | 最快 | 快速出片 / 兜底(用户 30s 不选择自动用) |
渲染方式有五种,按优先顺序:
方式 A:CDP 逐帧精确渲染(推荐 · 音画零漂移)
scripts/render-mp4-seek.mjs
只有这一种方式能保证长视频音画精确同步。screencast 连续截帧(方式 B/C)的帧节奏由浏览器合成器决定,固定 30fps 编码后会累积漂移——5 分 39 秒的视频只截到 331.67s,画面越到后面越超前(20260730 血训)。正式交付必须用本方式。
node "{skillRoot}/scripts/render-mp4-seek.mjs" "{projectRoot}" --output output.mp4
🔴 CHECKPOINT:渲染前必须问用户画质——三档预设:
| 画质 | 参数 | 效果 |
|---|
high(高质量) | CRF 16 · JPEG 95 · preset slow | 视觉近乎无损,文件大,编码稍慢 |
standard(标准,默认) | CRF 20 · JPEG 90 · preset medium | 质量与体积均衡 |
general(一般) | CRF 23 · JPEG 85 · preset veryfast | 快速出片,文件小,适合预览 |
node "{skillRoot}/scripts/render-mp4-seek.mjs" "{projectRoot}" --output output.mp4 --quality high
可选参数:
--width 1920 --height 1080 # 输出尺寸
--fps 30 # 帧率
--quality high|standard|general # 画质预设(默认 standard)
--settle 45 # seek 后等待页面渲染的毫秒数(渲染提前量随动)
--limit 6 # 只渲染前 N 秒(冒烟测试用)
--output output-sync.mp4 # 输出文件名
原理:逐帧 audio.currentTime = k/FPS(音频时钟精确驱动)→ 页面按音频时钟切换场景/触发动效 → settle 后 Page.captureScreenshot 截帧 → 管道送 FFmpeg → mux audio.mp3。渲染脚本内置 HTTP 服务已实现 Range 支持(无 Range 时音频 seek 全被重置、视频全黑)。
缺点:逐帧 seek+截图较慢(5 分钟视频约 20-30 分钟)。可以先 --limit 10 冒烟测试再全量。
渲染前自动校验(fail-fast):脚本启动时自动运行 verify-timeline.mjs,校验 audio.mp3 时长 ↔ audio.srt 最后时间戳 ↔ scene-timing.json 边界 三方对齐(含场景边界对齐 SRT 字幕起点、场景边界连续性、场景文件齐全)。任一 FAIL → 直接拒绝渲染,不浪费时间。也可手动提前跑:
node "{skillRoot}/scripts/verify-timeline.mjs" --project "{projectRoot}"
渲染中时钟漂移检测(浏览器问题主力):逐帧 seek 后回读 audio.currentTime,对比期望帧时间。漂移 >0.3s 记警告、>0.5s 记失败,失败帧占比 ≥5% → 直接拒绝出片。浏览器 seek 不精确 / 音频未就绪 / HTTP Range 缺失都会触发(这是画面与字幕时间线错位的根源)。
渲染后必检(脚本自动):
- 时长:
ffprobe 视频时长必须 ≈ audio.mp3 时长,偏差 >0.5s 即异常
- 帧数:
ffprobe -count_frames 视频帧数必须 ≈ 预期帧数(±1%,抓管道丢帧导致时间轴缩短)
渲染后一键验证:node "{skillRoot}/scripts/check-render-frames.mjs" "{projectRoot}" — 自动完成 时长/帧数对齐 + 抽帧 OCR 验证画面内容 + PIL 像素分析网格线清晰度(间隔 56px、对比度 >4 即清晰)。无网络加 --no-ocr,无 python/PIL 加 --no-grid。
方式 B:CDP 管道实时截帧(快速 · 长视频有漂移风险)
scripts/render-mp4-pipe.mjs
⚠️ 仅适合短视频(<1 分钟)或快速预览。长视频会累积音画漂移(原因见方式 A)。
无窗口无声音,后台默默截图走管子。
一次性依赖安装(每个工作区装一次即可):
npm i chrome-remote-interface --save-dev --prefix "{skillRoot}"
一行命令渲染:
node "{skillRoot}/scripts/render-mp4-pipe.mjs" "{projectRoot}"
可选参数:
--width 1920 --height 1080 # 输出尺寸
--fps 30 # 帧率
--port 9226 # CDP 端口起始
--http-port 3022 # 静态服务端口起始
--chrome "path/to/chrome.exe" # 显式指定 Chrome 路径
--output output-pipe.mp4 # 输出文件名
原理:起本地 HTTP → headless Chrome + CDP 截图 → 管道直送 FFmpeg(不写磁盘) → mux audio.mp3 → output.mp4
优点:不写临时文件,省约 1 分钟(无需 FFmpeg 二次编码)。
缺点:管道在 Windows 上可能偶发背压问题。
输出:{projectRoot}/{output文件名}
方式 C:CDP 写文件模式(兜底 · 有漂移风险)
scripts/render-mp4.mjs
⚠️ 仅适合短视频或快速预览(漂移原因见方式 A)。当管道模式异常时,用这个:
node "{skillRoot}/scripts/render-mp4.mjs" "{projectRoot}"
可选参数:
--width 1920 --height 1080 # 输出尺寸
--fps 30 # 帧率
--port 9222 # CDP 端口
--http-port 3009 # 静态服务端口
--chrome "path/to/chrome.exe" # 显式指定 Chrome 路径
--keep-frames # 保留临时帧不删(调试用)
原理:起本地 HTTP → headless Chrome + CDP 逐帧收 JPEG → 写磁盘 → FFmpeg 拼帧 + 塞 audio.mp3 → output.mp4
优点:多一道 FFmpeg 重编码,文件更小(CRF 20 质量可控)。
缺点:多写 14000 个临时文件(~1.5GB),多约 1 分钟。
输出:{projectRoot}/output.mp4
方式 D:FFmpeg gdigrab 自动录屏(需弹窗 · 有漂移风险)
scripts/render-mp4-gdigrab.mjs
⚠️ 仅适合短视频或快速预览(漂移原因见方式 A)。
弹一个可见 Chrome 窗口在 (0,0) 位置,FFmpeg 用 gdigrab 直接录窗口画面。
node "{skillRoot}/scripts/render-mp4-gdigrab.mjs" "{projectRoot}"
可选参数:
--width 1920 --height 1080 # 输出尺寸
--fps 30 # 帧率
--chrome "path/to/chrome.exe" # 显式指定 Chrome 路径
--output output-gdigrab.mp4 # 输出文件名
原理:开可见 Chrome 窗口 → FFmpeg gdigrab 录窗口画面 → CDP 触发播放 → 播完停录 → mux 音频
优点:录制期间画面即时可见,编码效率由 FFmpeg 显卡/CPU 决定。
缺点:必须弹窗且不能被遮挡,录制时屏幕 (0,0)-(1920,1080) 区域被占用。
输出:{projectRoot}/{输出文件名}
兜底路径:OBS 手动录屏
见 references/RENDER_MP4_MANUAL.md。用于 CDP 自动化异常(Chrome 版本不兼容、npm 装不上)时。
字体与布局系统
字体方案
所有字体从 Google Fonts 下载到本地,存放在 assets/*.ttf,通过 @font-face 加载,不依赖外部 CDN。
基础字体(默认继承):
| 字体 | Weight | 角色 |
|---|
| Inter | 400/500/600/700/800 | 英文、数字、标签、页码(通用) |
| Noto Sans SC | 400/500/600/700/800 | 中文正文、标题(跨平台统一) |
扩展字体(按场景匹配):
| 字体 | 感觉 | 使用场景 | 下载 |
|---|
| Archivo Black | 极粗冲击 | 开场 hook、CTA 号召 | 400 一个 weight |
| Space Grotesk | 几何科技 | 维度展示、步骤流程、结构说明 | 400/500/600/700 |
| Anton | 扁宽震撼 | 过渡场景、大数字 | 400 一个 weight |
| Playfair Display | 优雅衬线 | 引文、金句、收尾升华 | 400/700/400i |
| JetBrains Mono | 等宽硬核 | 数据、证据、统计数字 | 400/500/700 |
字体分配原则:
- 每个场景根元素显式设置 font-family(不依赖 body 继承,防止动态加载丢失)
- 眉标(eyebrow badge)、标签(tags)等 UI 辅助元素统一用 Inter
- 中文统一用 Noto Sans SC(或衬线搭配时用 Noto Serif SC)
- 大数字/标题用对应风格字体,正文保持不变
index.html 必须包含:
<style>
/* 全部 @font-face 规则在此,不依赖外部 CSS 文件 */
@font-face { font-family:'Inter'; font-weight:400; font-display:swap; src:url('assets/Inter-400.ttf') format('truetype'); }
/* ... 所有字体 ... */
html, body { font-family:'Inter','Noto Sans SC','PingFang SC','Microsoft YaHei',system-ui,sans-serif; }
</style>
布局模式
根据内容量选择布局,不预设固定规则:
| 布局 | 适合 | 特点 |
|---|
| 左竖线 + 单栏左对齐 | 内容量少的场景(1-2个元素) | 左侧 3px 竖线装饰,内容靠左,留白在右 |
| 中间竖线 + 左右分栏 | 左右内容均衡的场景 | 统一背景 + 中间 1px 渐变竖线分隔 |
| 居中布局 | 大数字过渡、纯文本标题 | 简洁有力,适合 6 秒内的过渡场景 |
基础 HTML 模板(左竖线单栏):
<style>
.sN{position:absolute;inset:0;font-family:'对应字体','Noto Sans SC',sans-serif;background:var(--hf-bg);overflow:hidden}
.sN .accent-rule{position:absolute;left:80px;top:10%;bottom:10%;width:3px;background:linear-gradient(180deg,transparent,var(--hf-primary),transparent);border-radius:2px;opacity:0;z-index:1}
.sN .content{position:absolute;left:120px;right:80px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column}
</style>
<div class="sN">
<div class="accent-rule"></div>
<div class="content">...</div>
</div>
基础 HTML 模板(中间竖线分栏):
<style>
.sN{position:absolute;inset:0;display:flex;background:var(--hf-bg);overflow:hidden}
.sN::after{content:'';position:absolute;left:38%;top:12%;bottom:12%;width:1px;background:linear-gradient(180deg,transparent,color-mix(in srgb,var(--hf-text-muted) 18%,transparent),transparent);z-index:1}
.sN .left{width:38%;display:flex;flex-direction:column;align-items:flex-end;justify-content:center;padding:0 40px 0 72px}
.sN .right{flex:1;display:flex;flex-direction:column;justify-content:center;padding:0 72px 0 44px}
</style>
<div class="sN">
<div class="left">...</div>
<div class="right">...</div>
</div>
关键原则
- 视觉冲击优先 — 不拘泥固定形式,根据内容选择最能突出信息的布局和字体
- 布局服从内容 — 左边内容少就单栏竖线,左右均衡就分栏,过渡场景居中
- 字体区分角色 — 标题用风格字体(Archivo/Anton/Playfair),正文用 Noto Sans SC,UI 元素用 Inter
- 全部本地加载 — 字体下载到项目
assets/ 目录,不在线引用 CDN
改进已有项目指南
用户可能会要求对已生成的项目做修改。以下是常见场景和操作流程:
场景 A:换主题
# 1. 选择新主题
node "{skillRoot}/scripts/select-theme.mjs" --query "暖色杂志风"
# 2. 重新应用主题到项目
node "{skillRoot}/scripts/init-project.mjs" `
--theme {newThemeId} `
--layout {layout} `
--project "{projectRoot}"
🔴 CHECKPOINT:换主题后必须检查所有场景的 CSS 颜色变量是否正常(greo #RRGGBB 确认无硬编码),预览确认无白底白字。
场景 B:修改场景内容
- 找到对应场景的
scenes/NN-name.html
- 修改内容后,用
_serve.js 预览局部修改
- 🔴 CHECKPOINT:确认修改后的元素无 syntax error(console 检查花括号平衡)
场景 C:重新渲染
# 逐帧精确渲染(推荐,音画零漂移)
node "{skillRoot}/scripts/render-mp4-seek.mjs" "{projectRoot}" --output output-v2.mp4
🛑 Note:重新渲染前确认音频时间轴未变,否则需同步更新 scene-timing.json。渲染后 ffprobe 对比输出时长与 audio.mp3 时长。
组件反哺机制
当你在场景里做出一个通用性强、视觉效果好的组件,完成后要做两件事:
- 提取:把 HTML/CSS 剥离出来写成
references/components/MICRO/xxx.md 或 MACRO/xxx.md
- 记录:更新
references/components/INDEX.md,加上组件名 + 用途 + 使用示例
每个项目都在为下一个项目积累素材,组件库越用越强。
引擎脚本一览
已内置(v2 生态):
| 脚本 | 作用 |
|---|
init-project.mjs | 初始化项目(复制模板 + 应用主题 + 扁平结构) |
render-mp4-seek.mjs | 逐帧精确 MP4 渲染(推荐,音画零漂移),启动时自动跑时间线校验 |
verify-timeline.mjs | 渲染前时间线校验(音频↔SRT↔scene-timing 三方对齐),FAIL 拒绝渲染 |
render-mp4.mjs | CDP 写文件模式 MP4 渲染(快速预览/兜底,长视频有漂移风险) |
render-mp4-pipe.mjs | CDP 管道模式 MP4 渲染(快速预览/兜底,长视频有漂移风险) |
render-mp4-gdigrab.mjs | FFmpeg gdigrab 自动录屏(弹窗可见) |
select-theme.mjs | 根据文本推荐主题 |
install-optional-theme.mjs | 安装扩展主题包 |
compress-script.mjs | ⚠️ 已废弃(无压缩稿概念)——仅保留给用户自录场景做录音稿参考,不参与 AI 配音流程 |
parse-srt.mjs | SRT 解析 |
build-timing-from-srt.mjs | SRT → scene-timing.json(auto-split 或 anchor) |
match-scene-timing.mjs | 锚点词映射 SRT 到场景 ID |
generate-tts-audio.mjs | 百度声音克隆 TTS |
generate-full-audio.mjs | 一站式:口播稿 → TTS + SRT(音频先生成,每句真实时间戳) |
test-tts.mjs | TTS 音色试听 |
人像模式独立体系(与全屏完全隔离,辉哥 2026-08-05 定):
| 脚本 | 作用 |
|---|
analyze-portrait-colors.mjs | 人像背景抽帧配色分析 → scene-colors.json(人像特有,必跑) |
generate-portrait-plan.mjs | 人像场景规划生成器 → portrait-plan.json + portrait-plan.md 人可读确认单(左右栏元素 + hero 主角 + 档位 + 时间轴 + motionPlan + rules,独立于全屏 generate-scene-plan) |
check-portrait-rules.mjs | 人像硬规则检查(M/I/P/S/T/A 组,独立于全屏 check-hard-rules) |
check-portrait-plan.mjs | 人像规划合规检查 R1-R9(按 portrait-plan.json 验证左右栏元素/档位/时间轴/时间锚定字幕) |
portrait-full-check.mjs | 人像全链路汇总检查(硬规则 + 规划合规 + 音频时长,全过才允许预览/渲染) |
内部库(scripts/lib/):
text-analysis.mjs — 场景类型推断、标题提取
visual-planning.mjs — 语义动作 / 视觉家族 / 布局变体推断
storyboard-planner.mjs — SRT 分组
layout-planner.mjs — 密度与安全区
sync-planner.mjs — Beat 揭示时间计算(视觉领先 0.12s)
layout-configs.mjs — 布局模式常量 + SYNC_CONFIG
theme-configs.mjs / base-themes.mjs / installed-extended-themes.mjs — 主题元数据