--- name: wjs-converting-text-to-video description: Use when the user wants a 王建硕-style WeChat article (article.md) turned into a narrated short MP4 video — TTS voiceover via 火山引擎 Volcano TTS, HyperFrames CSS/GSAP animation per scene, subtle SFX, abstract watercolor background, full pipeline rendering to 1080×1920 portrait MP4 (30-90s). Triggers — "把这篇文章做成视频", "做一个解说视频", "讲解视频", "/wjs-converting-text-to-video". --- # wjs-converting-text-to-video 把一篇王建硕风格的微信公众号 `article.md` 做成 **1080×1920 竖屏、30-90 秒** 的中文解说短视频:TTS 旁白 + HyperFrames CSS/GSAP 动画 + 抽象水彩背景 + 转场 SFX。输出 MP4 给视频号 / 抖音 / 小红书 / Reels。 ## What this skill produces | 维度 | 默认 | |---|---| | 尺寸 | 1080×1920 竖屏 (9:16) | | 时长 | 30-90 秒 | | Scene 数 | 5-10 | | 旁白 | 火山引擎 Volcano TTS,默认阿虎对话男声 | | 背景 | GPT Image 2 生成的抽象水彩 (`bg.png`) + blur 30 + 暖黑半透明 overlay | | 字体 | Noto Sans SC,hero 900,主文字暖奶白 | | 输出 | `/.mp4`(与 `video/` 平行,不放 `video/` 里)| | 发布 | 自动上传到 YouTube — Portrait → Shorts,Landscape → 普通 video;重新渲染会替换老视频(不累积)| ## When this skill fires - 用户已有 `article.md`,说「做成视频」「做一个解说」「讲一遍」 - 用户跑 `/wjs-converting-text-to-video ` - 用户说「把昨天发的那 X 篇都做成视频」之类的批量请求 ## When NOT to use - 没有文章稿,只是一个想法 → 先用 `/wjs-publishing-wechat` 写出 article.md,再来 - 用户要的是字幕烧录 / 翻译 / 配音替换 → 用 `/wjs-burning-subtitles` / `/wjs-dubbing-video` / `/wjs-localizing-video` - 视频要英文 / 西语等非中文 → 本 skill 专注中文 TTS (Volcano 火山引擎);非中文走 hyperframes 自带 tts 命令 (kokoro 英文还可以) - 横屏 16:9 → 本 skill 默认竖屏;横屏仅在用户明确要求时改 ## Core Principle **视频不是文章的可视化朗读,而是文章的视觉重构。** 每个 scene 是一个独立的视觉时刻 —— 一个对比、一个排比、一个数字、一个比喻。文字撑满屏幕,黑体加粗,重点字橙色高亮。背景是抽象水彩 (blur 后柔化),整体调子稳重、克制、有冲击力。 **节奏 > 模板**。一段 5-10 scene 的视频,如果从头到尾都是"两行对照"的同一种排版,就不是视频,是 slideshow。**现代感来自对比** —— 极端字号差、不对称布局、短 scene 与长 scene 交替、纯文字 scene 与几何元素 scene 交替、水彩底 scene 与亮色 punch scene 交替。 **默认是平庸的**。如果只是从模板表顶端挑几种最容易的,结果一定是"平铺直叙的两行格式"。强制走 [Step 1b Scene Mix Rule](#step-1b-scene-mix-rule强制) 配比。 ## Workflow ### Step 1: 设计 5-10 个视觉时刻 读 `/article.md`,按论证结构拆成 5-10 个 scene(控制在 30-90 秒总时长)。短文(核心 1-2 个要点)做 5-6 scene / 30-50s;长文 8-10 scene / 60-90s。每个 scene 一段叙述(旁白)+ 一个清晰的视觉骨架。 **模板表 —— 6 类共 16 种,按需混搭**: #### A. Hero / Punch(强对比 climax,每片 ≥1,时长 ≤4s) | 模板 | 适合 | |---|---| | **A1. 全屏单字 hero** | 1-3 字 climax 词撑满屏,字号 280-400px | | **A2. Outline hero** | 空心字 `-webkit-text-stroke: 4px #f5efe5; color: transparent;` | | **A3. Color-flip punch** | 整屏背景换亮色(橙/红/金/翠绿等),反白字 | | **A4. Gradient text hero** | 大字加 `background: linear-gradient(...); -webkit-background-clip: text;` | #### B. Contrast / 对照(反差结构,每片 1-2 个,时长 5-8s) | 模板 | 适合 | |---|---| | **B1. 双行对照 + strikethrough** | 「以前 X,现在 Y」「不是 A,是 B」 — **整片最多 2 个** | | **B2. 左右分屏对照** | 屏幕一分为二(可加竖线分隔) | | **B3. 对角线对照** | 左上 ↔ 右下,中间大量留白 | #### C. List / 结构(多项并列,每片 1-2 个,时长 6-10s) | 模板 | 适合 | |---|---| | **C1. N 个卡片横排** | 3-5 个并列,用深暖黑 + 单色边框 | | **C2. 垂直堆叠关键词** | 6-8 个排比项,可加大数字编号 01-08 | | **C3. 真网格** | 2×2 / 3×2 网格,每格图标 + 标签(竖屏宽度有限,4 列横排会挤) | | **C4. 阶梯 / 错位列表** | 每项 `margin-left` 递增 | #### D. Stat / 数据(数字 climax,每片 ≥1,时长 4-6s) | 模板 | 适合 | |---|---| | **D1. 数字 ticker** | 0 → N 滚动动画(`gsap.to({textContent})`)| | **D2. 数字 + 标签** | 主数字 200-400px + 60-80px 解释 | | **D3. 进度条 / 时间轴** | 横向 progress bar + 节点 | #### E. Quote / Climax(金句落点,每片 1-2 个,时长 6-10s) | 模板 | 适合 | |---|---| | **E1. 段落级 hero text** | 一句 60-100px 金句,左对齐 + 左侧 emphasis bar | | **E2. 大引号 + 内文** | 巨大半透明开引号作背景装饰 | #### F. 装饰 / 几何(节奏调味,可选) | 模板 | 适合 | |---|---| | **F1. 格子 + spinner / 进度条** | 多并发画面 | | **F2. 对话气泡 ↔ 回应** | 角色 A 说 → 角色 B 做 | **每个 scene 的旁白控制在 3-12 秒**(短 punch 3-4s,长 breath 10-12s,**不要全部都是 5-7s**)。所有 scene 加起来 **30-90 秒**,不要超过 90 秒。文章短就做短,5 个 scene × 6s = 30s 也是合格。 ### Step 1b: Scene Mix Rule(强制) **写完 5-10 个 scene 设计后,按下面 checklist 自查。任何一条不满足 → 回去调整。** #### 配比硬规则 - [ ] ≥1 个 A 类 / D 类 / C 类 / E 类 - [ ] ≤2 个 B1 模板(双行 strikethrough — 历史上最容易被滥用) - [ ] ≥1 个 A3 color-flip scene(亮色背景反白字) - [ ] ≥4 种不同的模板类型(A/B/C/D/E/F 至少 4 类) - [ ] ≤2 个连续 scene 用同一类 #### 节奏硬规则 - [ ] scene 时长跨度 ≥ 6s(最短 ≤ 4s、最长 ≥ 9s) - [ ] ≥2 次"短 → 长 → 短"或"长 → 短"节奏切换 - [ ] 字号跨度 ≥ 240px(最大 hero ≥ 320px,最小 ≤ 80px) #### 布局硬规则 - [ ] ≥2 个 scene 非居中(贴角、对角、左对齐、阶梯等) - [ ] ≥1 个 scene 留白占 ≥ 60% 屏幕(呼吸) - [ ] ≥1 个 scene 含几何装饰(粗线、色块、箭头、圆点、大编号) #### 配色硬规则 - [ ] **大部分 scene 没有 `background:` 色** — 让水彩 bg-image 透出;只有 A3 color-flip 才用纯色 bg - [ ] color-flip scene 颜色不只是橙/蓝/白(深红 / 深金 / 翠绿 / 青松 / 暗紫 等都可) - [ ] emphasis 至少 2-3 种颜色(技术词用蓝、价值词用金、增长词用绿、警告词用红) #### 反单调自检 1. 把所有 scene 截图缩成缩略图并排 — **能一眼分辨吗**?如果 8 个看起来一样 → 重做 2. 第 1、4、7 scene 的视觉密度是不是不一样?应该有的密、有的极简 3. 有"meta-rhythm"吗?比如 A 开场 → 3 个 B/C 展开 → D climax → E 收尾 — 比线性铺更有戏剧弧 ### Step 2: 写 `narration_chunks.json` ```json [ {"id": "s01", "text": "我们以前,是 AI 的领导。现在,我们就是它的维修工。"}, {"id": "s02", "text": "..."} ] ``` **写旁白细节**: - 比 article.md 更口语、更短促,逗号/句号多用让 TTS 自然停顿 - 数字 / 英文混排 OK("Claude Code"、"100 倍"),Volcano 都能读 - 不写括号注释、不写 `...`、不写破折号 `——`(TTS 会念出 "破折号" 三字) - 删掉 article.md 里的 `**加粗 markdown**`,只留纯文字 - **去掉百姓网相关 facts**:article.md 里如出现「百姓网」「百姓网现在 X 人」「百姓网员工」等都要 strip 或泛化("百姓网现在 158 个人" → "现实里没几个真人")。这是过时信息,不要进视频。同理 visuals 不要出现 "百姓网" label 或 "158 人" stat。详见 [[no-baixing-facts]] ### Step 3: 生成 TTS narration ```bash cd /video python3 tts_narration.py ``` 脚本默认用 `zh_male_ahu_conversation_wvae_bigtts`(阿虎对话)— 段间插 0.35s 静音,输出 `narration.mp3` + `timing.json`。 **Volcano TTS 注意事项(踩过的坑)**: - 用 resource `volc.service_type.10029`,speaker 选 `zh_*_*_bigtts` - **绝对不要传 `emotion` / `emotion_scale`** — 大部分 `_bigtts` 声音会返回 `data: null` 静默失败 - **绝对不要用 kokoro**(hyperframes 自带 tts)— 中文质量差,用户明确不接受 - **避免** `zh_male_jieshuonansheng_mars_bigtts` — 含英文专名(如 "Claude Code")会循环 hallucinate **备用声音**(按推荐顺序): - `zh_male_ahu_conversation_wvae_bigtts` (阿虎对话) — 默认,自然口语 - `zh_male_M392_conversation_wvae_bigtts` — 同 wvae 系列 - `zh_male_wennuanahu_moon_bigtts` (温暖阿虎) — 更暖、播音感 - `zh_male_silang_mars_bigtts` (思朗) — 沉稳思考,戏剧感强 - `zh_male_baqiqingshu_mars_bigtts` (霸气) — 更有力度 切声音:`python3 tts_narration.py --voice zh_male_silang_mars_bigtts` ### Step 4: 生成水彩背景图 bg-image 是视觉主基调(柔化的抽象水彩)。**不要用 article 的 `illustration.png`** — 手绘示意图细节太多,blur 后变成均匀深色泥(视觉上仍是纯黑)。必须用专门生成的抽象水彩。 ```bash ~/.claude/skills/wjs-converting-text-to-video/scripts/generate-bg.sh ``` `` 选(根据文章主题): | theme | 色板 | 适合 | |---|---|---| | `personal` | bright warm yellow, soft coral pink, terracotta, sage green, cream | 个人、手作、温暖 | | `tech` | cool teal, electric blue, deep purple, mint, white | AI、技术、数据 | | `reflection` | sage green, dusty blue, lavender, pearl, cream | 反思、沉静 | | `warning` | burnt orange, deep red, mustard, charcoal | 警示、张力 | | `growth` | fresh green, gold, soft yellow, sky blue | 增长、复利 | | `abstract` | lavender, dusty rose, sage, soft amber | 抽象、哲思 | 输出:`/video/bg.png` (1088×1920, ~3MB)。 **⚠️ 图片必须在 `video/` 目录内** — 不能用 `../illustration.png`,hyperframes render 不解析跨目录相对路径,会渲染成纯黑。 ### Step 5: 写 HyperFrames composition (`index.html`) 读 `timing.json`,按每个 chunk 的 start/end 设计 scene。竖屏 1080×1920 结构: ```html
``` #### 🎬 第一帧规则(硬性) 视频 **t=0** 必须包含: 1. **bg-image 完全可见** — 永远 opacity 1,从不 fade-in(CSS 默认就可见,别在 GSAP 里改它的 opacity) 2. **标题元素可见** — s1 的主要标题元素 `tl.from({y:30, scale:0.95})` 可,但 **不要** `tl.from({opacity:0})`,否则 t=0 就是黑屏 3. **s1 不能是 A3 color-flip** — 否则盖住 bg-image,第一帧就看不到水彩。color-flip 留给 s2+ #### 色彩系统 **主文字 / 锚定色(design system,全片一致)**: | 角色 | 值 | 用法 | |---|---|---| | 主文字 | `#f5efe5` 暖奶白 | hero / 主要内容 | | 二级文字(副标题、caption)| `#f5efe5` + `opacity: 0.7` + 小字号 | **不要用灰色**(`#8a7e72` 在水彩底上看不清)。用 opacity + 缩字号做 hierarchy | | 划掉文字本身 | `#f5efe5` + `opacity: 0.5` + strikethrough line | **不要用 `#6d635a` 暗灰** — 在水彩底上看不清。改用 opacity 弱化 + 橙色 strike line | | 装饰大编号(01-08)| `#f5efe5` + `opacity: 0.18` 或 `#e87a3e` + `opacity: 0.35` | **不要用 `#2b2620` 等深灰**(水彩底上完全消失)| | Outline 描边 | `#f5efe5` 4-8px stroke + `color: transparent` | A2 空心字 | | 默认 fallback bg | `#0e0b08` 深暖黑 | 被 bg-image + overlay 覆盖;color-flip 不用 | **核心原则:所有文字用 `#f5efe5` cream 或 `#e87a3e` 橙系(accent palette),用 opacity + size 做 hierarchy,不用色相变化。灰色是黑底时代的遗物,水彩底上一律不用。** 详见 [[no-low-contrast-text]] **Color-flip 背景 palette(A3,不只是橙/蓝/白)**: | hex | 适合 | |---|---| | `#e87a3e` 经典橙 | 警示、强调、climax punch | | `#6b9bc4` 亮蓝 | 数据、技术 climax | | `#f5efe5` 暖奶白 | 收尾、安静的反差 | | `#c45c3e` 深红 | 警告、错误 climax | | `#d4a040` 深金 | 成就、价值 climax | | `#5a8c6a` 翠绿 | 增长、复利、生命力 | | `#4a8a8a` 青松 | 冷静、长期主义 | | `#7a5a8a` 暗紫 | 智慧、神秘 climax | | `#c48a8a` 暗粉 | 柔软、人性 | color-flip 上的文字用 `#0e0b08` 或 `#f5efe5` 反相。 **Emphasis / Accent palette(不只是橙)**: | hex | 适合 | |---|---| | `#e87a3e` 橙 | 默认 emphasis | | `#6b9bc4` 蓝 | 数据、技术词、AI | | `#d4a040` 金 | 价值、成就 | | `#5a8c6a` 翠绿 | 增长、好结果 | | `#4a8a8a` 青松 | 长期、稳定 | | `#c45c3e` 深红 | 警告、反差 | | `#8a7aaa` 暗紫 | 抽象、智慧 | | `#c48a8a` 暗粉 | 柔软、人性化 | **整片 emphasis ≥ 2-3 种**,根据 scene 主题选 accent。 #### 字体系统(**竖屏 1080 宽**) | 项 | 值 | |---|---| | 字重 | hero 900 / 主文 800 / 二级 600-700 / caption 500 | | 字距 | hero `-0.04em` 到 `-0.06em` / 主文 `-0.02em` / caption `0` | | Punch hero (A1/A2,1-3 字) | 280-400px | | 短句 hero (4-6 字) | 160-240px | | 长句 hero (7-10 字) | 100-150px | | 卡片内容 | 56-130px | | 副标题 | 40-72px | | Caption / 序号 / 标签 | 20-40px | #### 布局系统(**反居中惯性**) | 布局 | CSS 关键 | 适合 | |---|---|---| | 居中 | `flex; center; center;` | A 类 hero,但 ≤50% scene | | 左对齐贴顶 | `padding: 80px 80px 0 80px;` | E 类金句、长 quote | | 右下角锚定 | `position: absolute; right: 80px; bottom: 80px;` | 落款、climax 词 | | 对角线 | top-left / bottom-right | B3 对角对照 | | 网格 | `display: grid; grid-template-columns: repeat(2, 1fr);` | C3(竖屏 2×N 而非 3×N)| | 阶梯 | 每项 `margin-left: calc(60px * var(--i));` | C4 错位列表 | | 贴底 + 上方留白 | `position: absolute; bottom: 60px;` 上方空白 | 呼吸 scene | | 边角小元素 | 文字小贴一角,其他全空 | 极简 / 留白 punch | **Padding**:撑满型 40-80px,呼吸型 120-200px。不要所有 scene 都用同一个 padding。 #### 几何装饰元素 每隔几个 scene 用一个: - **粗短线** 8-16px × 40-200px,emphasis bar,橙色 - **左侧 emphasis bar** 6px × 100%,配长 quote - **大数字编号** 01-08,list 项序号(淡灰、巨大、装饰性) - **大引号字符** `"` 半透明超大置左上 - **横向分隔线** 2-4px 奶白 30% 透明 - **圆点 / 方块** 12-20px、橙色,list bullet - **箭头** ➜ 或自绘 SVG #### Scene 转场(4 种 + 混用规则) **不要全片都 blur crossfade**。每 4 个转场必须 ≥2 种类型。 **T1. Blur crossfade**(默认柔和) - 0.6s,`sine.inOut` - 后 scene `opacity: 0, filter: blur(24px)` → `opacity: 1, filter: blur(0)` - 前 scene 同时 fade-out + blur **T2. White flash cut**(punch 切,最现代) - 0.18s 总长:60ms 白闪 → 切 → 40ms 新 scene scale 1.05 → 1 - 适合:进入 A 类 hero、D 类 stat、climax 切换 ```js tl.to('.flash', { opacity: 1, duration: 0.06, ease: 'none' }, T - 0.06) .set(prevScene, { opacity: 0 }, T) .set(nextScene, { opacity: 1 }, T) .to('.flash', { opacity: 0, duration: 0.12, ease: 'power2.out' }, T) .from(nextScene, { scale: 1.05, duration: 0.25, ease: 'expo.out' }, T); ``` **T3. Scale push**(推进感) - 0.55s,前 scene `scale: 1 → 0.85`,后 scene `scale: 1.15 → 1` - 适合:从概览推到细节 **T4. Color flash cut**(橙/蓝闪一下,强烈节奏) - 0.22s 总长:80ms 全屏橙 → 切 → 40ms 收 - 适合:进入 A3 color-flip 或关键转折 - **全片最多 2 次** flash overlay 在 HTML 里加 `
` 全屏定位、默认 opacity 0、z-index 100。 #### 入场动画规则 - 每个 scene 的每个元素都用 `tl.from(...)` 入场(y/opacity/scale) - 入场 stagger 0.1-0.3s;首元素 t = scene.start + 0.3 起 - ≥3 种不同 ease(`power3.out` / `back.out(1.3)` / `expo.out` / `elastic.out(1, 0.5)`) - **不要 `gsap.to({opacity: 0})` 退场** — 转场已处理。只有最后 scene 可 fade-to-black - **整片必须用到 ≥3 种** [Modern Motion Techniques](#modern-motion-techniques) ### Modern Motion Techniques 平庸视频和现代视频的差别一半在排版、一半在 motion。下面 7 种每片必须用 ≥3 种(特定 scene 用,不要全片堆)。 #### 1. Kinetic Typography(字符 stagger 入场)—— A 类 hero ```html

