brainferno-mcp-bridge 0.2.2 → 0.3.0
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/CHANGELOG.md +28 -0
- package/README.md +26 -18
- package/dist/tools/after-effects.d.ts +45 -0
- package/dist/tools/after-effects.js +322 -2
- package/dist/tools/after-effects.js.map +1 -1
- package/dist/tools/photoshop.js +80 -0
- package/dist/tools/photoshop.js.map +1 -1
- package/dist/tools/premiere.js +19 -0
- package/dist/tools/premiere.js.map +1 -1
- package/package.json +2 -2
- package/panels/panel-uxp/commands.js +220 -0
- package/panels/panel-uxp-ppro/commands.js +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.3.0 — 2026-09-05
|
|
4
|
+
|
|
5
|
+
- After Effects `ae_import_as_comp`: import a layered Photoshop (.psd) or Illustrator (.ai)
|
|
6
|
+
file as a composition — each source layer becomes its own AE layer, keeping blend modes,
|
|
7
|
+
positions, and (for PSD) editable layer styles. `cropped` chooses Composition - Cropped
|
|
8
|
+
Layers. Verified live: a PSD came in as three layers with its drop shadow editable in AE.
|
|
9
|
+
(Illustrator maps top-level layers to AE layers, so put objects on separate layers to keep
|
|
10
|
+
them separate; the file must be RGB and saved PDF-compatible.)
|
|
11
|
+
- After Effects Essential Graphics: `ae_add_to_essential_graphics` exposes a layer property
|
|
12
|
+
(transform channel, a text layer's source text, or any property via propertyPath) as a
|
|
13
|
+
control, and `ae_export_mogrt` exports the composition as a Motion Graphics template
|
|
14
|
+
(.mogrt). Verified live against AE 26.3.
|
|
15
|
+
- Photoshop gradient overlay: `ps_set_layer_style` now takes `style: "gradientOverlay"` with
|
|
16
|
+
`colors` (two or more hex stops), `gradientStyle` (linear/radial/angle/reflected/diamond),
|
|
17
|
+
`angle`, `scale`, `reverse`, `alignWithLayer`, and `dither`.
|
|
18
|
+
- After Effects `ae_set_expression` now rolls the property back to its previous expression
|
|
19
|
+
when the new one fails to compile (it used to leave the broken source assigned).
|
|
20
|
+
- Layer styles for Photoshop and After Effects. Photoshop: `ps_set_layer_style` (add or edit
|
|
21
|
+
a drop shadow, inner shadow, glow, bevel & emboss, satin, color overlay, or stroke — other
|
|
22
|
+
styles on the layer are kept), `ps_get_layer_styles`, `ps_remove_layer_style`. After
|
|
23
|
+
Effects: `ae_add_layer_style`, `ae_set_layer_style_param`, `ae_get_layer_styles`,
|
|
24
|
+
`ae_remove_layer_style` — friendly names map to AE's match names (satin → `chromeFX`,
|
|
25
|
+
colorOverlay → `solidFill`, stroke → `frameFX`).
|
|
26
|
+
- New Premiere Pro tool `pp_insert_mogrt`: drop a Motion Graphics template (.mogrt) on the
|
|
27
|
+
timeline at a time, like dragging it from the Essential Graphics panel. Without
|
|
28
|
+
`videoTrackIndex` it lands on the first track above every video clip playing at that time,
|
|
29
|
+
so the graphic sits over the picture.
|
|
30
|
+
|
|
3
31
|
## v0.2.2 — 2026-08-30
|
|
4
32
|
|
|
5
33
|
- The server reported itself as **0.1.0** to every MCP client and every panel, whatever
|
package/README.md
CHANGED
|
@@ -25,8 +25,9 @@ the way. Every action is one undo step in the app, so you can always step back.
|
|
|
25
25
|
- **Runs on your machine.** Nothing leaves your computer unless you turn on *shared* mode
|
|
26
26
|
for other computers on your network (token-protected).
|
|
27
27
|
|
|
28
|
-
> Status:
|
|
29
|
-
>
|
|
28
|
+
> Status: every tool verified live on Windows and macOS, the two platforms Adobe ships
|
|
29
|
+
> Creative Cloud for (Adobe 2026 apps). The npm badge above is the current version.
|
|
30
|
+
> See [Status](#status).
|
|
30
31
|
|
|
31
32
|
The companion panel inside each app — here in Adobe After Effects and Adobe Photoshop —
|
|
32
33
|
shows the connection, a live log, and a kill switch:
|
|
@@ -77,22 +78,28 @@ Ideas people build with it:
|
|
|
77
78
|
Counts are the tools registered per app. Each family has a live-run write-up in
|
|
78
79
|
`docs/spikes/` with the quirks found on real installs.
|
|
79
80
|
|
|
80
|
-
### Photoshop —
|
|
81
|
+
### Photoshop — 21 tools
|
|
81
82
|
Documents (list, create, open, save, export PNG/JPEG, preview image), layers (create,
|
|
82
|
-
text layers with font/size/color, properties, move, duplicate, delete),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
and
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
text layers with font/size/color, properties, move, duplicate, delete), layer styles
|
|
84
|
+
(drop shadow, glows, bevel, satin, color and gradient overlay, stroke… — add, adjust, read,
|
|
85
|
+
remove), place an image as a smart object, fill, filters (Gaussian/motion/unsharp…),
|
|
86
|
+
resize, crop.
|
|
87
|
+
|
|
88
|
+
### After Effects — 31 tools
|
|
89
|
+
Project info and file, compositions and footage (list, create, import), import a layered
|
|
90
|
+
PSD or AI file as a composition (one layer each, with PSD layer styles editable), layers of
|
|
91
|
+
every kind (footage, solid, text, null, adjustment), keyframes with easing, expressions,
|
|
92
|
+
effects and their parameters, layer styles (drop shadow, glows, bevel, stroke… — add, adjust,
|
|
93
|
+
read, remove), text content/font/color/justification, markers, Essential Graphics (expose a
|
|
94
|
+
control, export a Motion Graphics template), single-frame preview, render queue, and
|
|
95
|
+
headless rendering through `aerender` (the UI stays free).
|
|
96
|
+
|
|
97
|
+
### Premiere Pro — 29 tools
|
|
92
98
|
Project, sequences and items, a full sequence read (every track and clip with times),
|
|
93
|
-
import, create a sequence from media, insert/overwrite,
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
import, create a sequence from media, insert/overwrite, Motion Graphics templates (.mogrt)
|
|
100
|
+
dropped over the picture, ripple remove, move, trim, rename, transitions, effects and
|
|
101
|
+
keyframes (Motion, Opacity, any applied effect), markers, frame preview, export preset
|
|
102
|
+
search, and export (in Premiere or handed to Media Encoder).
|
|
96
103
|
|
|
97
104
|
### Illustrator — 7 tools + Adobe's own 46
|
|
98
105
|
Create documents, draw shapes (rect, ellipse, line, polygon, star) and text, save `.ai`,
|
|
@@ -412,8 +419,9 @@ docs/ build plan, protocol, live-run notes (spikes/), Audition AP
|
|
|
412
419
|
|
|
413
420
|
- **Working, verified live on Windows and macOS (Adobe 2026 apps)**: all six application
|
|
414
421
|
lanes and their v1 tool sets, the ffmpeg lane, the job registry, the four pipelines, remote
|
|
415
|
-
mode, and the installer. Write-ups with the quirks found: `docs/spikes/05`–`
|
|
416
|
-
macOS run: aerender path, Media Encoder's config file and renderer
|
|
422
|
+
mode, and the installer. Write-ups with the quirks found: `docs/spikes/05`–`14` (`13` is the
|
|
423
|
+
macOS run: aerender path, Media Encoder's config file and renderer; `14` is the release
|
|
424
|
+
process and what npm actually does after a publish).
|
|
417
425
|
- **Not yet**: double-click panel installs (`.ccx`/`.zxp`) so the UXP Developer
|
|
418
426
|
Tool is not needed; a single signed installer; TLS for shared mode; Audition multitrack
|
|
419
427
|
writes; Media Encoder queue control.
|
|
@@ -9,6 +9,14 @@ export declare function getLayerScript(compId: number, layerIndex: number): stri
|
|
|
9
9
|
export declare function openProjectScript(path: string): string;
|
|
10
10
|
export declare function saveProjectScript(path: string | undefined): string;
|
|
11
11
|
export declare function importFootageScript(path: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Import a layered PSD or AI as a composition (one AE layer per source layer).
|
|
14
|
+
* ImportAsType.COMP keeps each layer at document size; COMP_CROPPED_LAYERS crops
|
|
15
|
+
* to content. The importer preserves blend modes and positions, and — for PSD —
|
|
16
|
+
* layer styles as After Effects imports them. There is no ImportOptions flag for
|
|
17
|
+
* the dialog's "editable vs merged layer styles"; AE uses its own default.
|
|
18
|
+
*/
|
|
19
|
+
export declare function importAsCompScript(path: string, cropped: boolean): string;
|
|
12
20
|
export interface CreateCompParams {
|
|
13
21
|
name: string;
|
|
14
22
|
width: number;
|
|
@@ -62,6 +70,43 @@ export declare function removeKeyframesScript(compId: number, layerIndex: number
|
|
|
62
70
|
export declare function setExpressionScript(compId: number, layerIndex: number, property: string, propertyPath: string[] | undefined, expression: string | null): string;
|
|
63
71
|
export declare function applyEffectScript(compId: number, layerIndex: number, matchName: string): string;
|
|
64
72
|
export declare function setEffectParamScript(compId: number, layerIndex: number, effect: number | string, param: string, value: number | number[] | string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Layer styles are Photoshop's effects (drop shadow, stroke, …) living under
|
|
75
|
+
* "ADBE Layer Styles". Each maps to an internal key; the group is addressed by
|
|
76
|
+
* its match name "<key>/enabled" and its parameters by "<key>/<param>".
|
|
77
|
+
*/
|
|
78
|
+
export declare const LAYER_STYLE_KEYS: {
|
|
79
|
+
readonly dropShadow: "dropShadow";
|
|
80
|
+
readonly innerShadow: "innerShadow";
|
|
81
|
+
readonly outerGlow: "outerGlow";
|
|
82
|
+
readonly innerGlow: "innerGlow";
|
|
83
|
+
readonly bevelEmboss: "bevelEmboss";
|
|
84
|
+
readonly satin: "chromeFX";
|
|
85
|
+
readonly colorOverlay: "solidFill";
|
|
86
|
+
readonly gradientOverlay: "gradientFill";
|
|
87
|
+
readonly stroke: "frameFX";
|
|
88
|
+
};
|
|
89
|
+
export type LayerStyleName = keyof typeof LAYER_STYLE_KEYS;
|
|
90
|
+
export declare function addLayerStyleScript(compId: number, layerIndex: number, style: LayerStyleName): string;
|
|
91
|
+
export declare function getLayerStylesScript(compId: number, layerIndex: number): string;
|
|
92
|
+
export declare function setLayerStyleParamScript(compId: number, layerIndex: number, style: LayerStyleName, param: string, value: number | number[] | string): string;
|
|
93
|
+
export declare function removeLayerStyleScript(compId: number, layerIndex: number, style: LayerStyleName): string;
|
|
94
|
+
/**
|
|
95
|
+
* Expose a layer property in the comp's Essential Graphics panel so the comp can
|
|
96
|
+
* be exported as a Motion Graphics template. Verified live on AE 26.3:
|
|
97
|
+
* canAddToMotionGraphicsTemplate / addToMotionGraphicsTemplate take the comp the
|
|
98
|
+
* property lives in; the control is named after the property (AE 26.3 has no
|
|
99
|
+
* script to rename it). Source Text lives at ADBE Text Properties > Text Document.
|
|
100
|
+
*/
|
|
101
|
+
export declare function addToEssentialGraphicsScript(compId: number, layerIndex: number, property: string, propertyPath: string[] | undefined): string;
|
|
102
|
+
/**
|
|
103
|
+
* Export the comp as a .mogrt. The host's exportAsMotionGraphicsTemplate takes a
|
|
104
|
+
* *folder* and names the file from motionGraphicsTemplateName, so we split the
|
|
105
|
+
* requested path, set the name, and write <folder>/<name>.mogrt. Verified live on
|
|
106
|
+
* AE 26.3 (the pre-24.3 "always returns false" bug is gone; we still check the
|
|
107
|
+
* file exists). The project must be saved — a dirty project makes AE prompt.
|
|
108
|
+
*/
|
|
109
|
+
export declare function exportMogrtScript(compId: number, folder: string, name: string, overwrite: boolean): string;
|
|
65
110
|
export interface SetTextParams {
|
|
66
111
|
compId: number;
|
|
67
112
|
layerIndex: number;
|
|
@@ -78,6 +78,30 @@ const HELPERS = `
|
|
|
78
78
|
function __compInfo(c) {
|
|
79
79
|
return { id: c.id, name: c.name, width: c.width, height: c.height, duration: c.duration, frameRate: c.frameRate, numLayers: c.numLayers, pixelAspect: c.pixelAspect };
|
|
80
80
|
}
|
|
81
|
+
function __styles(l) {
|
|
82
|
+
var g = l.property("ADBE Layer Styles");
|
|
83
|
+
if (!g) { throw new Error("Layer " + l.index + " has no layer styles group (cameras and lights cannot take styles)."); }
|
|
84
|
+
return g;
|
|
85
|
+
}
|
|
86
|
+
// A style's group is fetched by its match name "<key>/enabled" (the short key
|
|
87
|
+
// resolves for dropShadow only). canSetEnabled is false until the style is
|
|
88
|
+
// actually added, so it doubles as "is this style present".
|
|
89
|
+
function __styleGroup(g, key) {
|
|
90
|
+
var f = null;
|
|
91
|
+
try { f = g.property(key + "/enabled"); } catch (e) {}
|
|
92
|
+
if (f && !f.canSetEnabled) { f = null; }
|
|
93
|
+
return f;
|
|
94
|
+
}
|
|
95
|
+
function __styleParams(s) {
|
|
96
|
+
var out = [];
|
|
97
|
+
for (var i = 1; i <= s.numProperties; i++) {
|
|
98
|
+
var q = s.property(i);
|
|
99
|
+
var v = null;
|
|
100
|
+
try { v = q.value; } catch (e) {}
|
|
101
|
+
out.push({ index: i, name: q.name, matchName: q.matchName, value: v });
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
81
105
|
`;
|
|
82
106
|
const wrap = (body) => `(function () {${HELPERS}${body}
|
|
83
107
|
})()`;
|
|
@@ -152,6 +176,32 @@ export function importFootageScript(path) {
|
|
|
152
176
|
return { id: item.id, name: item.name, width: item.width, height: item.height, duration: item.duration, hasVideo: item.hasVideo, hasAudio: item.hasAudio };
|
|
153
177
|
});`);
|
|
154
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Import a layered PSD or AI as a composition (one AE layer per source layer).
|
|
181
|
+
* ImportAsType.COMP keeps each layer at document size; COMP_CROPPED_LAYERS crops
|
|
182
|
+
* to content. The importer preserves blend modes and positions, and — for PSD —
|
|
183
|
+
* layer styles as After Effects imports them. There is no ImportOptions flag for
|
|
184
|
+
* the dialog's "editable vs merged layer styles"; AE uses its own default.
|
|
185
|
+
*/
|
|
186
|
+
export function importAsCompScript(path, cropped) {
|
|
187
|
+
const want = cropped ? "ImportAsType.COMP_CROPPED_LAYERS" : "ImportAsType.COMP";
|
|
188
|
+
return wrap(`
|
|
189
|
+
var f = new File(${lit(path)});
|
|
190
|
+
if (!f.exists) { throw new Error("No file at " + f.fsName); }
|
|
191
|
+
return __undo("Import as composition", function () {
|
|
192
|
+
var io = new ImportOptions(f);
|
|
193
|
+
if (io.canImportAs(${want})) { io.importAs = ${want}; }
|
|
194
|
+
else if (io.canImportAs(ImportAsType.COMP)) { io.importAs = ImportAsType.COMP; }
|
|
195
|
+
else { throw new Error("After Effects can't import this as a composition. Layered .psd works; an .ai must be RGB (not CMYK) and saved with PDF compatibility."); }
|
|
196
|
+
var item = app.project.importFile(io);
|
|
197
|
+
if (!(item instanceof CompItem)) { throw new Error("Imported '" + item.name + "', but not as a composition — the file may have only one layer."); }
|
|
198
|
+
var info = __compInfo(item);
|
|
199
|
+
var layers = [];
|
|
200
|
+
for (var i = 1; i <= item.numLayers; i++) { layers.push(__layerInfo(item.layer(i))); }
|
|
201
|
+
info.layers = layers;
|
|
202
|
+
return info;
|
|
203
|
+
});`);
|
|
204
|
+
}
|
|
155
205
|
export function createCompScript(p) {
|
|
156
206
|
return wrap(`
|
|
157
207
|
return __undo("Create comp", function () {
|
|
@@ -271,8 +321,15 @@ export function setExpressionScript(compId, layerIndex, property, propertyPath,
|
|
|
271
321
|
return __undo("Set expression", function () {
|
|
272
322
|
var e = ${opt(expression)};
|
|
273
323
|
if (e === null) { prop.expression = ""; return { property: prop.name, expression: null }; }
|
|
324
|
+
// Keep the old expression so a failed compile leaves the property untouched
|
|
325
|
+
// (AE otherwise keeps the broken source assigned, just disabled).
|
|
326
|
+
var prev = prop.expression;
|
|
274
327
|
prop.expression = e;
|
|
275
|
-
if (prop.expressionError) {
|
|
328
|
+
if (prop.expressionError) {
|
|
329
|
+
var err = prop.expressionError;
|
|
330
|
+
prop.expression = prev;
|
|
331
|
+
throw new Error("Expression error: " + err);
|
|
332
|
+
}
|
|
276
333
|
return { property: prop.name, expression: prop.expression, enabled: prop.expressionEnabled };
|
|
277
334
|
});`);
|
|
278
335
|
}
|
|
@@ -299,6 +356,180 @@ export function setEffectParamScript(compId, layerIndex, effect, param, value) {
|
|
|
299
356
|
if (!pp) { throw new Error("Parameter not found: " + ${lit(param)}); }
|
|
300
357
|
return __undo("Set effect parameter", function () { pp.setValue(${v}); return { effect: e.name, param: pp.name, value: pp.value }; });`);
|
|
301
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Layer styles are Photoshop's effects (drop shadow, stroke, …) living under
|
|
361
|
+
* "ADBE Layer Styles". Each maps to an internal key; the group is addressed by
|
|
362
|
+
* its match name "<key>/enabled" and its parameters by "<key>/<param>".
|
|
363
|
+
*/
|
|
364
|
+
export const LAYER_STYLE_KEYS = {
|
|
365
|
+
dropShadow: "dropShadow",
|
|
366
|
+
innerShadow: "innerShadow",
|
|
367
|
+
outerGlow: "outerGlow",
|
|
368
|
+
innerGlow: "innerGlow",
|
|
369
|
+
bevelEmboss: "bevelEmboss",
|
|
370
|
+
satin: "chromeFX",
|
|
371
|
+
colorOverlay: "solidFill",
|
|
372
|
+
gradientOverlay: "gradientFill",
|
|
373
|
+
stroke: "frameFX",
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* Layer styles cannot be added with addProperty (After Effects refuses it). The
|
|
377
|
+
* working way is the Layer > Layer Styles menu command, which needs the comp
|
|
378
|
+
* open in the viewer and the target layer selected. The menu ids are the stable
|
|
379
|
+
* 9000–9008 block (verified live); findMenuCommandId returned a bogus id once
|
|
380
|
+
* after a reconnect, so the fixed id runs first and the label is only a retry.
|
|
381
|
+
*/
|
|
382
|
+
const LAYER_STYLE_MENU = {
|
|
383
|
+
dropShadow: { label: "Drop Shadow", id: 9000 },
|
|
384
|
+
innerShadow: { label: "Inner Shadow", id: 9001 },
|
|
385
|
+
outerGlow: { label: "Outer Glow", id: 9002 },
|
|
386
|
+
innerGlow: { label: "Inner Glow", id: 9003 },
|
|
387
|
+
bevelEmboss: { label: "Bevel and Emboss", id: 9004 },
|
|
388
|
+
satin: { label: "Satin", id: 9005 },
|
|
389
|
+
colorOverlay: { label: "Color Overlay", id: 9006 },
|
|
390
|
+
gradientOverlay: { label: "Gradient Overlay", id: 9007 },
|
|
391
|
+
stroke: { label: "Stroke", id: 9008 },
|
|
392
|
+
};
|
|
393
|
+
export function addLayerStyleScript(compId, layerIndex, style) {
|
|
394
|
+
const key = LAYER_STYLE_KEYS[style];
|
|
395
|
+
const menu = LAYER_STYLE_MENU[style];
|
|
396
|
+
return wrap(`
|
|
397
|
+
var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
|
|
398
|
+
var g = __styles(l);
|
|
399
|
+
var s = __styleGroup(g, ${lit(key)});
|
|
400
|
+
if (s) {
|
|
401
|
+
return __undo("Enable layer style", function () {
|
|
402
|
+
try { s.enabled = true; } catch (e) {}
|
|
403
|
+
return { style: ${lit(style)}, matchName: s.matchName, params: __styleParams(s), alreadyPresent: true };
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
// addProperty does not work for layer styles — the Layer Styles menu command
|
|
407
|
+
// is the only way, and it needs the comp foremost and only this layer selected.
|
|
408
|
+
return __undo("Add layer style", function () {
|
|
409
|
+
c.openInViewer();
|
|
410
|
+
for (var j = 1; j <= c.numLayers; j++) { c.layer(j).selected = false; }
|
|
411
|
+
l.selected = true;
|
|
412
|
+
app.executeCommand(${num(menu.id)});
|
|
413
|
+
var added = __styleGroup(g, ${lit(key)});
|
|
414
|
+
if (!added) {
|
|
415
|
+
// Very rare: the fixed id was renumbered. Retry via the localized label.
|
|
416
|
+
var mid = 0;
|
|
417
|
+
try { mid = app.findMenuCommandId(${lit(menu.label)}); } catch (e) {}
|
|
418
|
+
if (mid) { app.executeCommand(mid); added = __styleGroup(g, ${lit(key)}); }
|
|
419
|
+
}
|
|
420
|
+
if (!added) { throw new Error("Could not add the ${style} layer style (menu command did not take; is the layer a camera or light?)."); }
|
|
421
|
+
try { added.enabled = true; } catch (e) {}
|
|
422
|
+
return { style: ${lit(style)}, matchName: added.matchName, params: __styleParams(added) };
|
|
423
|
+
});`);
|
|
424
|
+
}
|
|
425
|
+
export function getLayerStylesScript(compId, layerIndex) {
|
|
426
|
+
return wrap(`
|
|
427
|
+
var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
|
|
428
|
+
var g = __styles(l);
|
|
429
|
+
var out = { enabled: g.enabled, styles: [] };
|
|
430
|
+
for (var i = 1; i <= g.numProperties; i++) {
|
|
431
|
+
var s = g.property(i);
|
|
432
|
+
if (s.matchName === "ADBE Blend Options Group") { continue; }
|
|
433
|
+
if (!s.canSetEnabled) { continue; } // hidden template group, not actually on the layer
|
|
434
|
+
out.styles.push({ name: s.name, matchName: s.matchName, enabled: s.enabled, params: __styleParams(s) });
|
|
435
|
+
}
|
|
436
|
+
return out;`);
|
|
437
|
+
}
|
|
438
|
+
export function setLayerStyleParamScript(compId, layerIndex, style, param, value) {
|
|
439
|
+
const key = LAYER_STYLE_KEYS[style];
|
|
440
|
+
// Hex colors become AE's [r, g, b, a] 0–1 arrays; other strings pass through.
|
|
441
|
+
const hexMatch = typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value);
|
|
442
|
+
const v = hexMatch ? `${rgb(value).slice(0, -1)}, 1]` : typeof value === "string" ? lit(value) : Array.isArray(value) ? `[${value.map(num).join(", ")}]` : num(value);
|
|
443
|
+
return wrap(`
|
|
444
|
+
var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
|
|
445
|
+
var g = __styles(l);
|
|
446
|
+
var s = __styleGroup(g, ${lit(key)});
|
|
447
|
+
if (!s) { throw new Error("The ${style} style is not on this layer. Call ae_add_layer_style first."); }
|
|
448
|
+
var q = null;
|
|
449
|
+
var want = ${lit(param)};
|
|
450
|
+
var wl = want.toLowerCase();
|
|
451
|
+
for (var i = 1; i <= s.numProperties; i++) {
|
|
452
|
+
var cand = s.property(i);
|
|
453
|
+
if (cand.matchName === want || String(cand.name).toLowerCase() === wl) { q = cand; break; }
|
|
454
|
+
}
|
|
455
|
+
if (!q) {
|
|
456
|
+
var names = [];
|
|
457
|
+
for (var j = 1; j <= s.numProperties; j++) { names.push(s.property(j).name); }
|
|
458
|
+
throw new Error("No parameter " + want + " on " + s.name + ". Parameters: " + names.join(", "));
|
|
459
|
+
}
|
|
460
|
+
return __undo("Set layer style parameter", function () { q.setValue(${v}); return { style: ${lit(style)}, param: q.name, matchName: q.matchName, value: q.value }; });`);
|
|
461
|
+
}
|
|
462
|
+
export function removeLayerStyleScript(compId, layerIndex, style) {
|
|
463
|
+
const key = LAYER_STYLE_KEYS[style];
|
|
464
|
+
// After Effects keeps a permanent slot per style and rejects .remove() on it,
|
|
465
|
+
// so "remove" turns the style off (enabled = false) — the visible effect goes
|
|
466
|
+
// away, matching what the eyeball toggle does in the UI.
|
|
467
|
+
return wrap(`
|
|
468
|
+
var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
|
|
469
|
+
var g = __styles(l);
|
|
470
|
+
var s = __styleGroup(g, ${lit(key)});
|
|
471
|
+
if (!s) { throw new Error("The ${style} style is not on this layer."); }
|
|
472
|
+
return __undo("Turn off layer style", function () { s.enabled = false; return { style: ${lit(style)}, enabled: false }; });`);
|
|
473
|
+
}
|
|
474
|
+
// ---- Essential Graphics / Motion Graphics templates -------------------------
|
|
475
|
+
/**
|
|
476
|
+
* Expose a layer property in the comp's Essential Graphics panel so the comp can
|
|
477
|
+
* be exported as a Motion Graphics template. Verified live on AE 26.3:
|
|
478
|
+
* canAddToMotionGraphicsTemplate / addToMotionGraphicsTemplate take the comp the
|
|
479
|
+
* property lives in; the control is named after the property (AE 26.3 has no
|
|
480
|
+
* script to rename it). Source Text lives at ADBE Text Properties > Text Document.
|
|
481
|
+
*/
|
|
482
|
+
export function addToEssentialGraphicsScript(compId, layerIndex, property, propertyPath) {
|
|
483
|
+
const path = propertyPath ? `[${propertyPath.map(lit).join(", ")}]` : "null";
|
|
484
|
+
return wrap(`
|
|
485
|
+
var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
|
|
486
|
+
var prop = __prop(l, ${lit(property)}, ${path});
|
|
487
|
+
if (!prop.canAddToMotionGraphicsTemplate(c)) {
|
|
488
|
+
throw new Error("'" + prop.name + "' cannot be added to Essential Graphics — either its type is not supported or a control with that name is already there.");
|
|
489
|
+
}
|
|
490
|
+
return __undo("Add to Essential Graphics", function () {
|
|
491
|
+
var ok = prop.addToMotionGraphicsTemplate(c);
|
|
492
|
+
if (!ok) { throw new Error("After Effects refused to add '" + prop.name + "' to the Essential Graphics panel."); }
|
|
493
|
+
var count = c.motionGraphicsTemplateControllerCount;
|
|
494
|
+
var names = [];
|
|
495
|
+
for (var i = 1; i <= count; i++) { names.push(c.getMotionGraphicsTemplateControllerName(i)); }
|
|
496
|
+
return { added: prop.name, controllerCount: count, controllers: names };
|
|
497
|
+
});`);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Export the comp as a .mogrt. The host's exportAsMotionGraphicsTemplate takes a
|
|
501
|
+
* *folder* and names the file from motionGraphicsTemplateName, so we split the
|
|
502
|
+
* requested path, set the name, and write <folder>/<name>.mogrt. Verified live on
|
|
503
|
+
* AE 26.3 (the pre-24.3 "always returns false" bug is gone; we still check the
|
|
504
|
+
* file exists). The project must be saved — a dirty project makes AE prompt.
|
|
505
|
+
*/
|
|
506
|
+
export function exportMogrtScript(compId, folder, name, overwrite) {
|
|
507
|
+
return wrap(`
|
|
508
|
+
var c = __comp(${num(compId)});
|
|
509
|
+
if (!app.project.file) { throw new Error("Save the After Effects project first (ae_save_project) — exporting a Motion Graphics template needs a saved project."); }
|
|
510
|
+
var fo = new Folder(${lit(folder)});
|
|
511
|
+
if (!fo.exists) { fo.create(); }
|
|
512
|
+
c.motionGraphicsTemplateName = ${lit(name)};
|
|
513
|
+
// Read everything off the comp now: exportAsMotionGraphicsTemplate invalidates
|
|
514
|
+
// the comp reference, so touching c afterwards throws "Object is invalid".
|
|
515
|
+
var compName = c.name;
|
|
516
|
+
var controllers = c.motionGraphicsTemplateControllerCount;
|
|
517
|
+
// A missing font (or any project warning) pops a modal, and any modal blocks ALL
|
|
518
|
+
// scripting until a human dismisses it. Suppress dialogs around the save+export
|
|
519
|
+
// so it stays headless.
|
|
520
|
+
var ok = false;
|
|
521
|
+
app.beginSuppressDialogs();
|
|
522
|
+
try {
|
|
523
|
+
app.project.save();
|
|
524
|
+
ok = c.exportAsMotionGraphicsTemplate(${overwrite ? "true" : "false"}, ${lit(folder)});
|
|
525
|
+
} finally {
|
|
526
|
+
app.endSuppressDialogs(false);
|
|
527
|
+
}
|
|
528
|
+
var outPath = ${lit(folder)} + "/" + ${lit(name)} + ".mogrt";
|
|
529
|
+
var f = new File(outPath);
|
|
530
|
+
if (!f.exists) { throw new Error("After Effects did not write the .mogrt (export returned " + ok + "). Does the file already exist without overwrite, or is the folder writable? " + outPath); }
|
|
531
|
+
return { compName: compName, name: ${lit(name)}, outputPath: outPath, controllerCount: controllers };`);
|
|
532
|
+
}
|
|
302
533
|
export function setTextScript(p) {
|
|
303
534
|
return wrap(`
|
|
304
535
|
var c = __comp(${num(p.compId)}); var l = __layer(c, ${num(p.layerIndex)});
|
|
@@ -516,6 +747,17 @@ export function registerAfterEffectsTools(server, bridge, options = {}) {
|
|
|
516
747
|
description: "Import an image, video, audio, or PSD file into the project. Returns the footage item id for ae_add_layer.",
|
|
517
748
|
inputSchema: { path: z.string().min(1).describe("Absolute path to the media file.") },
|
|
518
749
|
}, async ({ path }) => run(importFootageScript(path)));
|
|
750
|
+
server.registerTool("ae_import_as_comp", {
|
|
751
|
+
title: "After Effects: import a PSD or AI file as a composition",
|
|
752
|
+
description: "Import a layered Photoshop (.psd) or Illustrator (.ai) file as a composition — every source layer becomes " +
|
|
753
|
+
"its own After Effects layer, keeping blend modes, positions, and (for PSD) layer styles as After Effects " +
|
|
754
|
+
"imports them. Illustrator files must be RGB (not CMYK) and saved with PDF compatibility. Returns the new " +
|
|
755
|
+
"composition with its layers.",
|
|
756
|
+
inputSchema: {
|
|
757
|
+
path: z.string().min(1).describe("Absolute path to a .psd or .ai file."),
|
|
758
|
+
cropped: z.boolean().optional().describe("Crop each layer to its content (Composition - Cropped Layers). Defaults to false = keep document-size layers."),
|
|
759
|
+
},
|
|
760
|
+
}, async ({ path, cropped }) => run(importAsCompScript(path, cropped ?? false)));
|
|
519
761
|
// ---- comps & layers -----------------------------------------------------
|
|
520
762
|
server.registerTool("ae_create_comp", {
|
|
521
763
|
title: "After Effects: create a composition",
|
|
@@ -604,9 +846,52 @@ export function registerAfterEffectsTools(server, bridge, options = {}) {
|
|
|
604
846
|
}, async ({ compId: id, layerIndex: li, property, propertyPath: pp }) => run(removeKeyframesScript(id, li, property, pp)));
|
|
605
847
|
server.registerTool("ae_set_expression", {
|
|
606
848
|
title: "After Effects: set an expression",
|
|
607
|
-
description: "Set (or clear with null) the expression on a layer property.
|
|
849
|
+
description: "Set (or clear with null) the expression on a layer property. Use `property` for a transform channel, or " +
|
|
850
|
+
"`propertyPath` (match/display names) to reach an effect parameter, mask, or text source. If the expression " +
|
|
851
|
+
"does not compile it fails with the error text and the property keeps its previous expression.",
|
|
608
852
|
inputSchema: { compId, layerIndex, property: propertyName, propertyPath, expression: z.string().nullable().describe("Expression source, or null to remove.") },
|
|
609
853
|
}, async ({ compId: id, layerIndex: li, property, propertyPath: pp, expression }) => run(setExpressionScript(id, li, property, pp, expression)));
|
|
854
|
+
// ---- essential graphics / motion graphics templates ---------------------
|
|
855
|
+
server.registerTool("ae_add_to_essential_graphics", {
|
|
856
|
+
title: "After Effects: add a control to Essential Graphics",
|
|
857
|
+
description: "Expose a layer property in the comp's Essential Graphics panel so the comp can be exported as a Motion " +
|
|
858
|
+
"Graphics template (.mogrt). Give `property` for a transform channel or `sourceText` for a text layer's " +
|
|
859
|
+
"content, or `propertyPath` (match/display names) for anything else, e.g. an effect's Slider or Color. " +
|
|
860
|
+
"The control takes the property's name (rename it in the panel; After Effects has no script for that).",
|
|
861
|
+
inputSchema: {
|
|
862
|
+
compId,
|
|
863
|
+
layerIndex,
|
|
864
|
+
property: z
|
|
865
|
+
.enum(["position", "scale", "rotation", "opacity", "anchorPoint", "sourceText"])
|
|
866
|
+
.optional()
|
|
867
|
+
.describe("Transform channel, or sourceText for a text layer's content. For anything else, use propertyPath."),
|
|
868
|
+
propertyPath,
|
|
869
|
+
},
|
|
870
|
+
}, async ({ compId: id, layerIndex: li, property, propertyPath: pp }) => {
|
|
871
|
+
const path = property === "sourceText" && !pp ? ["ADBE Text Properties", "ADBE Text Document"] : pp;
|
|
872
|
+
const prop = property && property !== "sourceText" ? property : "position";
|
|
873
|
+
return run(addToEssentialGraphicsScript(id, li, prop, path));
|
|
874
|
+
});
|
|
875
|
+
server.registerTool("ae_export_mogrt", {
|
|
876
|
+
title: "After Effects: export a Motion Graphics template",
|
|
877
|
+
description: "Export a composition as a Motion Graphics template (.mogrt) — the controls added with " +
|
|
878
|
+
"ae_add_to_essential_graphics become its editable fields. Saves the project first (it must have been saved " +
|
|
879
|
+
"once). Returns the written path.",
|
|
880
|
+
inputSchema: {
|
|
881
|
+
compId,
|
|
882
|
+
outputPath: z.string().min(1).describe("Absolute path ending in .mogrt. The file name (minus .mogrt) becomes the template name."),
|
|
883
|
+
overwrite: z.boolean().optional().describe("Overwrite an existing file at that path. Defaults to true."),
|
|
884
|
+
},
|
|
885
|
+
annotations: { destructiveHint: true },
|
|
886
|
+
}, async ({ compId: id, outputPath, overwrite }) => {
|
|
887
|
+
const norm = outputPath.replace(/\\/g, "/");
|
|
888
|
+
const slash = norm.lastIndexOf("/");
|
|
889
|
+
const folder = slash >= 0 ? norm.slice(0, slash) : ".";
|
|
890
|
+
const name = (slash >= 0 ? norm.slice(slash + 1) : norm).replace(/\.mogrt$/i, "");
|
|
891
|
+
if (!name)
|
|
892
|
+
return run(exportMogrtScript(id, folder, "Untitled", overwrite ?? true)); // unreachable-ish; keeps a name
|
|
893
|
+
return run(exportMogrtScript(id, folder, name, overwrite ?? true));
|
|
894
|
+
});
|
|
610
895
|
// ---- effects, text, markers ---------------------------------------------
|
|
611
896
|
server.registerTool("ae_apply_effect", {
|
|
612
897
|
title: "After Effects: apply an effect",
|
|
@@ -625,6 +910,41 @@ export function registerAfterEffectsTools(server, bridge, options = {}) {
|
|
|
625
910
|
value: z.union([z.number(), z.array(z.number()), z.string()]),
|
|
626
911
|
},
|
|
627
912
|
}, async ({ compId: id, layerIndex: li, effect, param, value }) => run(setEffectParamScript(id, li, effect, param, value)));
|
|
913
|
+
// ---- layer styles -------------------------------------------------------
|
|
914
|
+
const styleName = z
|
|
915
|
+
.enum(["dropShadow", "innerShadow", "outerGlow", "innerGlow", "bevelEmboss", "satin", "colorOverlay", "gradientOverlay", "stroke"])
|
|
916
|
+
.describe("Layer style name.");
|
|
917
|
+
server.registerTool("ae_add_layer_style", {
|
|
918
|
+
title: "After Effects: add a layer style",
|
|
919
|
+
description: "Add a Photoshop-style layer style (drop shadow, inner shadow, glows, bevel & emboss, satin, color/gradient " +
|
|
920
|
+
"overlay, stroke) to a layer. Returns the style's parameters (names and current values) for ae_set_layer_style_param.",
|
|
921
|
+
inputSchema: { compId, layerIndex, style: styleName },
|
|
922
|
+
}, async ({ compId: id, layerIndex: li, style }) => run(addLayerStyleScript(id, li, style)));
|
|
923
|
+
server.registerTool("ae_get_layer_styles", {
|
|
924
|
+
title: "After Effects: read a layer's styles",
|
|
925
|
+
description: "List the layer styles on a layer with each style's parameters and current values.",
|
|
926
|
+
inputSchema: { compId, layerIndex },
|
|
927
|
+
annotations: { readOnlyHint: true },
|
|
928
|
+
}, async ({ compId: id, layerIndex: li }) => run(getLayerStylesScript(id, li), { timeoutClass: "fast" }));
|
|
929
|
+
server.registerTool("ae_set_layer_style_param", {
|
|
930
|
+
title: "After Effects: set a layer style parameter",
|
|
931
|
+
description: "Set a parameter on a layer style already added with ae_add_layer_style — by display name (e.g. 'Distance', " +
|
|
932
|
+
"'Opacity', 'Color') or match name (e.g. 'dropShadow/blur'). Colors take a hex string or [r,g,b] 0–1 array.",
|
|
933
|
+
inputSchema: {
|
|
934
|
+
compId,
|
|
935
|
+
layerIndex,
|
|
936
|
+
style: styleName,
|
|
937
|
+
param: z.string().min(1).describe("Parameter display name or match name."),
|
|
938
|
+
value: z.union([z.number(), z.array(z.number()), z.string()]).describe("Number, [r,g,b] 0–1 array, or hex color like #ff8800."),
|
|
939
|
+
},
|
|
940
|
+
}, async ({ compId: id, layerIndex: li, style, param, value }) => run(setLayerStyleParamScript(id, li, style, param, value)));
|
|
941
|
+
server.registerTool("ae_remove_layer_style", {
|
|
942
|
+
title: "After Effects: turn off a layer style",
|
|
943
|
+
description: "Turn a layer style off (its visible effect goes away). After Effects keeps a permanent slot per style, " +
|
|
944
|
+
"so this disables the style rather than deleting the slot; ae_add_layer_style turns it back on.",
|
|
945
|
+
inputSchema: { compId, layerIndex, style: styleName },
|
|
946
|
+
annotations: { destructiveHint: true },
|
|
947
|
+
}, async ({ compId: id, layerIndex: li, style }) => run(removeLayerStyleScript(id, li, style)));
|
|
628
948
|
server.registerTool("ae_set_text", {
|
|
629
949
|
title: "After Effects: set text layer content and style",
|
|
630
950
|
description: "Change a text layer's text, font size, font (PostScript name), color, or justification.",
|