SRT 白板动画工作流:把字幕的叙事顺序变成可检查的手绘视频

SRT 白板动画真正难的不是把图片导出成 MP4,而是让字幕事件、画面主体、遮罩边界和笔迹时序彼此对齐。srt-whiteboard-animation 用分镜、语义标注、预览台、流式绘制和多幕合并,把这条链路拆成可以逐步确认的工程流程。

把一段 SRT 字幕变成白板手绘视频,表面上像是“配一张图,再让手把它画出来”。真正难的是时间、语义和遮挡关系必须同时成立:字幕先讲场景,画面就不能先露出结果;两个对象彼此遮挡,后面的对象不能从前面的矩形空隙里提前漏出来;一幕结束时,观众又必须看到一张完整、稳定的画面。只做一个从左到右的擦除动画,很容易得到画面变化,却得不到跟叙事同步的白板动画。

srt-whiteboard-animation 的价值在于把问题拆成一条可检查的流水线:SRT → scenes → source image → semantic annotation → preview → stream render → merge。前半段决定“什么时候、为什么出现哪个元素”,后半段决定“这个元素怎样连续地被画出来”。两件事没有混成一个不可调的黑盒,字幕改了可以重排场景,标注改了可以重渲染,而不必从头猜一遍所有参数。

项目仓库 给出的示例是按叙事顺序绘制猴子山、角色和围观孩子。这个例子说明目标不是把一张插图随机显现,而是把“铺垫—关键对象—动作或变化—反应或结果”变成观众能跟上的视觉节奏。

先把字幕当成时间轴,而不是台词附件

第一步是用字幕解析脚本读取 SRT,并依据叙事顺序建议分幕。仓库给出的默认建议是每幕 25–35 秒,目标值可以取 30 秒:

BASH

python scripts/parse_srt.py <字幕.srt> --target-sec 30 --min-sec 25 --max-sec 35

这个命令的作用不是把字幕简单切成等长片段,而是为后续场景设计提供时间跨度。每幕应该只表达一个核心意思:例如一幕讲清楚“角色在山上拿着香蕉”,下一幕讲“另一个角色抢走香蕉”,再下一幕讲“旁观者作出反应”。如果一幕同时塞入背景、冲突、结局和新地点,后面的区域顺序会失去可读性,单幕时长也很难安排。

解析结果中的 sceneDurationMs 来自该幕字幕的时间跨度。它是动画的总时间预算,不是随手填的数字。区域的 startMs 和 durationMs 要在这段预算内串行安排,全部区域画完后还要留下至少 0.5 秒的完整画面。这样做的好处是字幕、镜头和停顿有同一个时间依据,最后不会出现字幕已经说完、画面还在赶工,或者画面完成后长时间空等的情况。

分镜之后才生成统一风格的源图

分镜确认后,流程才进入 source image。源图是每一幕的静态底稿,不是最终视频。仓库规定使用暖米黄色纸张背景,建议颜色为 #F5EBD7,搭配深灰色素描线条,只用少量红、橙、蓝作概念性点缀。构图要简洁,主体之间留出足够空白,画面不能依赖文字、标签或复杂纹理解释意思;场景源图中不应出现字母、数字、字体或其他场景文字。

这些限制和后面的自动绘制直接相关。背景越复杂,流式绘制器越难判断哪些像素属于线稿、哪些只是纹理;主体互相压得越紧,矩形区域就越容易覆盖不该提前出现的内容。源图不是追求细节最多,而是要让每一个可叙述事件都有一个清楚的视觉主体。先定一套稳定的线条、人物和配色,再逐幕生成,比分别生成几张风格不一致的插图更适合批量合并。

语义标注把“画哪里”升级成“为什么现在画

源图确认后,需要创建与图片同名的 annotation.json。例如 scene-01-demo.png 必须对应 scene-01-demo.annotation.json。标注不是单纯给图片画几个框,而是把字幕事件映射到可绘制区域。一个最小元素可以这样写:

JSON