维 修 工

``` ```js tl.from('.kinetic span', { y: 180, opacity: 0, rotateX: -90, duration: 0.7, stagger: 0.06, ease: 'back.out(1.4)', transformOrigin: '50% 100%', }, T); ``` #### 2. Camera Punch(推近 / 拉远)—— A3、D 类 ```js tl.from(scene, { scale: 1.15, opacity: 0, duration: 0.5, ease: 'expo.out' }, sceneStart); ``` #### 3. Mask Reveal(clip-path 揭示)—— E 类 quote ```css .reveal { clip-path: inset(0 100% 0 0); } ``` ```js tl.to('.reveal', { clipPath: 'inset(0 0% 0 0)', duration: 0.9, ease: 'expo.inOut' }, T); ``` #### 4. Number Ticker(数字滚动)—— D1 ```html
0
``` ```js const ticker = document.querySelector('.ticker'); const obj = { val: 0 }; tl.to(obj, { val: parseInt(ticker.dataset.end), duration: 1.8, ease: 'power2.out', onUpdate: () => { ticker.textContent = Math.round(obj.val).toLocaleString(); }, }, T); ``` #### 5. Outline → Fill(空心字变实心)—— A2 ```css .morph { -webkit-text-stroke: 4px #f5efe5; color: transparent; } ``` ```js tl.to('.morph', { color: '#e87a3e', webkitTextStrokeColor: '#e87a3e', duration: 0.5, ease: 'power2.out' }, T); ``` #### 6. Letter Highlight Sweep(关键词扫光)—— E 类 climax 词 ```html 搭档 ``` ```css .sweep { position: relative; display: inline-block; padding: 0 8px; } .sweep-bg { position: absolute; inset: 0; background: #e87a3e; transform: scaleX(0); transform-origin: left; z-index: -1; } ``` ```js tl.to('.sweep-bg', { scaleX: 1, duration: 0.5, ease: 'power3.inOut' }, T); tl.to('.sweep', { color: '#0e0b08', duration: 0.1 }, T + 0.25); ``` #### 7. Background Color Punch(背景闪变)—— 全片 1-2 次 ```js tl.to(scene, { backgroundColor: '#e87a3e', duration: 0.08 }, T) .to(scene, { backgroundColor: '#0e0b08', duration: 0.4, ease: 'power2.out' }, T + 0.1); ``` **Strike-through 动画**:用真实 DOM `` 而不是 `::after`。伪元素 + CSS 变量在 hyperframes 某些渲染路径下不工作。 ```html 领导 ``` ```css .strike-line { position: absolute; left: -10px; right: -10px; top: 56%; height: 10px; background: #e87a3e; transform: scaleX(0); transform-origin: left; } ``` ```js tl.to('.strike .strike-line', { scaleX: 1, duration: 0.55, ease: 'power2.inOut' }, T); ``` ### Step 6: 加 SFX ```bash ~/.claude/skills/wjs-converting-text-to-video/scripts/synth-sfx.sh /video ``` 生成 `video/sfx/{tick,chime,bell}.mp3`: - `tick.mp3` — 80ms 1.2kHz sine,**转场用**(每次 scene 切换前 0.3s) - `chime.mp3` — 220ms 880+1320Hz 双音,对话/列表某项亮起时(**可选**) - `bell.mp3` — 1.5s 低频钟,最后 climax 词出来时(**全片最多 1 次**) **接入 timeline**: ```html ``` **⚠️ 每个 `