关于 cavalry
通过 JavaScript 脚本和 Cavalry Bridge 控制 Cavalry 动画软件的技能。它利用 API 模式和辅助脚本构建、检查并渲染场景,支持迭代预览。
主要功能
- 01通过 JavaScript 控制 Cavalry
- 02使用 API 辅助脚本构建场景
- 03创建关键帧与缓动
- 04检查渲染结果和场景数据
- 05渲染 PNG 帧序列
适用场景
- 01构建动画场景
- 02制作文字或形状动画
- 03预览并修正合成
- 04渲染帧序列
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jablonkai/ skills --skill cavalry安装后在 Claude Code 中输入/cavalry确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api-reference.mdreferences/cavalry-types/deformer.d.tsemitter.d.tsLICENSEmodifier.d.tsplugin.d.tsREADME.mdrender.d.tsscript.d.tsshape.d.tsutility.d.tsreferences/cavalry-types/namespaces/api.d.tscavalry.d.tsconsole.d.tsctx.d.tsdef.d.tsinstall.d.tsplugin.d.tsrender.d.tsui.d.tsscripts/cavalry-bridge.jscavalry-helpers.jscavalry-send.sh详细介绍
Cavalry 控制
Cavalry(/Applications/Cavalry.app)支持使用 JavaScript 编写脚本(自 2.6 起采用 V8 12.4,因此支持 ES2024——let/const、箭头函数和 Set 方法均可使用;提供 api.* / ui.* / cavalry.* 模块)。通过 Cavalry Bridge 控制它——这是一个运行在 Cavalry 内部的小型脚本 UI,可执行 POST 到 127.0.0.1:8731 的任意内容。以下所有内容均已在真实项目中通过 Cavalry 2.7.2(2026 年 5 月发布的当前版本)验证;其余 API 内容请参阅参考文件。
自 2.7 起,Cavalry 归 Canva 所有:登录需要使用 Canva 账号,原先的 Professional 功能现已向所有人免费开放——因此无需考虑许可等级。唯一的变化是 2.7.x 不再附带 CLI(参见“渲染”)。
- references/api-reference.md — 精简的模块参考(api、cavalry、ui、WebClient/WebServer、渲染脚本、CLI)。需要使用下方速查表未涵盖的内容时,请查阅此文件。
- references/cavalry-types/ — 以随附 TypeScript 定义和 JSDoc 示例呈现的完整 API;可在其中搜索参考文档未涵盖的函数。
- scripts/cavalry-bridge.js — 需安装到 Cavalry 中的桥接程序。
- scripts/cavalry-helpers.js —
setAttrs/keyAttrs(设置属性和关键帧,失败时会明确报错,而非静默失败)、newComp/useComp/findComp、splitLetters(逐字母文本)、renderFrames、writeJSON。在每个构建脚本顶部通过api.load("<abs path>/scripts/cavalry-helpers.js")引入。 - scripts/cavalry-send.sh — 将 JS 文件(或
-c 'inline code')发送到桥接程序并等待执行完成;失败时返回非零退出码并打印 JS 错误。--ping用于检查桥接程序是否可访问,并报告桥接程序和 Cavalry 的版本。
控制循环
- 用户启动桥接程序(每个会话只需一次):Cavalry 必须正在运行,并从其 Scripts 菜单启动
Cavalry Bridge.js。无法远程执行此操作——如果bash scripts/cavalry-send.sh --ping没有收到回复,请让用户打开 Cavalry 并运行 Scripts ▸ Cavalry Bridge。首次安装桥接程序时,将 scripts/cavalry-bridge.js 复制到 Cavalry Scripts 文件夹,并命名为Cavalry Bridge.js(~/Library/Application Support/Cavalry/Scripts/——如果文件夹不存在,请创建;Help ▸ Show Scripts Folder 可确认路径)。如果--ping有回复但没有"version"字段,则表示运行的是旧版桥接程序:用当前版本覆盖它并重启。 - 将构建脚本写入暂存区并发送:
bash scripts/cavalry-send.sh /path/to/build.js。每次发送都会获得一个请求 ID,并在用户的 Cavalry 偏好设置文件夹中等待对应的结果文件(该路径通过桥接程序的GET /get回复发现),因此多个调用方可以安全地共用一个桥接程序。长时间渲染需要设置CAVALRY_SEND_TIMEOUT=600(秒)。 - 反馈通过文件返回,而不是通过 HTTP。 桥接程序会报告成功或失败,以及抛出错误的异常消息和堆栈;
console.log输出会留在 Cavalry 的 Log 窗口中。因此,每个脚本都必须将结果写入磁盘,以便读取:- 视觉状态 → 在几个关键帧调用
api.setFrame(f); api.renderPNGFrame(dir + "/check_" + f, 50)(50 = 50% 缩放预览),然后读取 PNG。应在构建完成后单独发送渲染脚本——脚本中创建的连接(变形器、错开效果)会等到脚本返回后才进行评估,因此同一次发送中的预览可能不准确; - 数据(ID、边界框、属性值)→
api.writeToFile(path, JSON.stringify(x), true)。
- 视觉状态 → 在几个关键帧调用
- 迭代:检查预览、修复脚本并重新发送。构建脚本应可重复运行——每个版本都创建新的合成(
api.createComp("Thing v2")),避免重复运行时将图层叠加到旧合成中。当前合成属于共享 UI 状态(用户或其他调用方可能会在两次发送之间更改它),因此,任何在后续发送中进行渲染或编辑的脚本都应先按名称查找其合成——使用辅助函数中的useComp("Thing v2")。 - 最终渲染:PNG 帧循环 + ffmpeg(见下文“渲染”)。
发送失败时,stderr 会输出 Cavalry error: <message + stack>——行号与发送的脚本相对应。如果失败但没有消息,则表示存在语法错误(脚本尚未启动)或权限提示被拒绝:使用 node --check build.js 检查,并请用户查看 Cavalry 是否有打开的对话框。如果发送在渲染期间超时,通常只是渲染耗时较长——应延长超时时间,而不是重新发送,否则会运行两次渲染。
场景构建速查表(经验证的模式)
api.set 从不报错——检查名称,不要猜测
如果图层没有某个属性,api.set 会静默忽略:属性名称拼写错误,或名称来自旧版 Cavalry,都会被直接忽略,发送仍会报告成功。因此,使用 try/catch 回退链(“尝试 frameRate,否则用 fps”)毫无用处——第一个猜测总会“成功”。请使用辅助函数中的 setAttrs(id, {...})(它会检查 api.hasAttribute,并抛出错误,指出属性名称和图层类型);如果不确定,请使用 api.getAttributes(id) 列出实际名称。
合成设置
api.load("/abs/path/to/skills/cavalry/scripts/cavalry-helpers.js");
var comp = newComp("My animation v1", {width: 1920, height: 1080, fps: 30, frames: 240,
background: "#121212"}); // or "transparent"
即 api.createComp + api.setActiveComp + resolution、fps、startFrame/endFrame(包含首尾帧——240 帧即 0–239)和 backgroundColor。新建合成默认使用不透明背景(白色,除非用户偏好设置不同):设置 backgroundColor 即可使用纯色——无需添加超大背景矩形;设为 "#00000000"(alpha 0)则可输出透明背景。
形状和属性
var rect = api.primitive("rectangle", "Bar"); // also: "ellipse", "polygon", "star"...
setAttrs(rect, {"generator.dimensions": [400, 60], // rectangle: [w, h]
"position": [0, 0], // origin = comp centre, +y = UP
"material.materialColor": "#00ADEF"});
var dot = api.primitive("ellipse", "Dot");
setAttrs(dot, {"generator.radius": [20, 20]}); // ellipse: radius [rx, ry], not dimensions
常见属性路径:position [x,y]、position.x、scale.x/scale.y、rotation.z、
opacity、material.materialColor(十六进制字符串)、material.alpha(0–100 范围;值
≤ 1 时几乎不可见)、generator.dimensions(矩形)、generator.radius(椭圆)。
要查找名称:api.getAttributes(id)、api.getAttributeDefinition(id, attr)(类型、
默认值、枚举值),或在 UI 中右键单击属性 → 复制脚本路径。
可靠的尺寸调整:测量,而非猜测
文本和导入的图像没有可设置的尺寸属性,因此请测量实际尺寸并用缩放归一化:
var bb = api.getBoundingBox(layer, true); // world space: {x,y,width,height,left,right,top,bottom,centre}
var s = 470 / bb.height; // e.g. scale an image or title to 470 px tall
setAttrs(layer, {"scale.x": s, "scale.y": s});
在世界空间中,top > bottom(+y 方向朝上)。请在设置缩放关键帧之前测量。
关键帧和缓动
api.keyframe(layer, 22, {"position.y": 940});
api.keyframe(layer, 50, {"position.y": 150});
api.magicEasing(layer, "position.y", 22, "BounceOut"); // easing is applied per-attr AT a keyframe
api.keyframe 只接受标量叶属性。 数组——{"position": [x, y]}、
{"generator.dimensions": [w, h]}——或用于向量属性的单个数字——
{"rotation": 45}(从 2.x 起,rotation 包含 x/y/z;2D 旋转使用 rotation.z)——都不会创建
关键帧,也不会报错。请直接为 position.x/position.y、scale.x、rotation.z 等设置关键帧,
或使用辅助函数中的 keyAttrs(id, frame, {...}),它会拆分数组,将单个 rotation
值映射到 rotation.z,并拒绝未知名称。magicEasing 同样指定单个叶属性
("rotation.z",而不是 "rotation");可通过 api.getKeyframeTimes(id, "position.x") 检查关键帧。
(api.set 更宽容:数组和单个 rotation 数值都可用。)
Magic easing 预设:SlowIn/SlowOut/SlowInSlowOut、VerySlowIn/VerySlowOut/VerySlowInVerySlowOut、
SpringIn/SpringOut/SpringInSpringOut、SmallSpring… 变体、AnticipateIn、OvershootOut、
AnticipateInOvershootOut、BounceIn/BounceOut/BounceInBounceOut、None。
制作经验:文字弹入时用 OvershootOut,下落/着陆时用 BounceOut,
爆发效果(粒子、尘埃)用 SlowOut,漂移和脉动时用 SlowInSlowOut。
简易粒子爆发(无需粒子系统):创建 N 个小椭圆,从同一原点沿 Math.cos/sin(angle) * dist 向外运动,
缩放至 0,使用 SlowOut——完整的彩纸/火花代码片段请参阅参考资料。
文本——以及逐字母动画方法
var t = api.create("textShape", "Title");
setAttrs(t, {"text": "EMU 6-DAY RACE",
"horizontalAlignment": 1, "verticalAlignment": 1, // 1 = centre
"fontSize": 150, "font.font": "Nebula Sans", "font.style": "Black",
"material.materialColor": "#FFFFFF", "autoWidth": true, "autoHeight": true,
"position": [0, -190]});
如果没有 autoWidth,文本框会换行显示过长的行。Unicode(重音符号、ő/ű)可直接使用。使用字体前请先检查:
cavalry.fontExists(family, style)(两个参数都必填)和 cavalry.getFontStyles(family);如果字体不存在,
请改用已存在的字体,避免 Cavalry 静默替换。
逐字母动画(每个字形依次下落)——保留一个文本图层,并连接
Sub-Mesh 变形器 + 用于驱动其时间偏移的 Stagger。Sub-Mesh 默认使用 Level Mode
Text (Characters)(levelMode 为 3),因此每个字形都会获得一份独立的 Sub-Mesh 关键帧副本,
并由 stagger 逐个错开:
var subMesh = api.create("subMesh", "Title bounce");
api.keyframe(subMesh, 34, {"shapePosition.y": 990}); // start above frame (+y = up)
api.keyframe(subMesh, 64, {"shapePosition.y": 0});
api.magicEasing(subMesh, "shapePosition.y", 34, "BounceOut");
api.connect(subMesh, "id", t, "deformers");
api.parent(subMesh, t);
var stagger = api.create("stagger", "Title stagger");
setAttrs(stagger, {"minimum": -12, "maximum": 0}); // frames of per-letter offset
api.flipGraph(stagger, "graph", "vertical"); // so the FIRST letter lands first
api.connect(stagger, "id", subMesh, "shapeTimeOffset");
api.parent(stagger, t);
请在单独的一次发送中预览。 Cavalry 只有在创建连接(变形器、stagger、行为)的脚本返回后才会计算这些连接——
在同一次发送中渲染的帧会显示所有字母一起移动,看起来就像 stagger 出了问题。请在一次发送中构建,
然后在下一次发送中调用 useComp(...) + 渲染。同样,api.get 返回的 Sub-Mesh 值不会包含 stagger 效果;
只有渲染帧才能证明逐字母动画有效。
如果每个字形都需要单独的缓动、颜色或路径(或需要通过脚本重新排版),辅助函数中的
splitLetters(str, style, cx, cy) 会为每个字形创建一个文本图层,并将其放置在完整字符串中的准确位置
(包括字距调整);为每个图层设置各自的起始帧:
var letters = splitLetters("NIGHT RUN 2026",
{"fontSize": 150, "font.font": "Helvetica", "font.style": "Bold",
"material.materialColor": "#FFFFFF"}, 0, 40); // line centred on [0, 40]
letters.forEach(function (id, i) {
var y = api.get(id, "position.y"), f0 = 10 + i * 3; // 3-frame stagger
api.keyframe(id, f0, {"position.y": y + 700});
api.keyframe(id, f0 + 20, {"position.y": y});
api.magicEasing(id, "position.y", f0, "BounceOut");
});
图像和其他素材
var asset = api.loadAsset("/abs/path/logo.png", false); // false = not an image sequence
var layer = api.addAssetToComp(asset);
var bb = api.getBoundingBox(layer, true);
var s = 470 / bb.height; // scale to ~470px tall
setAttrs(layer, {"scale.x": s, "scale.y": s});
数据驱动场景:api.jsonFromAsset(assetId) 可解析 JSON/CSV 素材,
api.loadGoogleSheet(spreadsheetId, sheetId) 可读取实时表格,而 api.WebClient 可从任意 HTTP API 获取数据——详见参考资料。
渲染
默认方式:PNG 帧循环 + ffmpeg。 除桥接外无需其他工具,可供你检查每一帧,随后可从自己的 Shell 中运行 ffmpeg:
api.load("/abs/path/to/skills/cavalry/scripts/cavalry-helpers.js");
useComp("My animation v1");
renderFrames(OUT, "name", 0, 239, 100); // OUT/name_0000.png … name_0239.png
renderPNGFrame(pathWithoutExtension, scalePercent) 会渲染当前活动合成的帧;会自动追加 .png,且只渲染可见图层(独显状态也会生效——调用 api.soloLayers([]) 可清除)。渲染 240 帧 1080p 画面需要一段时间——请调高 CAVALRY_SEND_TIMEOUT。然后将帧合成为视频:
ffmpeg -framerate 30 -i name_%04d.png -c:v libx264 -pix_fmt yuv420p out.mp4 # opaque
ffmpeg -framerate 30 -i name_%04d.png -c:v qtrle out.mov # alpha overlay
如需输出透明背景,请为合成设置透明背景(newComp(..., {background: "transparent"}),即将 backgroundColor 设为 "#00000000")——如果合成保留默认设置,即使没有 BG 图层,渲染出的背景也会是不透明的白色。这样 PNG 会保留 Alpha 通道,qtrle .mov 可直接作为叠加素材导入 DaVinci Resolve(较小的替代方案是 ProRes 4444:-c:v prores_ks -profile:v 4 -pix_fmt yuva444p10le)。请验证:
ffprobe -v error -show_entries stream=pix_fmt out.mov 应显示 argb/rgba,而非 rgb24。
请从 Shell 运行 ffmpeg,而不要通过 api.runProcess 运行——从桥接这类脚本 UI 调用该命令时,会弹出“信任此脚本”对话框,并一直阻塞,直到用户作出回应。
api.renderSVGFrame(path, scale, skipComps) 可用于生成矢量快照。
渲染队列(应用内):api.addRenderQueueItem(compId) → 配置 → api.render(itemId) 或 api.renderAll()。适用于输出原生 mp4/ProRes/GIF,也适用于批量渲染多个合成或场景(每个文件调用一次 api.openScene(path, true))。渲染队列项目可以附带 Setup/Pre/Post 渲染脚本(例如在每次渲染时替换素材或文本)——请参阅参考文档。
Cavalry CLI——2.7.x 中不可用。 Scene Group 的 2.7.0–2.7.2 版本说明称 CLI 缺失(“请继续使用当前版本……我们正在努力恢复此功能”),而在 2.7 之前,其 render/list 命令需要 Enterprise 许可证。2.7.2 版本包中仍有 CavalryCLI.app,但其中的 Contents/MacOS/ 为空——没有可调用的二进制文件。要以无头方式批量渲染,请改为通过桥接驱动正在运行的应用(打开每个场景、渲染、运行 ffmpeg);如果用户固定使用较旧版本,请参阅参考文档了解 2.7 之前的 CLI 参数。
踩坑经验(来之不易)
- 桥接器采用“发后即忘”模式——围绕基于文件的反馈来设计每个脚本(状态 JSON、预览 PNG、错误捕获文件)。绝不要假设能看到
console.log。 - 桥接器会将
api.exec代码包装在 IIFE 中,因此顶层直接使用var没问题,但每次发送之间不会保留任何内容。请将状态保存在场景中或磁盘上。 api.set/api.keyframe不会报错:未知名称会被忽略,不会产生错误;keyframe还会忽略向量上的数组和裸数字——请使用setAttrs/keyAttrs。本文件中的名称适用于 2.7.2(fps、endFrame、text、backgroundColor);此前有关frameRate/string/frameRangeEnd的说明不适用于 2.7。如果脚本还必须在旧版本中运行,请根据api.getCavalryVersion()进行分支判断。- 颜色:2.6 移除了 Linear Gamma(合成设置)和 Color Filter 着色器——请改用工作色彩空间为 Linear sRGB 的色彩管理。
material.alpha的范围是 0–100,不是 0–1。- 新图层会添加到当前活动合成中。
api.create/api.primitive/addAssetToComp会将图层添加到当前活动的合成中——创建任何内容之前都要先调用newComp/useComp,即使是在一次性探测脚本中也不例外。 - 堆叠和分组:后创建的图层会绘制在上方——先创建背景和底板,再创建其上的文本。若要将多个图层作为一个整体移动(例如字幕条底板、色条和文本),请使用
api.create("null", "Group")创建空对象,并通过api.parent将这些图层设为其子级,然后对空对象添加动画。 - 向量属性读取时会返回对象:
api.get(id, "position")返回{x, y}(resolution、generator.radius同理,backgroundColor返回{r,g,b,a}),而api.set接受[x, y]。需要数字时,请读取position.x/position.y。 - 低成本预览:在几个关键情节点以 50% 比例渲染检查帧,而不是渲染每一帧;只有预览效果满意后,才渲染完整分辨率的整个范围。
- 重新运行:每次尝试都通过
api.createComp创建一个全新的带版本号的合成;在同一合成中重新运行会复制所有图层。 - 在脚本中保存场景——
api.saveSceneAs("/path/scene.cv")——这样即使发生崩溃,工作成果也能保留;如果模态对话框处于打开状态,该调用会返回false(或抛出异常),因此请检查结果。未经询问,绝不要调用api.newScene()/api.openScene()——它们会丢弃用户当前打开的场景。 - 随附的类型定义也可作为离线文档——几乎每个函数都有 JSDoc 示例;猜测签名之前先用 grep 搜索。
安全
运行桥接器意味着在用户的计算机上运行一个代码执行服务器。要求用户启动之前,请明确告知这一点。
- 桥接器绑定到
127.0.0.1:8731,并会在实时会话中执行 POST 到该地址的任何 JavaScript——使用用户的权限以及用户当前打开的场景。原始非 JSON 请求体会直接作为 JS 运行。请求没有身份验证:任何本地进程,以及共享计算机上的其他用户,都可以通过桥接器控制 Cavalry。 - 网页无法访问。带有
Origin标头或跨站Sec-Fetch-Site的请求会被拒绝并返回 403,因此用户浏览器中的网页无法访问桥接器。这项检查是唯一的防护措施——没有令牌。 - 只有用户运行桥接器后,它才会开始监听。 将
cavalry-bridge.js复制到 Scripts 文件夹只会将其添加到菜单中;只有用户选择 Scripts ▸ Cavalry Bridge 后,端口才会打开,并在该会话期间保持开启。 - 要停止桥接器,请退出 Cavalry。 没有远程关闭功能;进程结束时端口会释放。