{ "sceneId":"scene-01", "canvas":{"width":1672,"height":941}, "storyBasis":"该幕字幕的事件摘要", "sceneDurationMs":9000, "elements":[{"id":"rockery","label":"假山场景","sequence":1,"narrativeRole":"故事的场景铺垫","subtitle":"对应的 SRT 字幕文本","type":"structure","region":{"x":20,"y":120,"width":540,"height":780},"reveal":{"direction":"top_to_bottom","startMs":300,"durationMs":2600,"maskPaddingPx":22,"protectedRegions":[]},"handPath":{"start":[290,130],"end":[290,890],"easing":"easeInOut"}}]}

canvas.widthcanvas.height 必须等于原图的像素尺寸;region 使用左上角为原点的整数像素坐标,不能用百分比或凭感觉估计。sequence 从 1 连续递增,narrativeRole 用中文写清楚它是场景铺垫、关键人物、动作变化还是反应结果。subtitle 保存实际对应的字幕文本,便于预览台联动,也让日后调整时能追溯这个区域为何处在这个时刻。type 表达主体类型;reveal 携带时间、方向和遮罩保护;handPath 则给预览台一个矩形代理的起止方向。

语义排序比空间排序更重要。画面左边的物体不一定先画,字幕先提到的场景铺垫才应先画;动作发生后才出现的结果,不能因为它位于画面上方就提前显示。标注前必须同时阅读对应字幕、实际查看源图并确认原图尺寸。只看字幕会漏掉画面中的真实边界,只看图片则会退化成从左到右的机械动画。

为什么遮罩编排和流式绘制必须分开

这个项目刻意把 mask orchestration 与 stream drawing 分成两层。遮罩编排解决的是可见性:在时间 t,某元素只能在 startMs 之后,并且只能显示当前绘制进度允许的像素;未开始的区域必须完全隐藏。每个区域的允许掩码,是自己的矩形 region 扣除所有后续模块的 region,再扣除 protectedRegions。遇到主体交叠、矩形框过大或背景线条可能泄露时,就在较早元素的 reveal.protectedRegions 中写入需要延后显示的矩形区域。

流式绘制解决的是笔迹运动:一支笔沿着区域内的骨架或网格连续滑行,先以 ink 铺线稿,再以 color 添彩。默认 ink 与 color 的时间权重是 2:1;默认路径是 grid,线稿清晰的插画可以选择 skeleton。两层分开后,编排层可以保证“后续人物不会提前露出来”,绘制层可以保证“当前人物不是一块矩形突然出现”。如果把二者混在一起,调整区域边界可能破坏笔迹路径,调整笔迹算法又可能改变字幕事件的先后,调试成本会随场景数量一起增长。

还要注意 directionhandPath 的边界:它们用于预览台中的矩形代理,帮助人检查方向和时序,并不决定最终成片的真实笔迹。最终笔迹由流式绘制器自动生成。预览台因此是编排检查工具,不是把预览中的矩形擦除效果当作最终渲染结果。

预览台是低成本验收点

标注文件创建后,打开仓库里的 assets/preview.html,用“打开文件夹”载入场景目录。预览台可以编辑区域、顺序、时间和字幕关联:拖动区域四边或四角修改 region,右侧面板修改名称、方向、开始和结束时间,拖动模块列表调整顺序,选中模块时查看对应字幕。保存时会把区域字幕写回标注文件,并让 sceneDurationMs 对齐到最后一个区域结束后至少 0.5 秒。

这里应先检查三个问题。第一,静止状态下所有框是否覆盖了正确主体,且全部在画布范围内。第二,按播放查看时,模块是否按字幕叙事出现,未开始区域有没有任何线条提前露出。第三,交叠区域是否受到保护,后一模块的轮廓是否要等到正确时刻才出现。预览通过后再生成编号和方向检查图:

BASH

python scripts/render_annotation_preview.py <图片路径> <标注路径> <预览图输出路径>

这个检查图的目标是发现坐标、编号、方向和遮挡问题,不是替代最终 MP4。若预览暴露问题,应回到标注调整,而不是反复生成源图。

渲染与合并:把每幕当成可独立验收的产物

