Communitygithub.com

Apollonny/stickman-explainer

从脚本生成纵向 9:16 手绘火柴人解说/故事视频(适用于 Instagram Reels、Shorts、TikTok),使用基于 JSON 故事板驱动的 Python 渲染器,支持奶油色纸背景、黑色墨水火柴人、红色强调色,以及跟随语音的字幕重点高亮。

stickman-explainer 是什么?

该技能让 AI 编码代理能够将简短脚本自动转化为完整的 1080×1920 纵向视频,风格为手绘火柴人动画。用户只需提供一个遵循特定 schema 的 JSON 故事板,`scripts/render.py` 便会逐帧绘制(使用抖动墨水线条、8fps 的线辐动画、淡入/擦除/弹出等转场),并通过 ffmpeg 编码输出。支持将语音中的单词时间戳映射到字幕,实现逐字高亮,使红色强调色与当前发音单词同步。渲染器在绘图前会进行严格校验,并给出明确的错误消息(而非 traceback),确保故事板数据的完整性。设适用于 Claude Code、Cursor 等支持 Python 脚本执行的 AI 代理环境,用户只需描述想要的视频类型(如“科普短片”、“历史趣闻”、“动画解说”),代理即可根据描述生成故事板并调用渲染器完成视频制作。

兼容平台✓Claude Code✓Codex CLI~Cursor✓Antigravity✓OpenCode
npx skills add https://github.com/Apollonny/stickman-explainer/tree/main/stickman-explainer

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

火柴人解说器

将简短脚本转换为完整的 1080×1920 mp4 视频。您编写一个 故事板 JSON;scripts/render.py 绘制每一帧(抖动墨水线条、8fps 的线辐动画、淡入/擦除/弹出转场)并使用 ffmpeg 编码。

assets/example_storyboard.json 仅作为语法示例(3 个场景);请勿将其布局或角色配饰作为模板直接复制。

