火柴人解说器
将简短脚本转换为完整的 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区间;非激活的单词保持正常文字颜色。 - 优先使用非对称布局并有意留白。底线、太阳、标题卡、气泡等元素是可选的;仅在它们支持场景时才使用。
工作流
- 脚本 → 场景。 将故事拆解为每句一个场景(字幕 ≤ 6 个单词,每个 2.5–4 秒)。模式:标题卡 → 钩子/冲突 → 具体揭示。每个场景恰好添加一条新信息。当提供语音时,将场景边界与口语句子对齐。
- 为每个句子选择一个视觉元素。 每个场景一个比喻:角色 + 道具、气泡或一行图标(X = “没有”)。保持 ≤ 2 个角色和 ≤ 5 个图标。
- 编写故事板(schema 如下)。使用单词级语音时间戳时,为字幕中每个由空格分隔的单词添加一个
karaoke区间,以便红色强调色跟随正在发音的单词。仅当没有单词时间戳时使用**word**进行静态高亮;不要将静态标记与karaoke结合使用。 - 先检查静态图(快速,每场景约 2 秒):
python scripts/render.py story.json --stills out/stills→ 打开每个scene_NN.png,修复重叠/放置问题,重复此过程。 - 草稿视频:
python scripts/render.py story.json out/draft.mp4 --draft(无抗锯齿,12fps)。 - 最终版:
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 | 关键字段 |
|---|---|
ground | y(必填);x、x2 可选,若未提供则默认为画布的 ~2%–98% —— 抖动基线 |
sun | x、y、r、rays |
text | text(**accent** 标记)、x、y、size、font = sans/sans_bold/sans_light/serif、tracking、caps、color、max_w、max_lines(默认为 3)、rules(用于标题卡的双线;宽度和间距根据文本自动调整) |
figure | x、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 |
prop | kind = barrel/bowl/bucket/house/book/box/triangle/coins/tree、x、y(左上角)、w、h、cross(强调色 X)、flip |
thought | x、y、w、h、tail_to [px,py](头部位置)、inner:气泡本地坐标系中的元素列表(px) |
lines | paths(点列表的列表,本地 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时,红色字幕强调色使用提供的区间逐字跟随语音。不要在屏幕上留下固定的红色关键词;非激活的单词使用字幕文字颜色渲染。