CodeAnim
用 Python 代码描述动画,一条命令渲染成视频文件。无窗口、无交互、不弹播放器,跑完就落盘。
python main.py examples/demo.py
# -> out.mp4
默认零落盘:帧经管道直接进 ffmpeg,中途不写任何临时文件。 磁盘占用只有最终视频那么大 —— 4K / 高质 / 长片也敢跑。
安装
需要 Python 3.8+、Pillow、ffmpeg。
pip install codeanim-py # 发行名带 -py 后缀(codeanim 已被占用)
codeanim doctor # 检查依赖,缺什么会告诉你装什么
导入名仍然是
codeanim—— 写代码时不用管发行名:from codeanim import Scene # 不是 codeanim_py
装完会有两个等价入口:
codeanim demo.py -o out.mp4
python -m codeanim demo.py -o out.mp4 # 效果一样
不装包也能直接跑(开发引擎本身时最方便):
python -m pip install -r requirements.txt
python main.py doctor
命令
python main.py <脚本.py> [选项]
python main.py doctor
| 选项 | 作用 | 默认 |
|---|---|---|
-o, --output |
输出路径,扩展名决定格式 | out.mp4 |
--fps |
帧率 | 30 |
--quality |
low / medium / high / lossless |
high |
--workers, -w |
并行进程数 | CPU 核数−1 |
--snapshot <秒> |
只出一张单帧 PNG | — |
--frames <目录> |
导出 PNG 序列 | — |
--gif |
导出 GIF | — |
--keep-frames |
保留中间帧(调试用) | 关 |
--supersample <倍数> |
超采样倍数 | 2 |
--mode <模式> |
帧存储:stream / ram / disk |
自动 |
--alpha |
输出带透明通道(.mov / .webm) |
自动 |
--ffmpeg <路径> |
指定 ffmpeg | ffmpeg |
--watch, -W |
热重载:改代码自动刷新预览 | 关 |
--watch-at <秒> |
热重载预览的时间点 | 0 |
--watch-output <路径> |
热重载预览图路径 | .codeanim/watch_preview.png |
--inline |
热重载时在终端直接画画面 | 自动判断 |
--no-inline |
不画终端内嵌画面 | — |
--preview <秒> |
快速预览:渲一张图并尝试打开 | — |
--no-audio |
忽略场景里的音频轨道 | 关 |
--list-audio |
只列出音频轨道,不渲染 | — |
--layers |
只打印图层绘制顺序,不渲染 | — |
--layers-at <秒> |
--layers 求值的时间点 |
0 |
--quiet, -q |
只输出错误 | 关 |
--free-margin <大小> |
保留的空闲磁盘红线 | 256MB |
--force |
空间检查不通过也硬渲(不推荐) | 关 |
--no-disk-check |
跳过所有磁盘检查 | 关 |
python main.py demo.py --watch # 开着热重载改代码
python main.py demo.py --watch --watch-at 2.5 # 盯第 2.5 秒那一帧
python main.py demo.py --preview 2.0 # 渲一张图并打开
python main.py demo.py --snapshot 2.0 # 只出图,不打开
python main.py demo.py --gif # 出 GIF
python main.py demo.py --frames pngs/ # 导出 PNG 序列
python main.py demo.py -o out.mov --alpha # 带透明通道
开发流程:热重载
写动画最烦的是「改一个数字 → 跑一条命令 → 等 → 打开文件看」。
--watch 把中间全砍掉:
python main.py examples/demo.py --watch
CodeAnim 热重载
────────────────────────────────────────────────────────
入口 examples/demo.py
监视 7 个 .py 文件
examples/ball.py
examples/demo.py
examples/logo.py
... 还有 4 个
预览 .codeanim/watch_preview.png
时间点 t=2.00s
────────────────────────────────────────────────────────
改文件即自动重渲染,Ctrl-C 退出。
✓ 已更新 1280x720 8.60s 30 fps 11 个图元
预览 .codeanim/watch_preview.png (t=2.00s)
耗时 814 ms
↻ 检测到改动: examples/demo.py
✓ 已更新 1280x720 8.60s 30 fps 11 个图元
耗时 225 ms
行为:
- 监视入口脚本和它 import 的所有同项目
.py—— 改片段文件也会触发 - 只渲染一帧,不跑整段视频,通常 200~800 ms 出结果
- 报错不退出:语法错 / 运行错都只打印,等你下次保存
- 编辑器保存时的多次写事件会防抖合并成一次
- 新增的
.py文件会被自动纳入监视 - 无第三方依赖,纯轮询 mtime,跨平台
无图形界面的环境(服务器 / Termux)也能用:预览图就是普通 PNG, 你可以用任何方式查看,或者
codeanim会尝试xdg-open/open/am start帮你打开。
终端内嵌画面
不想开图片?直接让画面在终端里出现:
python main.py examples/demo.py --watch --inline
CodeAnim 热重载
────────────────────────────────────────────────────────
入口 examples/demo.py
预览 .codeanim/watch_preview.png
时间点 t=4.00s
────────────────────────────────────────────────────────
改文件即自动重渲染,Ctrl-C 退出。
✓ 已更新 1280x720 8.60s 30 fps 11 个图元 耗时 1.46 s
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄
▄▄▄ CodeAnim 用 Python 写动画 ▄▄▄
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
● ●
t=4.00s 11 个图元 .codeanim/watch_preview.png
原理:终端字符格子通常宽:高 ≈ 1:2,用 上下半块 ▄ 让一个字符格表示两个垂直像素,
宽高比自然正确。80×24 的终端 ≈ 80×48 个颜色块 —— 够看构图、配色、位置。
- 真彩色(24-bit)优先,退化为 256 色,再退化为灰度字符
- 每次重渲染原地覆盖(ANSI 光标移动),不刷屏
- 自动判断终端能力;用
--no-inline关掉,--inline强制开 - 也可以用 API:
from codeanim import render_terminal_preview
print(render_terminal_preview(scene, at=2.0)) # 打印一段带色字符串
lines = preview_lines(scene, at=2.0, max_cols=80) # 拿行列表
手感糖
写起来更短、更少心算。都是可选的,不改引擎语义。
一行建并加
dot = scene.add_circle(cx=100, cy=100, r=20, fill="red", z=5)
box = scene.add_rect(cx=640, cy=360, w=200, h=100, radius=16)
cap = scene.add_text("标题", cx=640, cy=120, font_size=48)
ln = scene.add_line(x=0, y=600, x2=1280, y2=600, stroke="#333")
自动算时长(不用再手算 start)
片段里打了一堆 key(),但不想手算它多长:
title_objs = add_title(scene)
print(span_of(title_objs)) # => 3.2 (最晚的关键帧时间)
# 或者直接让场景撑到刚好
add_title(scene)
add_ball(scene)
scene.fit_duration(0.5) # 撑到最长 + 末尾留 0.5 秒
span_of 认图元、Group(递归子项)、属性句柄、嵌套列表。
改文案不用重建
title = scene.add_text("旧标题", cx=640, cy=100, font_size=64)
set_text(title, "新标题") # 函数式
title.set_text("再改一次") # 方法式,可链式
title.content = "也可以直接赋值" # 属性式
print(title.content) # 读当前文案
批量添加
scene.add_all(make_particles(), z=5) # 列表
scene.add_all(Circle(r=3) for _ in range(100)) # 生成器
scene.add_all([[a, b], c]) # 嵌套
g = scene.add_group(Rect(...), Circle(...), cx=640, cy=360, z=10)
自动排布:row / grid
from codeanim import row, grid
# 一排点,按自身尺寸 + 间距自动推进
dots = row(scene, [{"r": 8}] * 5, cls=Circle,
gap=10, start=50, axis="x", cy=100, fill="#38bdf8")
# => 中心落在 x = 50, 76, 102, 128, 154
# 网格
tiles = grid(scene, rows=4, cols=6, cell_w=30, cell_h=30,
gap_x=10, gap_y=10, origin=(100, 100),
make_kwargs=lambda r, c: {"fill": COLORS[(r * 6 + c) % 7]})
Digits 更短的写法
d = Digits(value=0, cx=640, cy=120, font_size=64, thousands=True)
d.from_to(0, 12345.678, start=0.5, end=2.5, ease="outCubic") # 一行滚完
d.set_value(100) # 链式设值
print(d.content) # 当前显示的文本
Digits 自定义格式化
内置的 decimals / thousands / prefix / suffix 不够用时,用 formatter=:
# 1) 传函数 —— 任意逻辑
Digits(value=0, formatter=lambda v: f"¥{v:,.2f}")
Digits(value=0, formatter=lambda v: f"{v/10000:.2f} 万" if v >= 10000 else f"{v:.0f}")
# 2) 传 format spec —— 短
Digits(value=0, formatter="{:,.1f} kg")
Digits(value=0, formatter="{:+.2f}")
Digits(value=0, formatter="{:05.0f}")
想在自定义逻辑里复用内置规则,用 default_formatter 造基础格式器:
from codeanim import Digits, default_formatter
base = default_formatter(decimals=1, thousands=True, suffix=" 元")
Digits(value=0, formatter=lambda v: (
"%.2f 万" % (v / 10000) if v >= 10000 else base(v)))
给了
formatter后,decimals/thousands/prefix/suffix/sign/min_int_digits会被忽略 —— 格式化完全由你的函数接管。 函数抛异常不会让渲染挂掉,那一帧会退回原始数值。
磁盘安全(重要)
高质动画最容易踩的坑就是把磁盘写爆:4K/60fps/60s 的全 PNG 序列约 21 GB。 CodeAnim 用三层防护解决:
1. 默认零落盘
默认走 stream 模式:渲染好的帧直接经管道喂给 ffmpeg 的 stdin,
全程不产生任何中间文件,磁盘占用 = 最终视频大小。
python main.py demo.py -o out.mp4 # 4K 也不会爆盘
2. 要落盘就提前算账
只有你明确要求 --frames 或 --keep-frames 时才走落盘模式。
这时会先预估需要多少空间,不够就直接拒绝并给出建议,而不是跑到一半炸掉:
磁盘空间不够,无法把中间帧全部存成 PNG。
画面尺寸: 3840 x 2160
帧数: 3600
预计需要: 15.30 GB
可用空间: 2.10 GB(/mnt/data,已预留 15% 余量)
三个解决办法(推荐第一个):
1. 去掉 --frames / --keep-frames,改用默认的零落盘流式渲染:
python main.py 你的脚本.py -o out.mp4
整个过程不写任何中间帧,磁盘只占最终视频那点大小。
2. 换个空间充足的输出目录:
python main.py 你的脚本.py --frames /别的/盘/目录/
3. 降低分辨率或帧率,减少总数据量。
3. 渲染途中还有一道红线
流式模式也会每 40 帧查一次剩余空间,低于 --free-margin 就中止渲染,
避免把设备写到卡死。临时文件在正常结束 / 报错 / Ctrl-C 时都会被清掉。
写一个动画
from codeanim import Scene, Circle, Digits, Text
scene = Scene(1280, 720, duration=5, fps=30, background="#0b0f19")
# 用 cx/cy 按"中心"定位,不用手算偏移
ball = Circle(cx=200, cy=360, r=60, fill="red")
ball.cx.keys([(0, 200), (2, 1000)]) # 中心从 x=200 到 x=1000
ball.fill.keys([(0, "red"), (2, "#00aaff")]) # 颜色平滑过渡
scene.add(ball)
# 会滚动的数字
n = Digits(value=0, cx=640, cy=120, font_size=64, fill="white",
thousands=True, suffix=" fps")
n.value.keys([(0, 0), (2, 3000)], ease="outCubic")
scene.add(n)
scene.add(Text("你好,CodeAnim", cx=640, cy=640, font_size=48, fill="#e2e8f0"))
python main.py my_anim.py
定位:x/y 与 cx/cy
| 写法 | 含义 |
|---|---|
x=, y= |
自然锚点。圆是圆心,矩形是左上角,线是起点,文字由 anchor 决定 |
cx=, cy= |
几何中心。给矩形/文字用最省心,旋转也绕中心 |
两种可以混用:Rect(cx=100, y=50, ...) 表示"水平中心在 100,顶边在 50"。
想让中心动起来:
box.cx.keys([(0, 100), (2, 800)]) # 或者 box.center_x().keys([...])
cx/cy走独立的"中心通道",x/y始终是绘制坐标(左上角)。 即使w/h/r同时在变,中心点依然精确。
时间系统
时间全是绝对秒,不是相对偏移。
ball.x.key(0, 100) # 第 0 秒在 x=100
ball.x.key(2.5, 800) # 第 2.5 秒在 x=800
ease= 写在起始关键帧上,描述"从这一帧走向下一帧"的曲线:
ball.x.key(0, 0, ease="outBack").key(1, 500) # 回弹
ball.x.key(1, 500, ease="linear").key(2, 0) # 匀速
批量写:
ball.opacity.keys([(0, 0), (0.5, 1), (4, 1), (5, 0)])
25 条内置曲线:linear、inQuad/outQuad/inOutQuad、inCubic/outCubic/inOutCubic、
inQuart/outQuart/inOutQuart、inSine/outSine/inOutSine、
inExpo/outExpo/inOutExpo、inBack/outBack/inOutBack、
inElastic/outElastic/inOutElastic、inBounce/outBounce/inOutBounce。
名字不区分大小写,"out-cubic" 也能用。
图元
| 图元 | 位置 | 主要参数 |
|---|---|---|
Circle |
圆心 | r, fill, stroke, stroke_width |
Rect |
左上角 | w, h, radius(圆角), fill, stroke |
Line |
x,y → x2,y2 |
stroke, stroke_width |
Text |
锚点 | text, font_size, anchor, fill, weight, letter_spacing |
Image |
左上角 | source, w, h, crop, slice, tint, radius, fit |
Digits |
锚点 | value, decimals, thousands, prefix, suffix, sign, min_int_digits, formatter |
Group |
cx,cy |
rotation, scale, scale_x/y, opacity |
坐标原点在左上角,y 轴向下。
可动画属性
x y cx cy w h r radius rotation scale scale_x scale_y
opacity fill stroke stroke_width text font_size letter_spacing value
颜色平滑插值;text 按时点切换;Digits.value 是数值,插值后自动格式化。
图片:把素材当普通物体
from codeanim import Image, load_image, load_or_placeholder
# 一行建并加
logo = scene.add_image("logo.png", cx=640, cy=360, w=200, radius=16)
logo.rotation.keys([(0, 0), (2, 360)])
# 只给 w,按原图比例算 h
scene.add_image("hero.png", cx=300, cy=200, w=240)
# 同一张图复用多次(只加载一次)
tex = load_image("icon.png")
scene.add_image(tex, cx=200, cy=500, w=80)
scene.add_image(tex, cx=320, cy=500, w=80, tint="#22d3ee")
# 还没图?用占位棋盘格,先跑起来
scene.add_image(load_or_placeholder("还没上传.png"), cx=100, cy=100, w=60)
# 或者直接
scene.add_image(None, cx=100, cy=100, w=60, h=60)
| 参数 | 作用 |
|---|---|
w / h |
只给一个,按原图比例算另一个;都不给就用原尺寸 |
crop=(x,y,w,h) |
从原图裁一块(雪碧图) |
slice= |
九宫格拉伸:12 或 (12,) 或 (l,t,r,b) —— 缩放时边角不变形 |
radius= |
圆角 |
tint= + tint_amount= |
着色,做单色图标 |
fit= |
stretch(默认)/ contain / cover |
图片是普通图元:cx/cy 定位、rotation、scale、opacity、z、
丢进 Group —— 全都照常工作。
支持 PNG / JPG / WEBP / BMP / GIF(首帧)等常见格式。 位图带缓存:尺寸/着色没变时每帧几乎零成本。
音频:导入 + 生成
配置刻意保持朴素 —— 一句话说清"什么声音、第几秒响、多大声"。
from codeanim import Audio, beep, chime, sweep, whoosh, arpeggio
# ── 1. 导入现成文件 ──
scene.add_audio(Audio("bgm.mp3", at=0.0, volume=0.35,
loop=True, fade_in=0.5, fade_out=2.0))
scene.add_audio(Audio("whoosh.wav", at=3.5))
# 只要源文件第 3-8 秒那一段,放到第 2 秒处
scene.add_audio(Audio("speech.mp3", at=2.0, trim=(3.0, 8.0)))
# ── 2. 用代码生成,不需要任何素材 ──
scene.add_audio(beep(880, duration=0.2, at=1.0)) # 一声"哔"
scene.add_audio(chime(at=5.0)) # 有余韵的"叮"
scene.add_audio(whoosh(at=2.6)) # 转场"嗖"
scene.add_audio(sweep(200, 1600, duration=0.35, at=3.0)) # 上升扫频
scene.add_audio(arpeggio([523, 587, 659, 784], at=7.4)) # 上行音阶
Audio 的参数:
| 参数 | 含义 |
|---|---|
at |
从第几秒开始播(绝对时间,毫秒精度) |
volume |
音量倍数,1.0 为原声 |
loop |
循环填满剩下的时长 |
fade_in / fade_out |
淡入 / 淡出秒数 |
trim=(起, 止) |
只取源文件的一段 |
speed |
变速(会同时改音调) |
生成的音用 Tone 家族:
| 函数 | 用途 |
|---|---|
beep(freq, duration=0.15, at=0) |
UI 反馈音 |
chime(freq=784, duration=0.9, at=0) |
有余韵的"叮"(叠泛音 + 长淡出) |
sweep(200, 1600, duration=0.4, at=0) |
扫频(低→高像"嗖",高→低像"嘟") |
whoosh(duration=0.3, at=0) |
噪声 + 扫频,转场音效 |
arpeggio([...], step=0.14, at=0) |
一串音符依次响 |
波形可选 sine / square / triangle / saw,还能加泛音(harmonic)
和白噪(noise)。纯标准库实现,不需要 numpy。
混音交给 ffmpeg(adelay + amix),不自己算采样。
排查工具:
python main.py 脚本.py --list-audio # 列出现有音轨(含起始时间、音量)
python main.py 脚本.py --no-audio # 忽略音轨,只出画面
GIF 不支持音频。带声音的动画请输出
.mp4/.webm; 如果场景有音轨却要出 GIF,引擎会明确提示,而不是静默丢掉声音。
分组:把一堆图元当一个用
from codeanim import Group, Circle, Rect
card = Group(cx=640, cy=360) # 组的中心
card.add(Rect(cx=0, cy=0, w=300, h=200, fill="#1e293b"))
card.add(Circle(cx=0, cy=-60, r=30, fill="#38bdf8"))
card.rotation.keys([(0, 0), (2, 360)]) # 整张卡片转一圈
card.scale.key(0, 1.0).key(0.5, 1.2).key(1.0, 1.0)
card.opacity.keys([(0, 0), (0.6, 1)])
scene.add(card)
# 嵌套也支持
outer = Group(cx=200, cy=200)
inner = Group(cx=50, cy=0) # 相对 outer 的坐标
inner.add(Circle(r=10))
outer.add(inner)
组内坐标的 (0, 0) 就是组的中心。父组的旋转/缩放/透明度会正确传递到子树。
图层顺序
默认按添加顺序(先加的在下)。用 z= 强制排序,数值越大越靠上:
scene.add(background, z=-100) # 压到最底
scene.add(watermark, z=100) # 永远在最上层
同 z 时保持添加顺序。
图层顺序看不清?让引擎打出来
图元一多,"谁盖着谁"就容易糊。直接问它:
codeanim 脚本.py --layers # 打印绘制顺序
codeanim 脚本.py --layers --layers-at 2.5 # 按第 2.5 秒的状态算位置
图层顺序(从下到上绘制,共 30 项)
────────────────────────────────────────────────────────────────────────
# z 类型 名称 位置 / 尺寸
────────────────────────────────────────────────────────────────────────
1 -100 rect <rect> (0,0) 1280x720
2 0 text <text> c(640,340) '一条命令,一份视频'
3 10 text <text> c(640,330) 'CodeAnim'
4 20 digits <digits> c(1220,650) '0,000 帧'
────────────────────────────────────────────────────────────────────────
⚠ 有多个图元共用同一个 z:0(25项), 10(3项)。
同 z 时按添加顺序画 —— 后加的盖在前面之上。
⚠ 有 1 个静态图元(无关键帧)会被预合成到底层,它们的绘制顺序和动态图元分开处理。
如果你打算让静态图元盖住动态内容,就给它加个无用关键帧。
脚本里也能调:
from codeanim import describe_layers, layer_warnings
print(describe_layers(scene, t=2.0)) # 完整表格
layer_warnings(scene) # 只要提示
会提醒的几种情况:
- 所有 z 都是同一个值 → 顺序全靠添加顺序,容易和预期不符
- 同 z 有多项 → 明确告诉你同 z 的规则
- 有静态图元 → 它们会被预合成到底层,不参与逐帧排序
- z 出现下降 → 通常是组内和组外混排导致的
时间线:不写魔法数字
把片段写成"接受起始时间、返回时长"的函数,然后排列:
from codeanim import seq, par
total = seq(scene, [
lambda s: add_title(scene, start=s), # 自动排 0
lambda s: par(scene, [ # 两个同时
lambda t: add_logo(scene, start=t),
lambda t: add_counter(scene, start=t),
]),
lambda s: add_outro(scene, start=s),
])
scene.duration = total # 场景时长自动跟上
片段不返回时长也行,引擎会去问图元最晚的关键帧(不如显式返回精确)。
也可以直接指定每段占多久
不想让片段自己算?用 (片段, 时长):
total = seq(scene, [
add_title, # 片段自己 return
(add_ball, 2.0), # 至少占 2 秒
(add_logo, 1.5, 0.3), # 至少 1.5 秒,后面再空 0.3 秒
{"clip": add_outro, "span": 2.0, "gap": 0.5}, # 完整写法
])
指定时长是下界,不会截断动画:
| 片段自然时长 | 指定时长 | 实际占用 |
|---|---|---|
| 0.5s | 2.0 | 2.0s(拉长,留静止尾巴) |
| 3.0s | 1.0 | 3.0s(以片段为准,不切半截) |
段间统一留白:
seq(scene, [a, b, c], gap=0.5) # 每段之间空 0.5 秒
par 和 Timeline 也一样:
par(scene, [(fade_in, 2.5), (music_bar, 1.0)]) # 取最长 2.5
tl = Timeline(scene)
tl.add((add_ball, 2.0))
tl.add(add_logo, span=1.5) # 也可以走关键字
链式风格的 Timeline:
from codeanim import Timeline
tl = Timeline(scene)
tl.at(0.0).add(add_title)
tl.after(0.5).add(add_ball) # 接在上一段结束后 0.5 秒
tl.fit() # 撑长场景时长
文件组织
引擎是个包,你的项目里只放自己的 .py:
my_project/
├── main.py 入口
├── title.py 标题片段
├── ball.py 小球片段
└── logo.py 片头片段
每个片段导出一个小函数并返回时长:
# title.py
from codeanim import Text
def add_title(scene, start=0.0, title="Hello"):
t = Text(title, cx=scene.width/2, cy=scene.height/2,
font_size=96, fill="white", anchor="center")
t.opacity.keys([(start, 0), (start + 0.9, 1)])
scene.add(t, z=10)
return 1.5 # 这一段占 1.5 秒
同目录的 .py 可以直接 import,引擎会自动把脚本目录加进 sys.path。
脚本里定义名为 scene 的变量,或一个返回 Scene 的 build() 函数,都行。
输出格式
| 格式 | 用途 | 触发方式 | 透明 |
|---|---|---|---|
| MP4 (H.264) | 默认,兼容性最好 | -o out.mp4 |
— |
| WebM (VP9) | 体积小 | -o out.webm |
✅* |
| MOV | 苹果生态 | -o out.mov |
✅ ProRes 4444 |
| MKV | 通用容器 | -o out.mkv |
— |
| GIF | 发群、贴文档 | --gif |
— |
| PNG 序列 | 交给 AE / Blender | --frames 目录/ |
✅ |
| 单帧 PNG | 快速检查某一刻 | --snapshot 2.0 |
✅ |
* WebM 的 alpha 取决于本机 ffmpeg 的 libvpx 是否编译了该特性。 CodeAnim 会实测探测(编一段带透明的数据再解回来验), 不支持时明确报错并建议改用 MOV,而不是静默输出一个不透明的文件。
背景透明
scene = Scene(1280, 720, background=None) # None = 透明
python main.py overlay.py -o overlay.mov # ProRes 4444
拿去 Premiere / Final Cut / 网页 <video> 上直接叠。
设计约束
- 无 GUI。 永远不弹窗口,不做播放器。
- 纯函数场景。 任意时刻
f(t)可独立求值,不依赖前一帧 —— 因此能任意跳帧、 倒放、多进程并行。tests/test_codeanim.py里有专门的回归用例守住这条。 - 可脚本化。 能塞进 Makefile、CI、GitHub Actions。
- 命令行优先。 所有能力都能通过参数触发。
- 错误说人话。 缺 ffmpeg 告诉你怎么装;脚本报错只显示你自己的文件和行号; 空间不足、容器不支持透明这类问题给出具体解决办法,不糊一屏内部堆栈。
引擎能力
- 零落盘流式渲染:磁盘占用 = 最终视频大小
- 抗锯齿:2 倍超采样 + LANCZOS 高质量缩放
- 并行渲染:帧之间零依赖,多进程打满 CPU
- 静态预合成:不动的内容只画一次,之后每帧复用
- 快速路径:不需要旋转/缩放的图元直接画在底图,省掉整张图层分配(实测快 8 倍)
- 中文字体:自动查找系统字体,支持
.ttc;也可用CODEANIM_FONT或项目fonts/
测试
python tests/test_codeanim.py
335 项断言,覆盖颜色、缓动、关键帧、纯函数性、中心定位、分组父子变换与 组内定位回归、图片图元、音频(导入 + 生成 + 混音方案 + 音量曲线)、 帧动画、Digits(含自定义格式化)、时间线、片段时长指定、图层顺序、 手感糖、热重载监视与按键、终端内嵌预览、参数校验、磁盘预算、编码参数、超采样。
例子
| 文件 | 演示内容 |
|---|---|
examples/promo/ |
完整宣传片(19s/1080p,四个片段文件,见下) |
examples/demo.py |
完整片头:seq/par 编排 + Group + Digits + z 图层 |
examples/chart.py |
柱状图 + 折线 + 数值滚动 |
examples/particles.py |
300+ 图元压性能 |
examples/media.py |
图片 + 音频:图片复用、着色、圆角;声音按秒排布 |
examples/alpha_overlay.py |
背景透明,供后期叠加 |
examples/title.py ball.py logo.py |
可复用的片段模块 |
宣传片
用引擎自己的 API 做的 19 秒 1080p 宣传片,顺便当"这引擎能做什么"的实证:
python main.py examples/promo/__main__.py
python main.py examples/promo/__main__.py --watch --inline # 改代码看效果
四段式,每段一个文件:
| 文件 | 内容 |
|---|---|
promo/intro.py |
片头:CodeAnim 弹入 + 定位语 |
promo/code_panel.py |
左边打真实 Python 代码(逐行浮现 + 光标),右边实时出画面 |
promo/showcase.py |
四张能力卡,卡内各自跑真动效(公转圆、三种缓动对比、整体旋转、数字滚动) |
promo/outro.py |
$ python main.py demo.py → out.mp4 已生成 → Slogan |
各段用 segment_visibility() 管生命周期,seq 自动排布:
total = seq(scene, [
lambda s: add_intro(scene, start=s),
lambda s: add_code_panel(scene, start=s),
lambda s: add_showcase(scene, start=s),
lambda s: add_outro(scene, start=s),
])
scene.duration = total
全部特性一览
渲染
- 零落盘流式渲染(磁盘占用 = 最终视频大小)
- 2 倍超采样抗锯齿
- 多进程并行(帧间零依赖)
- 静态预合成 + 快速路径(不需要变换的图元直接画)
- 中文字体自动查找(支持
.ttc)
API
- 图元:
CircleRectLineTextImageDigitsGroup - 定位:
cx/cy按几何中心,center_x()/center_y()动态句柄 - 图层:
z=,--layers打印绘制顺序 - 时间线:
seq/par/Timeline - 手感:
add_circle/add_image/span_of/fit_duration/add_all/set_text/row/grid Digits自定义格式化:formatter=/default_formatter()- 25 条缓动曲线
- 颜色插值
素材
- 图片:任意路径或内存位图,支持裁剪 / 九宫格 / 圆角 / 着色 / 等比适配
- 帧动画:雪碧图自动切帧(
sprite=)或手动切(frames=),支持play(fps=)/frame关键帧 / 倒放 - 音频:导入任意格式,或用
beep/chime/sweep/whoosh/arpeggio生成 - 音频按"第几秒响"配置,支持音量 / 音量曲线 / 淡入淡出 / 循环 / 裁切 / 变速
工作流
--watch热重载(含按键交互:暂停 / 单步 / 换时间点)--inline终端内嵌画面(半块字符直接画)--preview快速看图--layers图层顺序调试doctor环境检查- 磁盘空间预估与红线保护
- 报错只显示用户文件 + 行号
输出
- MP4 / WebM / MOV / MKV / GIF / PNG 序列 / 单帧
- 背景透明的 MOV (ProRes 4444) 与 WebM (VP9)
Metadata
Release files for codeanim-py 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| codeanim_py-0.2.0.tar.gz | 149.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| codeanim_py-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 280.6 kB
Release files / codeanim_py-0.2.0.tar.gz
| Download URL | codeanim_py-0.2.0.tar.gz |
|---|---|
| Size | 149.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ac0d28d9eefb0cd987daafe563e337da417d184192959c19b917d35d92bde69f
|
|
BLAKE2b-256 checksum How to use checksums |
d4a9fbeafcd862e0495b660f1b81fd62826889598481763d1c9b0653e95c344c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / codeanim_py-0.2.0-py3-none-any.whl
| Download URL | codeanim_py-0.2.0-py3-none-any.whl |
|---|---|
| Size | 131.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
45f98f6715f00bc8e16d45db90bb10c33470fa1266ff2d3957be03b5632e6f42
|
|
BLAKE2b-256 checksum How to use checksums |
92db693df50ccb8210b364438c9b9d27caeee654cf6ac27be69a7ba57c761e74
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|