No description
  • Rust 93.5%
  • Shell 4.4%
  • PowerShell 2.1%
Find a file
2026-09-02 13:25:02 +08:00
assets session: fish support with zero injection 2026-09-02 12:26:41 +08:00
examples session: bash and zsh integration via OSC 133 2026-09-02 12:24:10 +08:00
scripts init: project scaffold, toolchain wrapper, DSL parser with tests 2026-09-02 09:35:23 +08:00
src session: fish support with zero injection 2026-09-02 12:26:41 +08:00
tests session: fish support with zero injection 2026-09-02 12:26:41 +08:00
.gitignore render+encode: avt renderer, dual fonts, GIF/MP4 output, CLI 2026-09-02 10:48:49 +08:00
AGENTS.md add agent.md 2026-09-02 13:25:02 +08:00
Cargo.lock init: project scaffold, toolchain wrapper, DSL parser with tests 2026-09-02 09:35:23 +08:00
Cargo.toml init: project scaffold, toolchain wrapper, DSL parser with tests 2026-09-02 09:35:23 +08:00
README.md first commit 2026-09-02 13:24:43 +08:00

termcast

把"脚本"渲染成终端操作演示视频(GIF / MP4)。

写一个描述"你想让观众看到什么"的 DSL 脚本,termcast 在真实 PTY 里逐字执行这些命令、 录制带时间戳的输出流,再把空闲时间钳制、把超长命令压缩到目标时长,最终渲染成 带字幕标题的演示视频。因为走的是真实终端仿真,输出、颜色、进度条、交互式回显 都和真机一模一样——不是事后伪造的字幕动画。

command "更新", "5s" do
    ping -n 11 127.0.0.1        # 真实运行约 10 秒
end

command "say hello" do
    echo "hello"
end

上面这段脚本会生成一个约 7 秒的视频:第一条命令实际跑 10 秒,被 压缩到 5 秒播完;第二条按实际节奏原速播放。每个 command 块的 标题(如"更新")会作为字幕显示在画面顶部。

特性

  • 真实执行:命令在 ConPTY/PTY 驱动的真实 shell 中运行,输出流被原样录制。
  • 精确切段:段边界由 OSC 133 语义标记(D;exit/A/B)检测——由注入的 shell 集成或 fish 原生提供——不靠提示符正则猜测。
  • 时间重映射:段内空闲钳制(idle)+ 目标时长压缩(只压缩、不放慢), 段间事件顺序有回归测试保证不交错。
  • asciinema 兼容:录制格式为 asciinema v2(.cast JSONL),可用 --keep-cast 保留并用自带 replay 示例单独回放检查。
  • 渲染与内容解耦:字体、字号是渲染期参数,默认 Cascadia Mono + 微软雅黑 作为 CJK 回退(缩放到双格宽),网格尺寸由录制决定。
  • GIF + MP4 双输出:MP4 依赖 ffmpeg,找不到时自动跳过并提示。

快速开始

cargo run --release -- build examples/hello.dsl -o out/hello.gif

默认同时输出 out/hello.gif 和 out/hello.mp4。examples/ 下还有 hello-bash.dsl(bash + 中文输出的例子)。

工作原理

script.dsl ─解析─► 段列表 ─PTY驱动─► 带时间戳的输出事件流(.cast)
                              │  段边界由 OSC 133 语义标记检测
                              ▼
                    时间重映射(空闲钳制 + 目标时长压缩)
                              ▼
              avt 终端仿真 → 字体渲染 → RGBA 帧 → GIF / ffmpeg → MP4
  1. 解析(dsl.rs):脚本解析为 set 设置 + command 段列表。
  2. 录制(session.rs):在 ConPTY/PTY 里起一个持久 shell,逐字"打字"执行 每段命令。注入的集成配置(assets/prompt.ps1、assets/bashrc、 assets/zshrc)发出 OSC 133 标记,驱动器由此精确知道每条命令何时结束。 fish ≥ 4.0 则零注入:它原生就发这些标记,只用 --no-config -C 起隔离会话 并统一提示符样式。
  3. 重映射(retimer.rs):钳制段内空闲停顿,把超过目标时长的段线性加速 到目标;有回归测试保证段事件不溢出、不交错。
  4. 渲染(render.rs):avt 终端仿真逐帧推进,双字体光栅化为 RGBA。
  5. 编码(encode.rs):写 GIF,若找到 ffmpeg 再写 MP4。

DSL 参考

