@engine-room/after-effects-mcp 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -111
- package/bin/server.js +119 -65
- package/package.json +10 -7
- package/panel/CSXS/manifest.xml +2 -2
- package/panel/client/main.js +21 -38
- package/panel/jsx/bundle.jsx +44 -5
- package/panel/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,32 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# After Effects MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Control Adobe After Effects with AI.** Describe the animation you want — a lower third, a logo reveal, an animated counter — and it gets built in your project: layers, keyframes, easing, effects, expressions and text, all editable afterwards like anything you would make by hand.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Works with Claude and other MCP clients, on macOS and Windows.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
"Build a lower third that says Chapter One, sliding in from the left with an easy ease,
|
|
9
|
-
in my house style."
|
|
10
|
-
```
|
|
7
|
+
Requires After Effects 2026 and [Node.js 20+](https://nodejs.org).
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
## Getting started
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
### 1. Create a project folder
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
In a terminal:
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/plugin marketplace add Engine-Room-Games/after-effects-mcp
|
|
22
|
-
/plugin install after-effects@engine-room
|
|
15
|
+
```bash
|
|
16
|
+
npx @engine-room/after-effects-mcp init my-video
|
|
17
|
+
cd my-video
|
|
23
18
|
```
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### Any other MCP client
|
|
20
|
+
Open that folder in your AI client. It contains everything the client needs to find the tools.
|
|
28
21
|
|
|
29
|
-
|
|
22
|
+
*If your client uses one global configuration file rather than per-folder settings* — Claude Desktop, for example — add this to it instead. On macOS it is `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
30
23
|
|
|
31
24
|
```json
|
|
32
25
|
{
|
|
@@ -39,110 +32,105 @@ Add this to your client's MCP config (for Claude Desktop, `~/Library/Application
|
|
|
39
32
|
}
|
|
40
33
|
```
|
|
41
34
|
|
|
42
|
-
Restart the client.
|
|
35
|
+
Restart the client afterwards.
|
|
43
36
|
|
|
44
|
-
|
|
37
|
+
### 2. Connect After Effects
|
|
45
38
|
|
|
46
|
-
|
|
39
|
+
Open After Effects, then ask the AI:
|
|
47
40
|
|
|
48
|
-
>
|
|
41
|
+
> Set up After Effects.
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
It installs a small panel inside After Effects, then tells you to quit and reopen it. Ask it to check the setup again to confirm.
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
2. If it still doesn't connect, **reboot once**. On some macOS builds the Adobe preference only applies after a restart. One time only.
|
|
45
|
+
This is a one-time step per machine. Until it is done, every tool reports that it cannot reach After Effects.
|
|
54
46
|
|
|
55
|
-
|
|
47
|
+
### 3. Start working
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
With After Effects open and your project folder open in your client, describe what you want:
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
npx @engine-room/after-effects-mcp init my-video
|
|
61
|
-
```
|
|
51
|
+
> Build a lower third that says Chapter One, sliding in from the left.
|
|
62
52
|
|
|
63
|
-
|
|
53
|
+
The AI reads the current state of your comp, makes the change, and can take a screenshot to check the result.
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
my-video/
|
|
67
|
-
├── .claude/skills/house-style/SKILL.md ← your palette, type, timing. Edit this.
|
|
68
|
-
├── CLAUDE.md ← what this project is
|
|
69
|
-
└── renders/
|
|
70
|
-
```
|
|
55
|
+
Day to day, this is the only step you repeat. Create a new folder when you start a new project.
|
|
71
56
|
|
|
72
|
-
|
|
57
|
+
## Your house style
|
|
73
58
|
|
|
74
|
-
|
|
59
|
+
Each project folder contains `.claude/skills/house-style/SKILL.md`. Fill it in with your colours, fonts and motion defaults, and everything built afterwards follows them.
|
|
75
60
|
|
|
76
|
-
|
|
61
|
+
A quick way to write it: build one piece the way you want it, then ask the AI to read that comp and write it up in the house-style skill.
|
|
77
62
|
|
|
78
|
-
|
|
63
|
+
Keep one folder per client or series. To apply the same style everywhere instead, put the skill in `~/.claude/skills/house-style/`.
|
|
79
64
|
|
|
80
|
-
|
|
65
|
+
Updating the tools never touches this file.
|
|
81
66
|
|
|
82
|
-
##
|
|
67
|
+
## Updating
|
|
83
68
|
|
|
84
|
-
|
|
85
|
-
|---|---|
|
|
86
|
-
| **Comps** (7) | `list_comps`, `get_comp`, `get_comp_tree`, `create_comp`, `set_comp`, `delete_comp`, `set_active_comp` |
|
|
87
|
-
| **Layers** (15) | `list_layers`, `get_layer_full` ⭐, `create_{text,shape,solid,null,adjustment,precomp,camera,light}_layer`, `duplicate_layer`, `delete_layer`, `set_layer`, `parent_layer`, `reorder_layer` |
|
|
88
|
-
| **Transforms** (1) | `set_transform` — position/scale/rotation/anchor/opacity, 2D and 3D, optionally keyframed |
|
|
89
|
-
| **Keyframes** (6) | `add_keyframe`, `remove_keyframe`, `get_keyframes`, `set_interpolation`, `set_temporal_ease`, `set_spatial_tangents` |
|
|
90
|
-
| **Expressions** (4) | `get_expression`, `set_expression`, `toggle_expression`, `clear_expression` |
|
|
91
|
-
| **Effects** (6) | `list_effects`, `add_effect`, `remove_effect`, `set_effect_param`, `set_effect_enabled`, `list_available_effects` |
|
|
92
|
-
| **Text** (2) | `set_text`, `add_text_animator` |
|
|
93
|
-
| **Shapes** (3) | `set_shape_path`, `add_shape_content`, `set_shape_property` |
|
|
94
|
-
| **Masks** (3) | `add_mask`, `set_mask`, `remove_mask` |
|
|
95
|
-
| **Markers** (2) | `add_marker`, `remove_marker` |
|
|
96
|
-
| **Vision** (2) | `screenshot_frame`, `screenshot_layer` |
|
|
97
|
-
| **Batch** (1) | `run_batch` — many ops, one undo step, progress streaming |
|
|
98
|
-
| **Explore** (2) | `get_project_summary`, `find_layers` |
|
|
99
|
-
| **Raw** (1) | `run_jsx` — arbitrary ExtendScript escape hatch |
|
|
100
|
-
| **Jobs** (3) | `await_job`, `get_job`, `cancel_job` |
|
|
101
|
-
| **Setup** (2) | `check_setup`, `setup_panel` |
|
|
102
|
-
|
|
103
|
-
⭐ **`get_layer_full` is the centrepiece.** One call returns a layer's transforms *with* keyframes and expressions, every effect with every parameter, masks, markers and visible bounds. Reading before writing is what separates an agent that builds what you asked for from one that guesses.
|
|
104
|
-
|
|
105
|
-
Three design decisions worth knowing about:
|
|
106
|
-
|
|
107
|
-
- **Screenshots are diagnostics, not a feedback loop.** The tool descriptions tell the agent to take two or three, never to scrub frame by frame. Pass `downsample` on large comps — a full 4K frame is big enough to exhaust an agent's context in one call.
|
|
108
|
-
- **Bulk work is one undo step.** `run_batch` runs hundreds of operations in a single ExtendScript pass, so "undo that" does what you mean. Long batches stream progress.
|
|
109
|
-
- **Failures are loud.** Tools that can't do what was asked return an error naming the problem rather than reporting success — an agent can only correct a mistake it's told about.
|
|
110
|
-
|
|
111
|
-
## How it works
|
|
69
|
+
Two things to update: the tools, and the panel inside After Effects. The panel does not update on its own.
|
|
112
70
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
│ HTTP + WebSocket on 127.0.0.1
|
|
119
|
-
▼
|
|
120
|
-
CEP panel inside After Effects
|
|
121
|
-
│ evalScript
|
|
122
|
-
▼
|
|
123
|
-
ExtendScript → After Effects
|
|
124
|
-
```
|
|
71
|
+
Restart your AI client — it picks up the current version of the tools. Then, with After Effects open, ask:
|
|
72
|
+
|
|
73
|
+
> Update the After Effects panel.
|
|
74
|
+
|
|
75
|
+
It reinstalls the panel and tells you to restart After Effects. Ask it to check the setup afterwards, which reports whether the panel matches the current version.
|
|
125
76
|
|
|
126
|
-
|
|
77
|
+
## Tools
|
|
78
|
+
|
|
79
|
+
| Group | Tools |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Comps (7) | `list_comps`, `get_comp`, `get_comp_tree`, `create_comp`, `set_comp`, `delete_comp`, `set_active_comp` |
|
|
82
|
+
| Layers (15) | `list_layers`, `get_layer_full`, `create_{text,shape,solid,null,adjustment,precomp,camera,light}_layer`, `duplicate_layer`, `delete_layer`, `set_layer`, `parent_layer`, `reorder_layer` |
|
|
83
|
+
| Transforms (1) | `set_transform` — position, scale, rotation, anchor, opacity; 2D and 3D; optionally keyframed |
|
|
84
|
+
| Keyframes (6) | `add_keyframe`, `remove_keyframe`, `get_keyframes`, `set_interpolation`, `set_temporal_ease`, `set_spatial_tangents` |
|
|
85
|
+
| Expressions (4) | `get_expression`, `set_expression`, `toggle_expression`, `clear_expression` |
|
|
86
|
+
| Effects (6) | `list_effects`, `add_effect`, `remove_effect`, `set_effect_param`, `set_effect_enabled`, `list_available_effects` |
|
|
87
|
+
| Text (2) | `set_text`, `add_text_animator` |
|
|
88
|
+
| Shapes (3) | `set_shape_path`, `add_shape_content`, `set_shape_property` |
|
|
89
|
+
| Masks (3) | `add_mask`, `set_mask`, `remove_mask` |
|
|
90
|
+
| Markers (2) | `add_marker`, `remove_marker` |
|
|
91
|
+
| Vision (2) | `screenshot_frame`, `screenshot_layer` |
|
|
92
|
+
| Batch (1) | `run_batch` |
|
|
93
|
+
| Explore (2) | `get_project_summary`, `find_layers` |
|
|
94
|
+
| Raw (1) | `run_jsx` |
|
|
95
|
+
| Jobs (3) | `await_job`, `get_job`, `cancel_job` |
|
|
96
|
+
| Setup (2) | `check_setup`, `setup_panel` |
|
|
97
|
+
|
|
98
|
+
A few notes on the ones that behave differently from the rest:
|
|
99
|
+
|
|
100
|
+
- `get_layer_full` returns a layer's transforms with their keyframes and expressions, every effect with every parameter, masks, markers and visible bounds — in one call.
|
|
101
|
+
- `run_batch` runs many operations in a single pass and counts as one undo step. Long batches stream progress.
|
|
102
|
+
- `screenshot_frame` and `screenshot_layer` are for occasional checks, not for reviewing motion frame by frame. On large comps, `downsample: 2` renders at half resolution, which is faster and keeps the image small.
|
|
103
|
+
- `run_jsx` runs arbitrary ExtendScript for anything the other tools do not cover.
|
|
127
104
|
|
|
128
105
|
## Troubleshooting
|
|
129
106
|
|
|
130
|
-
Ask
|
|
107
|
+
Ask the AI to check the After Effects setup. It reports which part is broken and what to do about it.
|
|
131
108
|
|
|
132
|
-
| Symptom | Cause |
|
|
109
|
+
| Symptom | Cause and fix |
|
|
133
110
|
|---|---|
|
|
134
|
-
| "Cannot reach the After Effects panel" | AE
|
|
135
|
-
| Tools worked before, now
|
|
136
|
-
| Panel never loads, setup looks correct |
|
|
137
|
-
|
|
|
111
|
+
| "Cannot reach the After Effects panel" | AE is not running, or the panel is not installed. Ask the AI to set up After Effects, then restart AE. |
|
|
112
|
+
| Tools worked before, now fail inside AE | The tools were updated but the panel was not. Ask the AI to update the panel, then restart AE. |
|
|
113
|
+
| Panel never loads, setup looks correct | On macOS, reboot once. Some builds cache the Adobe setting until a restart. |
|
|
114
|
+
| A panel answers but the setup check reports none installed | An older install is still serving. Remove it from the CEP extensions folder, reinstall the panel, restart AE. |
|
|
115
|
+
| Need the panel's own log | In AE: Window → Extensions → AE MCP Bridge. |
|
|
116
|
+
|
|
117
|
+
## Platforms
|
|
118
|
+
|
|
119
|
+
macOS and Windows. Two things differ, and are handled for you:
|
|
120
|
+
|
|
121
|
+
| | macOS | Windows |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| Panel location | `~/Library/Application Support/Adobe/CEP/extensions` | `%APPDATA%\Adobe\CEP\extensions` |
|
|
124
|
+
| Unsigned-panel setting | `defaults` preference | `HKCU\Software\Adobe\CSXS.*` registry value |
|
|
125
|
+
|
|
126
|
+
macOS is the more exercised of the two; [issue reports](https://github.com/Engine-Room-Games/after-effects-mcp/issues) are welcome.
|
|
138
127
|
|
|
139
128
|
## Limitations
|
|
140
129
|
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
- **Not covered:** render queue, footage import and replacement, AE preferences. Preferences are excluded by design — this tool animates, it doesn't reconfigure your app.
|
|
130
|
+
- The panel is unsigned, so loading it requires Adobe's `PlayerDebugMode`, which the setup step enables. This is Adobe's documented path for unsigned extensions.
|
|
131
|
+
- `saveFrameToPng` is community-known rather than officially documented. It works, but alpha edges can be imperfect on some comps.
|
|
132
|
+
- A long synchronous loop in `run_jsx` will freeze the After Effects UI. Use `run_batch` for bulk work.
|
|
133
|
+
- Not covered: the render queue, footage import and replacement, and application preferences.
|
|
146
134
|
|
|
147
135
|
## Development
|
|
148
136
|
|
|
@@ -150,26 +138,23 @@ Ask Claude to run `check_setup` first — it reports exactly which link in the c
|
|
|
150
138
|
git clone https://github.com/Engine-Room-Games/after-effects-mcp.git
|
|
151
139
|
cd after-effects-mcp
|
|
152
140
|
npm install && npm run build
|
|
153
|
-
npm run install:panel
|
|
154
|
-
npm run doctor
|
|
141
|
+
npm run install:panel
|
|
142
|
+
npm run doctor
|
|
155
143
|
```
|
|
156
144
|
|
|
157
|
-
| Command |
|
|
145
|
+
| Command | Purpose |
|
|
158
146
|
|---|---|
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
147
|
+
| `make build` | Compile TypeScript and the ExtendScript bundle |
|
|
148
|
+
| `make jsx` | Rebuild `bundle.jsx` and hot-reload it into a running After Effects |
|
|
149
|
+
| `make watch` | TypeScript watch mode |
|
|
150
|
+
| `make doctor` | Diagnose the install |
|
|
151
|
+
| `make verify` | Build, check version strings agree, dry-run the package |
|
|
152
|
+
| `make release` | Bump the patch version, tag, and publish |
|
|
153
|
+
| `make release 1.1.0` | Set an explicit version, tag, and publish |
|
|
165
154
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
curl -X POST http://127.0.0.1:7777/reload-jsx
|
|
170
|
-
```
|
|
155
|
+
Adding a tool takes three edits: a zod schema in `packages/shared/src/schemas.ts`, a handler in the matching `packages/jsx/*.jsx` module, and a description in `packages/mcp-server/src/tools/descriptions.ts`. Registration is automatic.
|
|
171
156
|
|
|
172
|
-
|
|
157
|
+
See [CLAUDE.md](CLAUDE.md) for the architecture, the ExtendScript conventions, and the known-fragile areas.
|
|
173
158
|
|
|
174
159
|
## License
|
|
175
160
|
|
package/bin/server.js
CHANGED
|
@@ -67,14 +67,14 @@ description: The visual and motion style for this project \u2014 palette, type,
|
|
|
67
67
|
`;
|
|
68
68
|
var CLAUDE_MD = `# {{NAME}}
|
|
69
69
|
|
|
70
|
-
After Effects project folder.
|
|
70
|
+
After Effects project folder. The tools drive After Effects directly from here.
|
|
71
71
|
|
|
72
72
|
## How to work in this folder
|
|
73
73
|
|
|
74
74
|
1. Open After Effects with the project you want to work on.
|
|
75
75
|
2. Describe what you want in plain language \u2014 "build a lower third that says
|
|
76
76
|
Chapter One and slides in from the left".
|
|
77
|
-
3.
|
|
77
|
+
3. The current state of the comp is read, the change is made, and you see it.
|
|
78
78
|
|
|
79
79
|
## Style
|
|
80
80
|
|
|
@@ -99,10 +99,11 @@ var MCP_JSON = `{
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
`;
|
|
102
|
+
var KNOWN_FLAGS = ["--no-mcp", "--with-mcp"];
|
|
102
103
|
function parseInitArgs(argv) {
|
|
103
104
|
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
104
|
-
const withMcp = argv.includes("--
|
|
105
|
-
const unknown = argv.filter((a) => a.startsWith("-") && a
|
|
105
|
+
const withMcp = !argv.includes("--no-mcp");
|
|
106
|
+
const unknown = argv.filter((a) => a.startsWith("-") && !KNOWN_FLAGS.includes(a));
|
|
106
107
|
if (unknown.length > 0) return { error: `Unknown option: ${unknown[0]}` };
|
|
107
108
|
if (positional.length === 0) return { error: "Missing target directory." };
|
|
108
109
|
if (positional.length > 1) return { error: `Expected one directory, got ${positional.length}.` };
|
|
@@ -113,7 +114,7 @@ function runInit(argv) {
|
|
|
113
114
|
if ("error" in parsed) {
|
|
114
115
|
process.stderr.write(`${parsed.error}
|
|
115
116
|
|
|
116
|
-
Usage: npx @engine-room/after-effects-mcp init <directory> [--
|
|
117
|
+
Usage: npx @engine-room/after-effects-mcp init <directory> [--no-mcp]
|
|
117
118
|
`);
|
|
118
119
|
return 1;
|
|
119
120
|
}
|
|
@@ -147,17 +148,16 @@ Delete them first, or choose a different directory.
|
|
|
147
148
|
` CLAUDE.md what this project is`,
|
|
148
149
|
` .claude/skills/house-style/SKILL.md your look \u2014 edit this first`,
|
|
149
150
|
` renders/ exports land here`,
|
|
150
|
-
...parsed.withMcp ? [` .mcp.json connects
|
|
151
|
+
...parsed.withMcp ? [` .mcp.json connects your client to After Effects`] : [],
|
|
151
152
|
``,
|
|
152
153
|
`Next:`,
|
|
153
|
-
` 1. Open the folder in
|
|
154
|
-
` 2.
|
|
155
|
-
` 3.
|
|
154
|
+
` 1. Open the folder in your MCP client: cd ${parsed.dir}`,
|
|
155
|
+
` 2. Open After Effects, then ask it to set up After Effects.`,
|
|
156
|
+
` 3. Fill in .claude/skills/house-style/SKILL.md with your palette, type and timing.`,
|
|
156
157
|
...parsed.withMcp ? [] : [
|
|
157
158
|
``,
|
|
158
|
-
`This folder has no .mcp.json
|
|
159
|
-
`
|
|
160
|
-
`re-run with --with-mcp to add a project-level connection instead.`
|
|
159
|
+
`This folder has no .mcp.json, so your client must already provide the`,
|
|
160
|
+
`After Effects tools some other way.`
|
|
161
161
|
],
|
|
162
162
|
``
|
|
163
163
|
].join("\n");
|
|
@@ -1110,11 +1110,9 @@ var descriptions = {
|
|
|
1110
1110
|
};
|
|
1111
1111
|
|
|
1112
1112
|
// src/setup/check.ts
|
|
1113
|
-
import { execFile } from "node:child_process";
|
|
1114
1113
|
import crypto from "node:crypto";
|
|
1115
1114
|
import fs4 from "node:fs";
|
|
1116
1115
|
import path4 from "node:path";
|
|
1117
|
-
import { promisify } from "node:util";
|
|
1118
1116
|
|
|
1119
1117
|
// src/setup/paths.ts
|
|
1120
1118
|
import fs3 from "node:fs";
|
|
@@ -1123,6 +1121,9 @@ import path3 from "node:path";
|
|
|
1123
1121
|
import { createRequire } from "node:module";
|
|
1124
1122
|
import { fileURLToPath } from "node:url";
|
|
1125
1123
|
var BUNDLE_ID = "games.engine-room.ae-mcp";
|
|
1124
|
+
function isSupportedPlatform() {
|
|
1125
|
+
return process.platform === "darwin" || process.platform === "win32";
|
|
1126
|
+
}
|
|
1126
1127
|
function packageRoot() {
|
|
1127
1128
|
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
1128
1129
|
for (let i = 0; i < 8; i++) {
|
|
@@ -1147,6 +1148,10 @@ function panelSourceDir() {
|
|
|
1147
1148
|
return null;
|
|
1148
1149
|
}
|
|
1149
1150
|
function cepExtensionsDir() {
|
|
1151
|
+
if (process.platform === "win32") {
|
|
1152
|
+
const appData = process.env.APPDATA ?? path3.join(os2.homedir(), "AppData", "Roaming");
|
|
1153
|
+
return path3.join(appData, "Adobe", "CEP", "extensions");
|
|
1154
|
+
}
|
|
1150
1155
|
return path3.join(os2.homedir(), "Library", "Application Support", "Adobe", "CEP", "extensions");
|
|
1151
1156
|
}
|
|
1152
1157
|
function installedPanelDir() {
|
|
@@ -1178,27 +1183,84 @@ function copyRecursive(src, dst) {
|
|
|
1178
1183
|
}
|
|
1179
1184
|
}
|
|
1180
1185
|
|
|
1181
|
-
// src/setup/
|
|
1186
|
+
// src/setup/platform.ts
|
|
1187
|
+
import { execFile } from "node:child_process";
|
|
1188
|
+
import { promisify } from "node:util";
|
|
1182
1189
|
var exec = promisify(execFile);
|
|
1183
1190
|
var CSXS_VERSIONS = [12, 11, 10, 9];
|
|
1184
|
-
async function
|
|
1191
|
+
async function isDebugModeOn() {
|
|
1185
1192
|
for (const v of CSXS_VERSIONS) {
|
|
1186
1193
|
try {
|
|
1187
|
-
|
|
1188
|
-
|
|
1194
|
+
if (process.platform === "win32") {
|
|
1195
|
+
const { stdout } = await exec("reg", [
|
|
1196
|
+
"query",
|
|
1197
|
+
`HKCU\\Software\\Adobe\\CSXS.${v}`,
|
|
1198
|
+
"/v",
|
|
1199
|
+
"PlayerDebugMode"
|
|
1200
|
+
]);
|
|
1201
|
+
if (/PlayerDebugMode\s+REG_SZ\s+1\b/.test(stdout)) {
|
|
1202
|
+
return { on: true, detail: `enabled (CSXS.${v})` };
|
|
1203
|
+
}
|
|
1204
|
+
} else {
|
|
1205
|
+
const { stdout } = await exec("defaults", [
|
|
1206
|
+
"read",
|
|
1207
|
+
`com.adobe.CSXS.${v}`,
|
|
1208
|
+
"PlayerDebugMode"
|
|
1209
|
+
]);
|
|
1210
|
+
if (stdout.trim() === "1") return { on: true, detail: `enabled (CSXS.${v})` };
|
|
1211
|
+
}
|
|
1189
1212
|
} catch {
|
|
1190
1213
|
}
|
|
1191
1214
|
}
|
|
1192
1215
|
return { on: false, detail: "not enabled for any CSXS version" };
|
|
1193
1216
|
}
|
|
1194
|
-
async function
|
|
1217
|
+
async function enableDebugMode() {
|
|
1218
|
+
const enabled = [];
|
|
1219
|
+
for (const v of CSXS_VERSIONS) {
|
|
1220
|
+
try {
|
|
1221
|
+
if (process.platform === "win32") {
|
|
1222
|
+
await exec("reg", [
|
|
1223
|
+
"add",
|
|
1224
|
+
`HKCU\\Software\\Adobe\\CSXS.${v}`,
|
|
1225
|
+
"/v",
|
|
1226
|
+
"PlayerDebugMode",
|
|
1227
|
+
"/t",
|
|
1228
|
+
"REG_SZ",
|
|
1229
|
+
"/d",
|
|
1230
|
+
"1",
|
|
1231
|
+
"/f"
|
|
1232
|
+
]);
|
|
1233
|
+
} else {
|
|
1234
|
+
await exec("defaults", [
|
|
1235
|
+
"write",
|
|
1236
|
+
`com.adobe.CSXS.${v}`,
|
|
1237
|
+
"PlayerDebugMode",
|
|
1238
|
+
"1"
|
|
1239
|
+
]);
|
|
1240
|
+
}
|
|
1241
|
+
enabled.push(v);
|
|
1242
|
+
} catch {
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return enabled;
|
|
1246
|
+
}
|
|
1247
|
+
async function isAfterEffectsRunning() {
|
|
1195
1248
|
try {
|
|
1249
|
+
if (process.platform === "win32") {
|
|
1250
|
+
const { stdout: stdout2 } = await exec("tasklist", ["/FI", "IMAGENAME eq AfterFX.exe", "/NH"]);
|
|
1251
|
+
return /AfterFX\.exe/i.test(stdout2);
|
|
1252
|
+
}
|
|
1196
1253
|
const { stdout } = await exec("pgrep", ["-f", "Adobe After Effects"]);
|
|
1197
1254
|
return stdout.trim().length > 0;
|
|
1198
1255
|
} catch {
|
|
1199
1256
|
return false;
|
|
1200
1257
|
}
|
|
1201
1258
|
}
|
|
1259
|
+
function debugModeLocation() {
|
|
1260
|
+
return process.platform === "win32" ? "the PlayerDebugMode value under HKEY_CURRENT_USER\\Software\\Adobe\\CSXS.*" : "Adobe's PlayerDebugMode preference";
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/setup/check.ts
|
|
1202
1264
|
function sha256(file) {
|
|
1203
1265
|
try {
|
|
1204
1266
|
return crypto.createHash("sha256").update(fs4.readFileSync(file)).digest("hex");
|
|
@@ -1217,12 +1279,12 @@ async function bridgeReachable(port) {
|
|
|
1217
1279
|
}
|
|
1218
1280
|
async function checkSetup() {
|
|
1219
1281
|
const checks = [];
|
|
1220
|
-
const
|
|
1282
|
+
const supported = isSupportedPlatform();
|
|
1221
1283
|
checks.push({
|
|
1222
1284
|
name: "platform",
|
|
1223
|
-
ok:
|
|
1285
|
+
ok: supported,
|
|
1224
1286
|
detail: process.platform,
|
|
1225
|
-
fix:
|
|
1287
|
+
fix: supported ? void 0 : "After Effects runs only on macOS and Windows, so this server supports only those two."
|
|
1226
1288
|
});
|
|
1227
1289
|
const source = panelSourceDir();
|
|
1228
1290
|
checks.push({
|
|
@@ -1231,12 +1293,12 @@ async function checkSetup() {
|
|
|
1231
1293
|
detail: source ?? "not found in this installation",
|
|
1232
1294
|
fix: source ? void 0 : "The package is missing its CEP panel assets \u2014 reinstall the server."
|
|
1233
1295
|
});
|
|
1234
|
-
const debugMode = await
|
|
1296
|
+
const debugMode = await isDebugModeOn();
|
|
1235
1297
|
checks.push({
|
|
1236
1298
|
name: "cepDebugMode",
|
|
1237
1299
|
ok: debugMode.on,
|
|
1238
1300
|
detail: debugMode.detail,
|
|
1239
|
-
fix: debugMode.on ? void 0 :
|
|
1301
|
+
fix: debugMode.on ? void 0 : `Run the setup_panel tool. After Effects only loads unsigned panels when ${debugModeLocation()} is set.`
|
|
1240
1302
|
});
|
|
1241
1303
|
const installed = installedPanelDir();
|
|
1242
1304
|
const isInstalled = fs4.existsSync(path4.join(installed, "CSXS", "manifest.xml"));
|
|
@@ -1257,7 +1319,7 @@ async function checkSetup() {
|
|
|
1257
1319
|
fix: upToDate ? void 0 : "Run setup_panel to refresh it, then restart After Effects."
|
|
1258
1320
|
});
|
|
1259
1321
|
}
|
|
1260
|
-
const running = await
|
|
1322
|
+
const running = await isAfterEffectsRunning();
|
|
1261
1323
|
checks.push({
|
|
1262
1324
|
name: "afterEffectsRunning",
|
|
1263
1325
|
ok: running,
|
|
@@ -1272,6 +1334,14 @@ async function checkSetup() {
|
|
|
1272
1334
|
detail: bridge.detail,
|
|
1273
1335
|
fix: bridge.ok ? void 0 : "If the other checks pass, restart After Effects so the panel reloads."
|
|
1274
1336
|
});
|
|
1337
|
+
if (bridge.ok && !isInstalled) {
|
|
1338
|
+
checks.push({
|
|
1339
|
+
name: "panelIdentity",
|
|
1340
|
+
ok: false,
|
|
1341
|
+
detail: `a panel is answering on port ${port}, but not the one at ${installed}`,
|
|
1342
|
+
fix: `An older install is serving the bridge. Look in ${cepExtensionsDir()} for a differently named folder, remove it, then run setup_panel and restart After Effects.`
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1275
1345
|
const ready = checks.every((c) => c.ok);
|
|
1276
1346
|
return { ready, checks, nextSteps: buildNextSteps(checks, ready) };
|
|
1277
1347
|
}
|
|
@@ -1287,7 +1357,13 @@ function buildNextSteps(checks, ready) {
|
|
|
1287
1357
|
steps.push("Run the setup_panel tool \u2014 it installs the After Effects panel and enables the Adobe preference that lets AE load it.");
|
|
1288
1358
|
}
|
|
1289
1359
|
if (needsDebug) {
|
|
1290
|
-
steps.push(
|
|
1360
|
+
steps.push(
|
|
1361
|
+
process.platform === "win32" ? "Quit and reopen After Effects so it re-reads the registry." : "Quit and reopen After Effects. If the panel still does not connect, restart the Mac once \u2014 the Adobe preference sometimes only takes effect after a reboot."
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
const identity = by("panelIdentity");
|
|
1365
|
+
if (identity && identity.ok === false) {
|
|
1366
|
+
steps.push(identity.fix);
|
|
1291
1367
|
}
|
|
1292
1368
|
if (by("afterEffectsRunning")?.ok === false) {
|
|
1293
1369
|
steps.push("Open After Effects 2026.");
|
|
@@ -1302,38 +1378,15 @@ function buildNextSteps(checks, ready) {
|
|
|
1302
1378
|
}
|
|
1303
1379
|
|
|
1304
1380
|
// src/setup/install.ts
|
|
1305
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
1306
1381
|
import fs5 from "node:fs";
|
|
1307
1382
|
import path5 from "node:path";
|
|
1308
|
-
import { promisify as promisify2 } from "node:util";
|
|
1309
|
-
var exec2 = promisify2(execFile2);
|
|
1310
|
-
var CSXS_VERSIONS2 = [12, 11, 10, 9];
|
|
1311
|
-
async function isDebugModeOn() {
|
|
1312
|
-
for (const v of CSXS_VERSIONS2) {
|
|
1313
|
-
try {
|
|
1314
|
-
const { stdout } = await exec2("defaults", ["read", `com.adobe.CSXS.${v}`, "PlayerDebugMode"]);
|
|
1315
|
-
if (stdout.trim() === "1") return true;
|
|
1316
|
-
} catch {
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
return false;
|
|
1320
|
-
}
|
|
1321
|
-
async function enableDebugMode() {
|
|
1322
|
-
const enabled = [];
|
|
1323
|
-
for (const v of CSXS_VERSIONS2) {
|
|
1324
|
-
try {
|
|
1325
|
-
await exec2("defaults", ["write", `com.adobe.CSXS.${v}`, "PlayerDebugMode", "1"]);
|
|
1326
|
-
enabled.push(v);
|
|
1327
|
-
} catch {
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
return enabled;
|
|
1331
|
-
}
|
|
1332
1383
|
async function installPanel(opts = {}) {
|
|
1333
1384
|
const actions = [];
|
|
1334
1385
|
const notes = [];
|
|
1335
|
-
if (
|
|
1336
|
-
throw new Error(
|
|
1386
|
+
if (!isSupportedPlatform()) {
|
|
1387
|
+
throw new Error(
|
|
1388
|
+
`After Effects runs only on macOS and Windows, so setup_panel cannot install anything on ${process.platform}.`
|
|
1389
|
+
);
|
|
1337
1390
|
}
|
|
1338
1391
|
const source = panelSourceDir();
|
|
1339
1392
|
if (!source) {
|
|
@@ -1377,16 +1430,16 @@ async function installPanel(opts = {}) {
|
|
|
1377
1430
|
}
|
|
1378
1431
|
let rebootRecommended = false;
|
|
1379
1432
|
if (opts.enableDebugMode !== false) {
|
|
1380
|
-
const
|
|
1381
|
-
if (
|
|
1382
|
-
actions.push(
|
|
1433
|
+
const existing2 = await isDebugModeOn();
|
|
1434
|
+
if (existing2.on) {
|
|
1435
|
+
actions.push(`PlayerDebugMode was already enabled \u2014 ${existing2.detail}.`);
|
|
1383
1436
|
} else {
|
|
1384
1437
|
const versions = await enableDebugMode();
|
|
1385
1438
|
if (versions.length === 0) {
|
|
1386
|
-
notes.push(
|
|
1439
|
+
notes.push(`Failed to set ${debugModeLocation()}. Without it After Effects will refuse to load this panel.`);
|
|
1387
1440
|
} else {
|
|
1388
1441
|
actions.push(`Enabled PlayerDebugMode for CSXS ${versions.join(", ")} so AE will load the unsigned panel.`);
|
|
1389
|
-
rebootRecommended =
|
|
1442
|
+
rebootRecommended = process.platform === "darwin";
|
|
1390
1443
|
}
|
|
1391
1444
|
}
|
|
1392
1445
|
}
|
|
@@ -1423,7 +1476,7 @@ var GetJobSchema = schemas_exports.GetJob;
|
|
|
1423
1476
|
var CancelJobSchema = schemas_exports.CancelJob;
|
|
1424
1477
|
function createServer() {
|
|
1425
1478
|
const server = new Server(
|
|
1426
|
-
{ name: "after-effects-mcp", version: "0.1.
|
|
1479
|
+
{ name: "after-effects-mcp", version: "0.1.1" },
|
|
1427
1480
|
{ capabilities: { tools: {}, logging: {} } }
|
|
1428
1481
|
);
|
|
1429
1482
|
const bridge = new HttpClient();
|
|
@@ -1578,11 +1631,12 @@ client (Claude Code, Claude Desktop, \u2026) starts it. You do not normally run
|
|
|
1578
1631
|
by hand.
|
|
1579
1632
|
|
|
1580
1633
|
Commands:
|
|
1581
|
-
init <directory> [--
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
--
|
|
1634
|
+
init <directory> [--no-mcp] Create a project folder containing the client
|
|
1635
|
+
configuration and a house-style skill you can
|
|
1636
|
+
edit. Pass --no-mcp if your client already
|
|
1637
|
+
provides these tools.
|
|
1638
|
+
--help Show this message.
|
|
1639
|
+
--version Print the version.
|
|
1586
1640
|
`;
|
|
1587
1641
|
async function main() {
|
|
1588
1642
|
const argv = process.argv.slice(2);
|
|
@@ -1609,7 +1663,7 @@ ${USAGE}`);
|
|
|
1609
1663
|
await server.connect(transport);
|
|
1610
1664
|
logger.info("MCP server running on stdio");
|
|
1611
1665
|
}
|
|
1612
|
-
var VERSION = "0.1.
|
|
1666
|
+
var VERSION = "0.1.1";
|
|
1613
1667
|
main().catch((e) => {
|
|
1614
1668
|
logger.error("fatal", e.message);
|
|
1615
1669
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@engine-room/after-effects-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Control Adobe After Effects with AI — describe the animation you want and it gets built: layers, keyframes, effects, expressions and text, all editable afterwards.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Engine Room",
|
|
8
8
|
"homepage": "https://github.com/Engine-Room-Games/after-effects-mcp#readme",
|
|
@@ -15,14 +15,16 @@
|
|
|
15
15
|
"url": "https://github.com/Engine-Room-Games/after-effects-mcp/issues"
|
|
16
16
|
},
|
|
17
17
|
"keywords": [
|
|
18
|
-
"mcp",
|
|
19
|
-
"model-context-protocol",
|
|
20
18
|
"after-effects",
|
|
21
19
|
"adobe",
|
|
20
|
+
"ai",
|
|
22
21
|
"animation",
|
|
23
22
|
"motion-graphics",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
23
|
+
"video",
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"claude",
|
|
27
|
+
"extendscript"
|
|
26
28
|
],
|
|
27
29
|
"bin": {
|
|
28
30
|
"after-effects-mcp": "bin/server.js"
|
|
@@ -37,7 +39,8 @@
|
|
|
37
39
|
"LICENSE"
|
|
38
40
|
],
|
|
39
41
|
"os": [
|
|
40
|
-
"darwin"
|
|
42
|
+
"darwin",
|
|
43
|
+
"win32"
|
|
41
44
|
],
|
|
42
45
|
"engines": {
|
|
43
46
|
"node": ">=20"
|
package/panel/CSXS/manifest.xml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<ExtensionManifest Version="11.0" ExtensionBundleId="games.engine-room.ae-mcp" ExtensionBundleVersion="0.1.
|
|
2
|
+
<ExtensionManifest Version="11.0" ExtensionBundleId="games.engine-room.ae-mcp" ExtensionBundleVersion="0.1.1"
|
|
3
3
|
ExtensionBundleName="AE MCP Bridge" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
4
4
|
<ExtensionList>
|
|
5
|
-
<Extension Id="games.engine-room.ae-mcp.panel" Version="0.1.
|
|
5
|
+
<Extension Id="games.engine-room.ae-mcp.panel" Version="0.1.1" />
|
|
6
6
|
</ExtensionList>
|
|
7
7
|
<ExecutionEnvironment>
|
|
8
8
|
<HostList>
|
package/panel/client/main.js
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
var fs = require("fs");
|
|
11
11
|
var os = require("os");
|
|
12
12
|
var http = require("http");
|
|
13
|
-
var execFileSync = require("child_process").execFileSync;
|
|
14
13
|
var WebSocket;
|
|
15
14
|
try { WebSocket = require("ws"); }
|
|
16
15
|
catch (e) {
|
|
@@ -145,45 +144,30 @@
|
|
|
145
144
|
})();
|
|
146
145
|
});
|
|
147
146
|
}
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
var outH = Math.max(1, Math.round(height / factor));
|
|
155
|
-
try {
|
|
156
|
-
// sips takes height then width.
|
|
157
|
-
execFileSync("sips", ["-z", String(outH), String(outW), file], { stdio: "ignore" });
|
|
158
|
-
return { width: outW, height: outH, downsample: factor };
|
|
159
|
-
} catch (e) {
|
|
160
|
-
log("warn", "downsample " + factor + "x failed, returning full resolution: " + e.message);
|
|
161
|
-
return {
|
|
162
|
-
width: width,
|
|
163
|
-
height: height,
|
|
164
|
-
downsample: 1,
|
|
165
|
-
warning: "downsample=" + factor + " was requested but `sips` failed (" + e.message +
|
|
166
|
-
"); returning the full-resolution frame instead.",
|
|
167
|
-
};
|
|
168
|
-
}
|
|
147
|
+
// Read the true pixel dimensions out of the PNG's IHDR chunk rather than
|
|
148
|
+
// computing them, so what we report is always what the client received.
|
|
149
|
+
function pngDimensions(buf) {
|
|
150
|
+
if (buf.length < 24) return null;
|
|
151
|
+
if (buf.readUInt32BE(12) !== 0x49484452) return null; // "IHDR"
|
|
152
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
169
153
|
}
|
|
170
154
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
155
|
+
// Downsampling happens in ExtendScript via the comp's resolutionFactor, so
|
|
156
|
+
// the file on disk is already the right size by the time we get here.
|
|
157
|
+
function readPngAsBase64(file) {
|
|
158
|
+
// A cold render of a heavy 4K comp can take well over 15s — measured on a
|
|
159
|
+
// real project. Five seconds silently failed screenshots that were simply
|
|
160
|
+
// still rendering.
|
|
161
|
+
return waitForPngFile(file, 120000).then(function () {
|
|
177
162
|
var buf = fs.readFileSync(file);
|
|
178
163
|
var b64 = buf.toString("base64");
|
|
179
164
|
try { fs.unlinkSync(file); } catch (e) {}
|
|
165
|
+
var dims = pngDimensions(buf);
|
|
180
166
|
return {
|
|
181
167
|
base64: b64,
|
|
182
168
|
bytes: buf.length,
|
|
183
|
-
width: dims.width,
|
|
184
|
-
height: dims.height,
|
|
185
|
-
downsample: dims.downsample,
|
|
186
|
-
warning: dims.warning,
|
|
169
|
+
width: dims ? dims.width : null,
|
|
170
|
+
height: dims ? dims.height : null,
|
|
187
171
|
};
|
|
188
172
|
});
|
|
189
173
|
}
|
|
@@ -214,15 +198,16 @@
|
|
|
214
198
|
function handleOp(op, args, progressToken) {
|
|
215
199
|
// Vision ops: run JSX, then read PNG and base64-encode on Node side.
|
|
216
200
|
if (op === "screenshot_frame" || op === "screenshot_layer") {
|
|
217
|
-
var factor = (args && args.downsample) ? Math.round(args.downsample) : 1;
|
|
218
201
|
return runOp(op, args).then(function (info) {
|
|
219
|
-
return readPngAsBase64(info.path
|
|
220
|
-
|
|
202
|
+
return readPngAsBase64(info.path).then(function (img) {
|
|
203
|
+
return {
|
|
204
|
+
// Dimensions come from the PNG itself, not from arithmetic on the
|
|
205
|
+
// comp size, so they cannot disagree with the image sent.
|
|
221
206
|
width: img.width,
|
|
222
207
|
height: img.height,
|
|
223
208
|
fullWidth: info.width,
|
|
224
209
|
fullHeight: info.height,
|
|
225
|
-
downsample:
|
|
210
|
+
downsample: info.downsample,
|
|
226
211
|
time: info.time,
|
|
227
212
|
compId: info.compId,
|
|
228
213
|
layerId: info.layerId,
|
|
@@ -230,8 +215,6 @@
|
|
|
230
215
|
base64: img.base64,
|
|
231
216
|
bytes: img.bytes,
|
|
232
217
|
};
|
|
233
|
-
if (img.warning) out.warning = img.warning;
|
|
234
|
-
return out;
|
|
235
218
|
});
|
|
236
219
|
});
|
|
237
220
|
}
|
package/panel/jsx/bundle.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated bundle. Do not edit directly — edit files in packages/jsx/.
|
|
2
|
-
// Generated 2026-08-
|
|
2
|
+
// Generated 2026-08-10T15:26:38.133Z
|
|
3
3
|
|
|
4
4
|
// ===== core.jsx =====
|
|
5
5
|
|
|
@@ -1146,14 +1146,47 @@ function __tmpPngPath() {
|
|
|
1146
1146
|
return folder.fsName + "/" + name;
|
|
1147
1147
|
}
|
|
1148
1148
|
|
|
1149
|
+
function __clampDownsample(v) {
|
|
1150
|
+
if (v === undefined || v === null) return 1;
|
|
1151
|
+
var n = Math.round(v);
|
|
1152
|
+
if (!(n > 1)) return 1;
|
|
1153
|
+
return n > 8 ? 8 : n;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// saveFrameToPng honours the comp's resolutionFactor, so AE can render the
|
|
1157
|
+
// reduced frame directly instead of writing full size and resampling
|
|
1158
|
+
// afterwards. That is faster (a quarter of the pixels at factor 2) and needs no
|
|
1159
|
+
// external image tool, which is what makes it work off macOS.
|
|
1160
|
+
function __saveFrameAt(comp, time, file, factor) {
|
|
1161
|
+
if (factor <= 1) {
|
|
1162
|
+
comp.saveFrameToPng(time, file);
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
var previous = comp.resolutionFactor;
|
|
1166
|
+
try {
|
|
1167
|
+
comp.resolutionFactor = [factor, factor];
|
|
1168
|
+
comp.saveFrameToPng(time, file);
|
|
1169
|
+
} finally {
|
|
1170
|
+
// Restore unconditionally — a failed render must never leave the user
|
|
1171
|
+
// looking at a half-resolution comp.
|
|
1172
|
+
comp.resolutionFactor = previous;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1149
1176
|
OPS.screenshot_frame = noUndo(function (args) {
|
|
1150
1177
|
var c = getCompById(args.compId);
|
|
1151
1178
|
var t = (args.time !== undefined && args.time !== null) ? args.time : c.time;
|
|
1179
|
+
var ds = __clampDownsample(args.downsample);
|
|
1152
1180
|
var path = __tmpPngPath();
|
|
1153
1181
|
var f = new File(path);
|
|
1154
1182
|
// saveFrameToPng is async-ish; the panel polls the file's existence/size.
|
|
1155
|
-
c
|
|
1156
|
-
return {
|
|
1183
|
+
__saveFrameAt(c, t, f, ds);
|
|
1184
|
+
return {
|
|
1185
|
+
path: path,
|
|
1186
|
+
width: c.width, height: c.height,
|
|
1187
|
+
downsample: ds,
|
|
1188
|
+
time: t, compId: c.id
|
|
1189
|
+
};
|
|
1157
1190
|
});
|
|
1158
1191
|
|
|
1159
1192
|
OPS.screenshot_layer = noUndo(function (args) {
|
|
@@ -1168,10 +1201,11 @@ OPS.screenshot_layer = noUndo(function (args) {
|
|
|
1168
1201
|
ll.solo = false;
|
|
1169
1202
|
}
|
|
1170
1203
|
l.solo = true;
|
|
1204
|
+
var ds = __clampDownsample(args.downsample);
|
|
1171
1205
|
var path = __tmpPngPath();
|
|
1172
1206
|
var f = new File(path);
|
|
1173
1207
|
try {
|
|
1174
|
-
c
|
|
1208
|
+
__saveFrameAt(c, t, f, ds);
|
|
1175
1209
|
} finally {
|
|
1176
1210
|
// restore
|
|
1177
1211
|
l.solo = false;
|
|
@@ -1179,7 +1213,12 @@ OPS.screenshot_layer = noUndo(function (args) {
|
|
|
1179
1213
|
try { c.layer(prevSolo[j].idx).solo = prevSolo[j].solo; } catch (e) {}
|
|
1180
1214
|
}
|
|
1181
1215
|
}
|
|
1182
|
-
return {
|
|
1216
|
+
return {
|
|
1217
|
+
path: path,
|
|
1218
|
+
width: c.width, height: c.height,
|
|
1219
|
+
downsample: ds,
|
|
1220
|
+
time: t, compId: c.id, layerId: l.id
|
|
1221
|
+
};
|
|
1183
1222
|
});
|
|
1184
1223
|
|
|
1185
1224
|
|