把一首古诗、一阕词(或一句诗、一段短文)做成 20–80 秒的「代码画的水墨手绘风 2D 动画」——一个红点小角色把诗走一遍,题签逐句写字,带朗诵,末句边念边写,红印章收尾;配乐和音效用 numpy 合成,封面是第一帧,最后给上传版和实测帖草稿。用户说「把 XX 这首诗 / 这首词做成动画」「做一条诗词动画 / 古诗动画 / 水墨动画」「像《早发白帝城》《赤壁怀古》那条一样做」「用代码画一首诗」「红点动画」「给这句诗配个动画和朗诵」「再来一首大气点的」时都用这个 skill;用户只给了一句诗、一个诗名、一个词牌,或者只说「下一首」也要用。不适用:有地图、有路线的历史纪录片(走 code-doc-film)、真人口播剪辑、产品宣传片、竖版视频(目前只做横版 16:9)。
Recursos
8Instal·la
npx skillscat add bangbuilds/code-poem-film Instal·la des del registre de SkillsCat.
代码诗词动画(code-poem-film)
把一首诗做成一条片子(绝句 20–40 秒,一阕百字的词 60–80 秒):宣纸底、水墨淡彩,一个红点落在纸上、长出眼睛,把这首诗走一遍;每句诗在右上角的题签上随朗诵写出来;最后一句红点自己提笔写,念到哪个字写到哪个字,然后落成一枚红印章。画面每一帧都是代码画的,配乐和音效是 numpy 算的,没有图片素材、视频素材、曲库。
这套做法来自两条成片:《早发白帝城》(examples/qingzhou,七绝,25.5 秒)和《念奴娇·赤壁怀古》(examples/chibi,词,72 秒)。它们翻过的车——猿挂的树悬空、鼓把旋律盖住、多音字怕念错、开头是白纸导致缩略图是白纸、一帧出错后画面静默卡住、小舟画在了礁石「上面」、淡出的东西压住片尾小字——现在都由引擎、检查脚本和 references/qa.md 兜住。你的工作是把这首诗的画面想好、演好,而不是重新踩一遍。
它擅长什么、不擅长什么
擅长:绝句、律诗,百字以内的词,一句名句,几十字的短文。有景、有动作、有转折的最好做(行舟、登楼、望月、送别、怀古、江雪)。画法库里有:山(远峰、群山、峡谷绝壁、渐隐的远山、石峰)、岸、江水、长浪、开阔水面、巨浪和浪花、日、月、星、祥云、雾、雨、雪、飞鸟、松、柳、城楼、多层楼阁、残垒、带帆小舟、乌篷小舟、战船、火、烟、人物剪影(文士、武将、仕女)、猿、酒杯、手卷的装裱,以及溅水、浪头、落地的墨点。看 references/kit-1.jpg … kit-4.jpg 就知道库里有什么、长什么样。
不擅长:人物的戏(库里的人是没有脸的远景剪影,只能站、举手、转身;主角永远是红点);一百字以上的长文(按每秒不到 3 个字算,一百字就是 70 秒,再长要节选);竖版(版式是按 1920×1080 排的);抽象说理、没有画面的句子(要先和用户商量画什么)。库里没有的东西可以现画(见 references/scenes.md),但要多花时间,开工前说清楚:赤壁那条现画了 14 样东西,整条从提出到成片用了 63 分钟。
用户只说了一句话时
「做一条《静夜思》的动画」就够开工。默认这样走:
- 先定题材。用户没指定诗,就给三四个候选,每个一句话说清「红点在里面干什么」,推荐一个,让用户选。用户指定了诗,就直接进入下一步。
- 只问会改变做法的事:发在哪(现在只能横版)、要不要朗诵、男声还是女声。用上次的选择或默认值时说一声就行,不要一项项问。
- 用户确认题材后一路做到出片。中途只在两种情况停下来:要花钱(真配音按次计费:先
film.py voice --plan看会调用几次、多少字,告诉用户)、要画库里没有的大件东西。 - 朗诵你听不到。能做的是量:语速、停顿、轻重变化、人声比配乐高多少。选音色拿不准时出两版让用户听,不要替用户说「这个更有感情」。
流程
所有命令在工程目录里运行,tools/film.py 是唯一入口。渲染用 HyperFrames(通过 npx hyperframes 调用),工程也是一个 HyperFrames 工程,但这类片子不需要再走它的问答和分镜流程——这份文档就是完整流程。
# SK = 这份 SKILL.md 所在的目录
python3 $SK/scripts/new_project.py videos/<slug> # 新建工程:HyperFrames 初始化 + 引擎 + 起步片子 + 笔画数据
cd videos/<slug>| 步骤 | 做什么 | 命令 / 文件 |
|---|---|---|
| 1 定稿 | 把诗填进 poem.json:标题、作者、每句、印文、多音字 |
references/poem-json.md |
| 2 空跑 | 不花钱:估每句时长,让起步片子先跑起来 | film.py voice --dry → film.py build → film.py qa |
| 3 分镜 | 每句诗画什么、红点做什么、哪个动作卡在哪个字上 | 写进 film/timing.js 的注释里;references/acting.md |
| 4 画场景 | 把起步片子里的「长卷」换成这首诗自己的画面 | film/timing.js、film/scenes.js;references/scenes.md |
| 5 看图 | 抓关键帧、看总览图,改到干净 | film.py qa;references/qa.md |
| 6 朗诵 | 真配音(缓存,同一段只花一次钱;长篇逐句合成);读得太快的句子本地放慢;末句对着频谱图标每个字的时间 | film.py voice --plan → film.py voice --spec;references/audio.md |
| 7 编曲 | 每个声音绑在时间表的一个时刻上 | film/score.py → film.py score |
| 8 出片 | 检查 → 渲染 → 核对 → 上传版(长片另出 720p 小版)、封面、总览图、数字 | film.py render |
| 9 交付 | 发上传版和封面;说清没验证的;按实测写帖子草稿 | references/delivery.md;film.py stats |
每到一个节点记一笔:python3 tools/film.py log "分镜确认"。
四个文件各管一件事
| 文件 | 管什么 |
|---|---|
poem.json |
字:标题、作者、诗句、印文、落款、多音字、音色 |
film/timing.js |
什么时候:这首诗每个动作的时刻。朗诵、题签、末句、总时长由引擎按配音实际长度推出来 |
film/scenes.js |
画什么:场景和红点的表演 |
film/score.py |
响什么:每件乐器在哪个时刻响 |
engine/ 和 tools/ 是引擎,新片子一般不用改;现画的新东西如果别的诗也用得上,就写进工程的 engine/,出片后同步回这个 skill 的 template/engine/ 并在 examples/gallery 里加一格。舞台(engine/stage.js)已经包了:封面及其淡出、题签逐句写字、末句随朗诵逐字书写、红点变印章、落款、整片的宣纸质感。你交给舞台的只有四样:build(一次性画好的贴图)、scene(ctx, t)(每一帧)、cover(c, time)(封面那张画)、titleFrom()(红点从哪儿跳起来去写字)。
film/timing.js 里时间表的写法:
const T = { drop: 0.35, land1: 0.85, …, bloom: 3.0, launch: 8.6, hop: 17.15, write0: 17.9 };
PoemTiming.finish(T, {
lines: [
{ at: 3.3 }, // 第 1 句:这个时刻开始念
{ gapAt: T.launch }, // 第 2 句:把句中的停顿(千里江陵|一日还)对在 launch 这一刻
{ after: 0.3, min: 10.3 }, // 第 3 句:上一句念完 0.3 秒后,但不早于 10.3
],
});句子少(绝句)就像上面这样,先定画面的时刻,再把每句朗诵摆上去。句子多(词、律诗)反过来:先定每句之间停多久(全用 after,长的停顿是留给画面的),朗诵的时刻由配音实际长度推出来,再把画面的动作挂在「第几句的哪个位置」上(T.v[i] 是第 i 句开口的时刻,T.vEnd[i] 是念完的时刻)。这样换音色、改语速,画面跟着走。写法见 examples/chibi/film/timing.js。
做的时候守住的几条(为什么)
- 每一帧只由时间决定。不用时钟、不用
Math.random(用QZ.hash/QZ.rng)。渲染是多个进程乱序抓帧的,画面依赖「上一帧」就会错位。 - 全片只有红点是正红色。印章是它变的,所以也是红的;别的东西(太阳、旗子、花)用藤黄、胭脂、赭石。观众的眼睛靠这一点红跟住主角,检查脚本也靠它判断主角在不在画面里。
- 先空跑再配音。
voice --dry估出来的时长足够排场景;真配音按次收费,分镜没定就配是白花钱。同一音色同一段文字有缓存,重跑不再花钱。 - 动作卡在字上,写在时间表里。「千里江陵」和「一日还」之间朗诵会换气,小舟就在这口气里弹射出去;猿在念「猿声」的时候叫。这种对位是这类片子好看的原因,用
gapAt/at把它写死,不要靠凑。 - 多音字写进
poem.json的say(朝→昭、还→环、重→崇)。合成用同音字,读音必然对;画面上写的永远是原字。你听不到,这是唯一能保证不念错的办法。 - 东西站在山上时,问贴图要高度(
tile.hs),并且算对「画面 x → 贴图 x」的偏移。上次猿挂的树悬在半空,就是偏移加错了,看截图才发现。 - 末句的每个字要对着频谱图标时间(
voice --spec,写进poem.json的onsets)。不标就是平均分,笔会和声音错开。 - 水面上的东西,谁的水线低谁在前面。小舟的水线比礁石的水线高,却画在礁石后面,看上去就像船搁在石头上。先定每样东西的水线,再按从远到近的顺序画。
- 会淡到大字后面的场景里,别留东西在落款和片尾小字底下。两条片子都在这里翻过车;最干净的办法是让那样东西在红点离开后自己淡出。
- 声音没人听过。
film.py score会打印每段的频段占比和人声高出配乐多少分贝:60–150Hz 超过 45% 就是鼓太重,人声余量低于 7 分贝就是被盖住。交付时必须请用户听,并且如实说你没听过。
检查是流程的一部分
python3 tools/film.py qa它做三件事:跑 HyperFrames 的工程检查;按时间表里的每个时刻抓一帧,自动查「几乎是白纸」「红点不在画面里」「红色太多」「画面没动」「这一帧报错了」;把这些帧拼成带时刻和事件名的总览图 build/qa/contact-N.jpg。总览图要自己打开看——脚本看不出构图空、字压住了主体、东西悬在半空。
一帧出错时,错误信息会用红底白字写在画面顶上(而不是静默卡住上一帧),所以看图就能发现。
film.py render 渲染后还会核对:帧数和时长对不对、响度是不是 −16 LUFS、每段朗诵在成片里的位置和计划差多少毫秒、中间有没有画面不动的片段、第一帧是不是白的。
参考文档
| 文件 | 什么时候读 |
|---|---|
references/poem-json.md |
填 poem.json、写 film/timing.js 之前:全部字段、时间规则 |
references/scenes.md |
画场景之前:舞台接口、画法库清单、镜头和视差、晕开/淡出/转场、怎么画新东西 |
references/acting.md |
排红点的戏之前:压扁拉伸、蓄力、眼神、表情、和朗诵对位的几种办法 |
references/audio.md |
朗诵和配乐:语音插件、多音字、读频谱图、乐器清单、自检数字怎么看 |
references/qa.md |
检查和调试:每条警告的意思、翻过的车、浏览器预览 |
references/delivery.md |
出片后:文件清单、交付时说什么、实测帖怎么写、数字从哪来 |
references/kit-1.jpg … kit-4.jpg |
画法库里每样东西的样子。开工前看一眼,库里有的不要重画 |
examples/qingzhou/ |
完整范例《早发白帝城》(绝句):连续镜头、视差、晕开、物体转场、远景、对位。短诗照着它改最快 |
examples/chibi/ |
完整范例《念奴娇·赤壁怀古》(词,72 秒):按停顿排时间、逐句配音、一个世界换三个时辰、人物和火、画中画。长篇照着它改 |
用范例新建一个能直接跑的工程:
python3 $SK/scripts/new_project.py videos/demo --example qingzhou升级已有工程的引擎(不动 poem.json、film/、assets/、audio/):
python3 $SK/scripts/new_project.py videos/<slug> --sync