首次运行先检查隔离环境,成功时捕获命令输出的 ENV_PY=<路径>,后续脚本都使用该解释器:

BASH

python scripts/prepare_env.py --check python scripts/prepare_env.py

单幕渲染命令如下,--ink-path 可选 gridskeleton--color-fill 可选 contour-wipebrush

BASH

<ENV_PY> scripts/render_stream_whiteboard.py <图片路径> <标注路径> <输出.mp4> assets/drawing-hand.png --ink-path grid --color-fill contour-wipe

如果没有提供 --total-ms,渲染器使用标注中的 sceneDurationMs。

每幕应抽查开场、任意重叠模块的中段和结尾:首帧应只有暖米黄纸张底,中段不能出现未开始模块或保护区内容,尾帧应是完整画面并至少停留半秒。所有单幕确认后,再按分镜顺序合并:

BASH

<ENV_PY> scripts/merge_scenes.py --inputs 幕1.mp4 幕2.mp4 幕3.mp4 --output final.mp4

合并脚本只负责把已验收的幕按顺序接起来,不能修复单幕里的字幕错配、区域越界或提前露线。因此多幕项目应保留 scene-01、scene-02 等独立 MP4,让问题能定位到具体分镜,而不是只留下一个难以追查的 final.mp4。

常见失败不是“画得不够像”

第一种失败是时序重叠。若多个元素的 startMs 重叠,渲染器仍会按顺序处理,但视觉上不再是并发意义明确的叙事;应该在预览台把下一区域安排到上一区域结束之后,必要时留出 100–300ms 的呼吸时间。第二种失败是把矩形代理当成真实笔迹。方向框只是预览台的辅助显示,成片效果要以 stream 渲染结果判断。

第三种失败是 region 使用估算坐标,或 canvas 尺寸和图片不一致。这会造成主体被裁掉、笔迹落在错误位置,甚至让遮罩边界看似正确却覆盖不到实际线稿。第四种失败是没有填写 protectedRegions。主体相交时,较早区域的宽矩形可能把后续对象的线条一起揭示;保护区不是装饰字段,而是控制“后续内容何时可见”的边界。第五种失败来自源图本身:加入场景文字、复杂纹理、高饱和背景或过多细节,会让“语义区域”难以定义,也让线稿与上色的识别变得不稳定。

第六种失败是跳过确认关卡,字幕分镜、线稿、标注和成片连着生成。仓库的工作方式要求每一步完成后等待明确确认;这不是形式上的停顿,而是为了在渲染成本增加之前,把错误拦在最便宜的阶段。遇到效果不对时,应先在预览台调整区域、顺序和时序,保存 annotation.json 后再重新渲染,而不是凭空反复出片。

谁适合采用这条流程

它适合有知识讲解、故事口播、课程字幕或短视频文案,并且愿意把画面拆成事件的人。独立创作者可以用它把一段已有 SRT 变成风格统一的解释视频;课程作者可以让字幕成为镜头编排的时间依据;AI 工具或开发者工具团队可以把分镜、源图、annotation.json 和 MP4 一起纳入项目资产,后续修改有明确的文件边界。熟悉 Python、能处理本地图片和命令行环境的人,会更容易排查渲染问题。

它不适合只想一键把任意长视频自动转成成片、又不愿检查字幕和画面的人。这个流程明确要求逐步确认,也要求人工判断主体是否对应字幕事件;它提供的是可复核的制作骨架,不是替代导演判断的全自动按钮。若需求是复杂摄影、密集文字、写实角色或自由剪辑,仓库规定的极简无文字源图和分区串行绘制反而会成为限制。

因此,srt-whiteboard-animation 最值得借鉴的不是某一个擦除效果,而是它把内容制作拆成了可追责的中间结果:字幕决定场景,场景决定源图,源图通过语义标注获得时序和边界,预览台负责低成本校正,流式绘制负责连续笔迹,单幕验收后才合并。只要把每个中间结果当作正式资产管理,白板视频就不再是“图片加动画”的临时技巧,而是一条能反复修改、检查和复用的制作工作流。