视觉风格默认值

  • 使用 9:16 画布(1080×1920,30fps)。每个场景只表达一个想法,通常持续 2.5–4 秒,并在平台安全区内放置简短字幕。
  • 使用暖色纸(#f3ebdd)、深色墨水(#2b2723)和语义性砖红色强调色(#b8372d)。金色(#d89d48)可选,仅用于太阳主题。保持其他部分扁平:无渐变、阴影或 3D 效果。
  • 绘制简单的黑色墨水人物,面部清晰,留有大量留白。除非故事或用户明确要求,否则不添加头盔、王冠、斗篷、红色衣物等配饰。不要根据历史名称推断服装。
  • 保持场景可读:最多 2 个角色和 5 个小图标。让主要主体占主导;避免字幕和道具重叠。最多使用 2 行字幕。
  • 让复杂道具易于识别。绘制绳结时,要有清晰的交叉环、紧凑的中心和可见的绳头。绘制剑时,要有清晰的握把、护手和尖刃。切割后,两个绳头之间要留有可见间隙。
  • 当有语音时间戳时,让红色字幕强调色跟随当前正在发音的单词移动。为字幕中每个由空格分隔的单词添加一个 karaoke 区间;非激活的单词保持正常文字颜色。
  • 优先使用非对称布局并有意留白。底线、太阳、标题卡、气泡等元素是可选的;仅在它们支持场景时才使用。

工作流

  1. 脚本 → 场景。 将故事拆解为每句一个场景(字幕 ≤ 6 个单词,每个 2.5–4 秒)。模式:标题卡 → 钩子/冲突 → 具体揭示。每个场景恰好添加一条新信息。当提供语音时,将场景边界与口语句子对齐。
  2. 为每个句子选择一个视觉元素。 每个场景一个比喻:角色 + 道具、气泡或一行图标(X = “没有”)。保持 ≤ 2 个角色和 ≤ 5 个图标。
  3. 编写故事板(schema 如下)。使用单词级语音时间戳时,为字幕中每个由空格分隔的单词添加一个 karaoke 区间,以便红色强调色跟随正在发音的单词。仅当没有单词时间戳时使用 **word** 进行静态高亮;不要将静态标记与 karaoke 结合使用。
  4. 先检查静态图(快速,每场景约 2 秒): python scripts/render.py story.json --stills out/stills → 打开每个 scene_NN.png,修复重叠/放置问题,重复此过程。
  5. 草稿视频: python scripts/render.py story.json out/draft.mp4 --draft(无抗锯齿,12fps)。
  6. 最终版: python scripts/render.py story.json out/final.mp4 [--audio voiceover.mp3]。 场景时长应与语音句子的长度匹配;使用 ffprobe 测量音频并相应设置 duration。单词间隔是从场景开始算起的秒数,应来自语音对齐或仔细聆听。确保每个红色单词在相应的语音出现时才显示,而不是提前或滞后。

错误消息与验证

render.py 在绘制故事板之前会进行验证:未知的 type/pose/face/kind/font、缺少必填字段、同一场景中重复的 id、duration <= 0、单点的 lines 路径、奇数(非偶数)分辨率和不存在的 carry id 都将被拒绝并返回清晰的错误消息而非 traceback。对永远不会被看到的元素(场景结束后的 at)会给出警告。如果音频文件短于视频,则尾部用静音填充;如果长于视频,则被截断;视频时长绝不改变。

故事板 schema

{"meta": {"width":1080,"height":1920,"fps":30,"boil_fps":8,"transition":0.18,
          "palette":{"accent":"#b8372d"}, "audio":null, "font_dirs":[]},
 "scenes":[{"duration":3.4, "caption":"他是**亚历山大**大帝。",
            "caption_at":0.5, "caption_y":null, "elements":[ ... ]}]}

对于跟随语音的单词强调,提供一个 karaoke 数组,其中包含 caption 中每个由空格分隔的单词对应的一个 { "start": seconds, "end": seconds } 对象。时间相对于场景起始;红色强调色在每个区间切换。当存在 karaoke 时,caption 不得包含 **...** 标记。

{"duration":2.8, "caption":"他**一刀**砍断了绳结。", "caption_at":0.35,
 "karaoke":[{"start":0.35,"end":0.76},{"start":0.76,"end":1.02},
            {"start":1.02,"end":1.53},{"start":1.53,"end":2.15}],
 "elements":[ ... ]}

每个元素:type、可选的 id(用于 carry)、at(出现时间,秒)、dur(出现持续时间,秒)、anim = fade | wipe | rise | pop、until/out_dur(淡出时间)。 位置 x、x2、y、ground 若 ≤ 2 则视为宽度/高度的分数,否则为像素。尺寸始终为像素。

type关键字段
groundy(必填);x、x2 可选,若未提供则默认为画布的 ~2%–98% —— 抖动基线
sunx、y、r、rays
texttext(**accent** 标记)、x、y、size、font = sans/sans_bold/sans_light/serif、tracking、caps、color、max_w、max_lines(默认为 3)、rules(用于标题卡的双线;宽度和间距根据文本自动调整)
figurex、ground、h、head_r、pose = stand/arms_open/point/wave/think/shrug/sit、dir(±1)、face = neutral/smile/sad/surprised/angry、可选 accessory = helmet/crown/hat/glasses、可选 cape、look
propkind = barrel/bowl/bucket/house/book/box/triangle/coins/tree、x、y(左上角)、w、h、cross(强调色 X)、flip
thoughtx、y、w、h、tail_to [px,py](头部位置)、inner:气泡本地坐标系中的元素列表(px)
linespaths(点列表的列表,本地 px)、closed、smooth、color、width —— 任何自定义形状的转义出口

场景间连续性 (carry)

要将一个场景的元素延续到下一个场景,请为该元素指定一个 id,然后在下一个场景中使用 carry 引用它。被延续的元素以相同的线辐动画(相同的随机种子) 开始,因此即使场景发生变化,它也看起来像是“同一幅画”;新场景的过渡默认为硬切(无交叉淡入淡出)。

"carry": [
  "ground",
  {"id": "sun",  "set": {"r": 122}},
  {"id": "alex", "set": {"x": 120, "face": "smile"}, "tween": {"at": 0.1, "dur": 0.9}},
  {"id": "diog", "until": 0.1, "out_dur": 0.5}
]

各行按顺序解释:"ground" 直接延续 · sun 修改字段后延续(更改在场景开始时立即生效)· alex 既修改字段又在一段时间内平滑移动其位置(tween)· diog 被延续但在场景内淡出。

  • "carry": "all" 延续所有内容("carry_except": ["id", ...] 排除特定项)。字幕绝不延续。
  • 未被延续的元素在场景开始时立即消失;如需平滑消失,请使用 until/out_dur。使用 until 淡出的元素不能延续到下一个场景。
  • tween 仅动画位置(x 和 y/ground);尺寸(r、h、w)和外观(face、pose)的更改在场景开始时立即应用。
  • 被延续的元素绘制在新元素之下。被延续的元素在场景间的 id 必须唯一。
  • 具有相同 id 的元素在每个场景中使用相同的随机种子;对于场景特定的新元素,请勿指定 id。

通过扩展 render.py 中的 PROPS / hands 来添加新道具/姿势;保持相同的墨水风格(使用 stroke(),切勿使用普通线条)。

布局:为每个视频重新决定

没有固定的坐标/模板。每个视频的主题不同,因此布局应从场景内容派生:

  • 首先为那个句子选择一个视觉创意(比较、列表、想象、旅程、数字……),然后构建适合该创意的布局。同一视频中场景之间的布局可以变化。
  • 为重要元素腾出空间:主要主体最大/最突出;次要元素保持小尺寸。有意识地留白(负空间)。
  • 大小、位置和数量根据内容而变化(是 1 个角色还是 2 个;是图标行还是单个大物体;是地图/时间线)。必要时使用 lines 绘制自定义形状。
  • 不要为历史人物自动添加服装/头饰。默认情况下,人物绘制时不带配饰;头盔、王冠、帽子或斗篷仅在叙事或用户明确要求时添加。不要将强调色作为装饰色用在角色的服装上。
  • 在涉及绳结和切割的场景中,要清晰绘制物体:用几个明显交叉的环画绳子,在中心显示一个紧结,并区分绳头。在剑上,要能辨认出握把、护手和尖刃。切割之后,两个断开的绳头之间要留有可见间隙并清晰显示;确保剑和绳结不被字幕遮挡。使用 lines 绘制自定义的绳子/剑形状。
  • 底线、太阳、标题卡等主题是可选的;如果与主题不符(例如室内、夜晚、抽象概念),则不要使用。
  • 将字幕放置在可读且远离平台界面的位置;若与元素重叠,则使用 caption_y 调整。在静态图中目视检查重叠和溢出,不要依赖数字。
  • 绘制坐标以 1080×1920 画布上的像素(或 0–2 之间的比例)给出;为每个场景从头规划这些值。

注意事项

  • 在编码之前始终查看静态图;重叠的元素和裁剪的太阳光线是常见的缺陷。使用 --draft 用于草稿,全渲染用于细节:未改变的帧不会重绘,10 秒视频全质量渲染大约需要 20 秒。
  • 字幕会在达到 86% 宽度时自动换行(最多 3 行),但如果一个单词放不下或超过 3 行,则会缩小。将超过 2 行的句子分成两个场景。默认字幕位置仅作为后备;必要时在场景中提供 caption_y。
  • 字体:安装 Poppins 和 Playfair Display(或 Lora),或者使用 --font-dir 参数指定。如果找不到任何 TrueType 字体,渲染器会打印警告,并且土耳其语字符(İ ş ğ â)可能缺失;请在静态图中检查。会扫描 Linux、macOS 和 Windows 字体文件夹。
  • 保持强调色的语义性(主题、关键词、否定)。不要添加更多颜色。
  • 当存在 karaoke 时,红色字幕强调色使用提供的区间逐字跟随语音。不要在屏幕上留下固定的红色关键词;非激活的单词使用字幕文字颜色渲染。

相关技能