set fps = 30            # 帧率
set font-size = 18      # 字号(px)
set geometry = "80x24"  # 终端尺寸
set typing = "40ms"     # 打字间隔
set idle = "800ms"      # 无目标时长的段内最大停顿
set gap = "300ms"       # 段间停顿
set shell = "powershell"  # 或 "pwsh" / "bash" / "zsh" / "fish"

command "标题", "5s" do   # 时长可省略 = 原速;支持 5s / 500ms / 5(秒)
    多行命令...
end

时长语义:只会压缩,不会放慢。真实时长超过目标则线性加速到目标; 不足目标则以原速播完后停顿补齐。

CLI 参考

termcast build <script.dsl> [选项]
选项 说明
-o, --out <PATH> 输出 GIF 路径(MP4 写在同名 .mp4)
--fps <N> 覆盖脚本中的帧率
--font-size <PX> 覆盖脚本中的字号
--geometry <WxH> 覆盖脚本中的终端尺寸,如 80x24
--main-font <PATH> 主字体路径(默认 Cascadia Mono)
--cjk-font <PATH> CJK 回退字体;传空串 "" 禁用
--dump-frames <DIR> 把每帧 RGBA 转储为 PNG,便于排查渲染问题
--keep-cast 保留中间产物 .cast 和 .segments.json

CLI 选项优先于脚本里的 set 值。默认输出名取脚本文件名主干(如 hello.dsl → hello.gif)。

回放与调试

cargo run --release -- build examples/hello.dsl --keep-cast -o out/hello.gif
cargo run --example replay -- out/hello.cast   # 直接回放录制,检查分段是否正确
  • TERMCAST_DEBUG=1:输出 PTY 数据块、各阶段耗时、逐帧屏幕转储。
  • TERMCAST_FFMPEG:显式指定 ffmpeg 路径(默认先找 PATH,再找 WinGet 包目录)。

Shell 支持

Shell 方式 备注
PowerShell / pwsh 注入 assets/prompt.ps1 经实测 ConPTY 透传 OSC 133
bash 注入 assets/bashrc Windows 上必须是 Git Bash——裸 bash 会解析到 WSL,读不了 Windows 路径的 rcfile;仅装了 WSL 的主机会自动把路径翻译成 /mnt/c/...
zsh 注入 assets/zshrc 需要 MSYS2/WSL,或在 Linux/macOS 上使用
fish ≥ 4.0 零注入 原生 OSC 133 标记,--no-config -C 起隔离会话;fish < 4.0 不支持

构建(Windows / GNU 工具链)

本仓库在 Windows 上用 rustup 的 stable-x86_64-pc-windows-gnu 工具链构建 (rust-toolchain.toml 已 gitignore,因为它只适用于本机;Linux/macOS 直接 cargo build 即可)。GNU 目标需要真实的 mingw-w64 binutils(rustup 自带的 dlltool 缺 as,无法生成导入库):

winget install BrechtSanders.WinLibs.POSIX.UCRT   # 一次性
sh scripts/cargo.sh build    # 包装脚本把 WinLibs 的 bin 挂进 PATH
sh scripts/cargo.sh test     # 13 单测 + 2 个真实 PTY 端到端测试

MP4 需要 ffmpeg(winget install Gyan.FFmpeg)。

测试

  • dsl 解析器、retimer(含"目标段事件不得溢出到下一段"回归)、OSC 133 扫描器:纯单元测试。
  • tests/session_e2e.rs:真实 ConPTY 会话,验证 133 标记往返、切段、cast 落盘(Windows only;未安装 zsh/fish 时软跳过)。

项目结构

模块 职责
src/dsl.rs 脚本解析(set / command ... do ... end)
src/session.rs PTY 驱动、shell 集成、asciinema v2 事件录制
src/retimer.rs 时间重映射:空闲钳制 + 目标时长压缩
src/render.rs avt 终端仿真 + 双字体(拉丁/CJK)光栅化
src/encode.rs GIF / ffmpeg MP4 / PNG dump 输出
src/main.rs CLI(clap)
assets/ 各 shell 的注入式集成配置
examples/ 示例脚本 + replay 回放工具

模块按 dsl → session → retimer → render → encode 单向分层。

已知限制 / 后续方向

  • GIF 体积偏大(逐帧独立调色板);可做全局调色板 + 帧差分。
  • 段内打字回显依赖 PSReadLine / readline 的定位重绘,段落间事件顺序已由 retimer 保证不交错;极端 ConPTY 重绘差异未穷尽测试。
  • Windows 上 zsh/fish 未经实测(本机未装,e2e 测试会软跳过;Linux/macOS CI 可覆盖)。
  • 计划:主题可配、--speed 全局倍速、avt dump 快照加速 seek(生成器是 离线的,暂不需要)。

许可证

Apache-2.0