@engine-room/after-effects-mcp 0.1.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.
@@ -0,0 +1,1562 @@
1
+ // Auto-generated bundle. Do not edit directly — edit files in packages/jsx/.
2
+ // Generated 2026-08-10T14:20:22.883Z
3
+
4
+ // ===== core.jsx =====
5
+
6
+ // core.jsx — JSON polyfill, dispatcher, undo wrapper, async job table.
7
+ // ExtendScript is ES3-ish; no let/const/arrow/template-literals.
8
+
9
+ // ---------- JSON polyfill (defensive; AE 2026 has JSON natively but we never know) ----------
10
+ if (typeof JSON === "undefined") { JSON = {}; }
11
+ if (typeof JSON.stringify !== "function") {
12
+ JSON.stringify = (function () {
13
+ function quote(s) {
14
+ var r = '"';
15
+ for (var i = 0; i < s.length; i++) {
16
+ var c = s.charAt(i), cc = s.charCodeAt(i);
17
+ if (c === '"') r += '\\"';
18
+ else if (c === "\\") r += "\\\\";
19
+ else if (c === "\n") r += "\\n";
20
+ else if (c === "\r") r += "\\r";
21
+ else if (c === "\t") r += "\\t";
22
+ else if (c === "\b") r += "\\b";
23
+ else if (c === "\f") r += "\\f";
24
+ else if (cc < 0x20) {
25
+ var h = cc.toString(16); while (h.length < 4) h = "0" + h;
26
+ r += "\\u" + h;
27
+ } else r += c;
28
+ }
29
+ return r + '"';
30
+ }
31
+ function str(v) {
32
+ if (v === null) return "null";
33
+ if (v === undefined) return "null";
34
+ var t = typeof v;
35
+ if (t === "number") return isFinite(v) ? String(v) : "null";
36
+ if (t === "boolean") return v ? "true" : "false";
37
+ if (t === "string") return quote(v);
38
+ if (t === "object") {
39
+ if (v instanceof Array) {
40
+ var a = [];
41
+ for (var i = 0; i < v.length; i++) a.push(str(v[i]));
42
+ return "[" + a.join(",") + "]";
43
+ }
44
+ var keys = [];
45
+ for (var k in v) { if (v.hasOwnProperty(k)) keys.push(k); }
46
+ var parts = [];
47
+ for (var j = 0; j < keys.length; j++) {
48
+ var val = str(v[keys[j]]);
49
+ if (val !== undefined) parts.push(quote(keys[j]) + ":" + val);
50
+ }
51
+ return "{" + parts.join(",") + "}";
52
+ }
53
+ return "null";
54
+ }
55
+ return function (v) { return str(v); };
56
+ })();
57
+ }
58
+ if (typeof JSON.parse !== "function") {
59
+ JSON.parse = function (text) { return eval("(" + text + ")"); };
60
+ }
61
+
62
+ // ---------- Global registry ----------
63
+ var OPS = OPS || {};
64
+ var JOBS = JOBS || {};
65
+ var __JOB_SEQ = __JOB_SEQ || 0;
66
+
67
+ function __newJobId() {
68
+ __JOB_SEQ += 1;
69
+ return "j_" + (new Date().getTime()) + "_" + __JOB_SEQ;
70
+ }
71
+
72
+ // ---------- Undo wrapper ----------
73
+ function withUndo(name, fn) {
74
+ app.beginUndoGroup(name || "AE MCP");
75
+ try { return fn(); }
76
+ finally { app.endUndoGroup(); }
77
+ }
78
+
79
+ // ---------- Error helper ----------
80
+ function __mkError(e) {
81
+ var msg = e && e.message ? String(e.message) : String(e);
82
+ var stack = e && e.stack ? String(e.stack) : "";
83
+ var line = e && typeof e.line !== "undefined" ? e.line : null;
84
+ return { ok: false, error: msg, stack: stack, line: line };
85
+ }
86
+
87
+ // ---------- Main dispatch ----------
88
+ // Called from panel with a JSON string payload {op, args, requestId}.
89
+ // Returns a plain object {ok, result|error}. Panel JSON.stringifys.
90
+ function dispatch(payloadJson) {
91
+ var payload;
92
+ try { payload = JSON.parse(payloadJson); }
93
+ catch (e) { return { ok: false, error: "Bad payload JSON: " + e.message }; }
94
+ var op = payload.op;
95
+ var args = payload.args || {};
96
+ if (!OPS.hasOwnProperty(op)) return { ok: false, error: "Unknown op: " + op };
97
+ try {
98
+ var handler = OPS[op];
99
+ var meta = handler.__meta || {};
100
+ if (meta.noUndo) {
101
+ return { ok: true, result: handler(args) };
102
+ }
103
+ var result = withUndo(meta.undoName || ("AE MCP: " + op), function () { return handler(args); });
104
+ return { ok: true, result: result };
105
+ } catch (e) {
106
+ return __mkError(e);
107
+ }
108
+ }
109
+
110
+ // Helper for handlers that don't want an undo group (read-only ops, job continuations).
111
+ function noUndo(fn) { fn.__meta = { noUndo: true }; return fn; }
112
+ function undoNamed(name, fn) { fn.__meta = { undoName: name }; return fn; }
113
+
114
+
115
+ // ===== ids.jsx =====
116
+
117
+ // ids.jsx — stable ID lookup helpers. (compId, layerId) is the canonical pair.
118
+
119
+ function getCompById(id) {
120
+ if (id === null || id === undefined) throw new Error("compId required");
121
+ var item = app.project.itemByID(id);
122
+ if (!item) throw new Error("No comp with id " + id);
123
+ if (!(item instanceof CompItem)) throw new Error("Item " + id + " is not a CompItem");
124
+ return item;
125
+ }
126
+
127
+ function getItemById(id) {
128
+ if (id === null || id === undefined) throw new Error("itemId required");
129
+ var item = app.project.itemByID(id);
130
+ if (!item) throw new Error("No item with id " + id);
131
+ return item;
132
+ }
133
+
134
+ function getLayerById(comp, layerId) {
135
+ if (!(comp instanceof CompItem)) throw new Error("Expected CompItem");
136
+ if (layerId === null || layerId === undefined) throw new Error("layerId required");
137
+ for (var i = 1; i <= comp.numLayers; i++) {
138
+ var l = comp.layer(i);
139
+ if (l.id === layerId) return l;
140
+ }
141
+ throw new Error("No layer with id " + layerId + " in comp " + comp.id);
142
+ }
143
+
144
+ // Walk a propertyPath into a layer/group, e.g. ["Transform","Position"] or ["Effects","Gaussian Blur","Blurriness"].
145
+ // Supports numeric indices (1-based) and string names. Returns the Property/PropertyGroup.
146
+ function walkProperty(root, path) {
147
+ if (!path || !path.length) throw new Error("Empty propertyPath");
148
+ var cur = root;
149
+ for (var i = 0; i < path.length; i++) {
150
+ var seg = path[i];
151
+ try { cur = cur.property(seg); }
152
+ catch (e) { throw new Error("Property path segment failed at [" + i + "]=" + String(seg) + ": " + e.message); }
153
+ if (!cur) throw new Error("Property path segment returned null at [" + i + "]=" + String(seg));
154
+ }
155
+ return cur;
156
+ }
157
+
158
+ function colorOrDefault(c, fallback) {
159
+ if (!c) return fallback;
160
+ return [Number(c[0]), Number(c[1]), Number(c[2])];
161
+ }
162
+
163
+
164
+ // ===== comps.jsx =====
165
+
166
+ // comps.jsx — composition ops.
167
+
168
+ function __compSummary(c) {
169
+ return {
170
+ id: c.id,
171
+ name: c.name,
172
+ width: c.width,
173
+ height: c.height,
174
+ duration: c.duration,
175
+ frameRate: c.frameRate,
176
+ pixelAspect: c.pixelAspect,
177
+ bgColor: [c.bgColor[0], c.bgColor[1], c.bgColor[2]],
178
+ numLayers: c.numLayers,
179
+ workAreaStart: c.workAreaStart,
180
+ workAreaDuration: c.workAreaDuration,
181
+ };
182
+ }
183
+
184
+ OPS.list_comps = noUndo(function (args) {
185
+ var out = [];
186
+ for (var i = 1; i <= app.project.numItems; i++) {
187
+ var it = app.project.item(i);
188
+ if (it instanceof CompItem) out.push(__compSummary(it));
189
+ }
190
+ return out;
191
+ });
192
+
193
+ OPS.get_comp = noUndo(function (args) {
194
+ return __compSummary(getCompById(args.compId));
195
+ });
196
+
197
+ OPS.create_comp = function (args) {
198
+ var bg = args.bgColor || [0, 0, 0];
199
+ var c = app.project.items.addComp(
200
+ args.name || "Untitled",
201
+ args.width || 1920,
202
+ args.height || 1080,
203
+ args.pixelAspect || 1,
204
+ args.duration || 5,
205
+ args.frameRate || 30
206
+ );
207
+ c.bgColor = [bg[0], bg[1], bg[2]];
208
+ return __compSummary(c);
209
+ };
210
+
211
+ OPS.set_comp = function (args) {
212
+ var c = getCompById(args.compId);
213
+ if (args.name !== undefined) c.name = args.name;
214
+ if (args.width !== undefined) c.width = args.width;
215
+ if (args.height !== undefined) c.height = args.height;
216
+ if (args.frameRate !== undefined) c.frameRate = args.frameRate;
217
+ if (args.duration !== undefined) c.duration = args.duration;
218
+ if (args.workAreaStart !== undefined) c.workAreaStart = args.workAreaStart;
219
+ if (args.workAreaDuration !== undefined) c.workAreaDuration = args.workAreaDuration;
220
+ if (args.bgColor) c.bgColor = [args.bgColor[0], args.bgColor[1], args.bgColor[2]];
221
+ return __compSummary(c);
222
+ };
223
+
224
+ OPS.delete_comp = function (args) {
225
+ var c = getCompById(args.compId);
226
+ c.remove();
227
+ return { ok: true };
228
+ };
229
+
230
+ OPS.set_active_comp = function (args) {
231
+ var c = getCompById(args.compId);
232
+ c.openInViewer();
233
+ return { ok: true };
234
+ };
235
+
236
+ OPS.get_comp_tree = noUndo(function (args) {
237
+ var c = getCompById(args.compId);
238
+ var depth = args.depth || 2;
239
+ function summarize(comp, d) {
240
+ var s = __compSummary(comp);
241
+ s.layers = [];
242
+ for (var i = 1; i <= comp.numLayers; i++) {
243
+ var l = comp.layer(i);
244
+ var ls = __layerSummary(l);
245
+ if (d > 0 && l.source && l.source instanceof CompItem) {
246
+ ls.precomp = summarize(l.source, d - 1);
247
+ }
248
+ s.layers.push(ls);
249
+ }
250
+ return s;
251
+ }
252
+ return summarize(c, depth);
253
+ });
254
+
255
+
256
+ // ===== layers.jsx =====
257
+
258
+ // layers.jsx — all layer-level ops.
259
+
260
+ function __layerKind(l) {
261
+ if (l instanceof TextLayer) return "text";
262
+ if (l instanceof ShapeLayer) return "shape";
263
+ if (l instanceof CameraLayer) return "camera";
264
+ if (l instanceof LightLayer) return "light";
265
+ if (l.nullLayer) return "null";
266
+ if (l.adjustmentLayer) return "adjustment";
267
+ if (l.source && l.source instanceof CompItem) return "precomp";
268
+ if (l.source && l.source instanceof FootageItem) {
269
+ if (l.source.mainSource && l.source.mainSource.color !== undefined) return "solid";
270
+ return "footage";
271
+ }
272
+ return "unknown";
273
+ }
274
+
275
+ function __layerSummary(l) {
276
+ var parent = l.parent ? l.parent.id : null;
277
+ return {
278
+ id: l.id,
279
+ index: l.index,
280
+ name: l.name,
281
+ enabled: l.enabled,
282
+ solo: l.solo,
283
+ locked: l.locked,
284
+ shy: l.shy,
285
+ threeDLayer: l.threeDLayer,
286
+ label: l.label,
287
+ inPoint: l.inPoint,
288
+ outPoint: l.outPoint,
289
+ startTime: l.startTime,
290
+ stretch: l.stretch,
291
+ sourceType: __layerKind(l),
292
+ parent: parent,
293
+ blendingMode: l.blendingMode,
294
+ };
295
+ }
296
+
297
+ OPS.list_layers = noUndo(function (args) {
298
+ var c = getCompById(args.compId);
299
+ var out = [];
300
+ for (var i = 1; i <= c.numLayers; i++) out.push(__layerSummary(c.layer(i)));
301
+ return out;
302
+ });
303
+
304
+ OPS.create_text_layer = function (args) {
305
+ var c = getCompById(args.compId);
306
+ var l = c.layers.addText(args.text || "");
307
+ if (args.name) l.name = args.name;
308
+ // Apply font/size/color through TextDocument first — sourceRectAtTime depends on these.
309
+ if (args.font || args.size || args.color) {
310
+ var srcText = l.property("Source Text");
311
+ var td = srcText.value;
312
+ if (args.font) td.font = args.font;
313
+ if (args.size) td.fontSize = args.size;
314
+ if (args.color) { td.applyFill = true; td.fillColor = [args.color[0], args.color[1], args.color[2]]; }
315
+ srcText.setValue(td);
316
+ }
317
+ // AE's addText() puts the anchor at the bbox center, which surprises agents
318
+ // who expect position to mean the left edge. Default to 'left' so position
319
+ // semantically matches the visible start of the text. 'none' opts out.
320
+ var align = args.anchorAlign === undefined ? "left" : args.anchorAlign;
321
+ if (align !== "none") {
322
+ try {
323
+ var __rect = l.sourceRectAtTime(c.time, false);
324
+ var __ax = __rect.left;
325
+ if (align === "center") __ax = __rect.left + __rect.width / 2;
326
+ else if (align === "right") __ax = __rect.left + __rect.width;
327
+ l.property("Transform").property("Anchor Point").setValue([__ax, 0, 0]);
328
+ } catch (__e) {}
329
+ }
330
+ if (args.position) {
331
+ var p = args.position;
332
+ l.property("Transform").property("Position").setValue(p.length === 3 ? p : [p[0], p[1]]);
333
+ }
334
+ return __layerSummary(l);
335
+ };
336
+
337
+ OPS.create_solid_layer = function (args) {
338
+ var c = getCompById(args.compId);
339
+ var w = args.width || c.width;
340
+ var h = args.height || c.height;
341
+ var dur = args.duration || c.duration;
342
+ var col = args.color;
343
+ var l = c.layers.addSolid([col[0], col[1], col[2]], args.name || "Solid", w, h, c.pixelAspect, dur);
344
+ return __layerSummary(l);
345
+ };
346
+
347
+ OPS.create_null_layer = function (args) {
348
+ var c = getCompById(args.compId);
349
+ var l = c.layers.addNull();
350
+ if (args.name) l.name = args.name;
351
+ return __layerSummary(l);
352
+ };
353
+
354
+ OPS.create_adjustment_layer = function (args) {
355
+ var c = getCompById(args.compId);
356
+ var l = c.layers.addSolid([1, 1, 1], args.name || "Adjustment", c.width, c.height, c.pixelAspect, c.duration);
357
+ l.adjustmentLayer = true;
358
+ return __layerSummary(l);
359
+ };
360
+
361
+ OPS.create_shape_layer = function (args) {
362
+ var c = getCompById(args.compId);
363
+ var l = c.layers.addShape();
364
+ if (args.name) l.name = args.name;
365
+ // shapes payload kept loose for v1 — the agent can use add_shape_content for detail
366
+ return __layerSummary(l);
367
+ };
368
+
369
+ OPS.create_precomp_layer = function (args) {
370
+ var c = getCompById(args.compId);
371
+ var src = getCompById(args.sourceCompId);
372
+ var l = c.layers.add(src);
373
+ if (args.position) {
374
+ var p = args.position;
375
+ l.property("Transform").property("Position").setValue(p.length === 3 ? p : [p[0], p[1]]);
376
+ }
377
+ return __layerSummary(l);
378
+ };
379
+
380
+ OPS.create_camera_layer = function (args) {
381
+ var c = getCompById(args.compId);
382
+ var center = (args.position && args.position.length >= 2) ? [args.position[0], args.position[1]] : [c.width / 2, c.height / 2];
383
+ var l = c.layers.addCamera(args.name || "Camera", center);
384
+ if (args.oneNode) { try { l.autoOrient = AutoOrientType.NO_AUTO_ORIENT; } catch (e) {} }
385
+ return __layerSummary(l);
386
+ };
387
+
388
+ OPS.create_light_layer = function (args) {
389
+ var c = getCompById(args.compId);
390
+ var center = (args.position && args.position.length >= 2) ? [args.position[0], args.position[1]] : [c.width / 2, c.height / 2];
391
+ var l = c.layers.addLight(args.name || "Light", center);
392
+ var lightTypeMap = { parallel: LightType.PARALLEL, spot: LightType.SPOT, point: LightType.POINT, ambient: LightType.AMBIENT };
393
+ if (args.lightType && lightTypeMap[args.lightType]) l.lightType = lightTypeMap[args.lightType];
394
+ if (args.color) l.lightOption.property("Color").setValue([args.color[0], args.color[1], args.color[2]]);
395
+ if (args.intensity !== undefined) l.lightOption.property("Intensity").setValue(args.intensity);
396
+ return __layerSummary(l);
397
+ };
398
+
399
+ OPS.duplicate_layer = function (args) {
400
+ var c = getCompById(args.compId);
401
+ var l = getLayerById(c, args.layerId);
402
+ var n = args.count || 1;
403
+ var out = [];
404
+ for (var i = 0; i < n; i++) {
405
+ var d = l.duplicate();
406
+ out.push(__layerSummary(d));
407
+ }
408
+ return out;
409
+ };
410
+
411
+ OPS.delete_layer = function (args) {
412
+ var c = getCompById(args.compId);
413
+ var l = getLayerById(c, args.layerId);
414
+ l.remove();
415
+ return { ok: true };
416
+ };
417
+
418
+ OPS.set_layer = function (args) {
419
+ var c = getCompById(args.compId);
420
+ var l = getLayerById(c, args.layerId);
421
+ if (args.name !== undefined) l.name = args.name;
422
+ if (args.enabled !== undefined) l.enabled = args.enabled;
423
+ if (args.locked !== undefined) l.locked = args.locked;
424
+ if (args.shy !== undefined) l.shy = args.shy;
425
+ if (args.solo !== undefined) l.solo = args.solo;
426
+ if (args.threeDLayer !== undefined) l.threeDLayer = args.threeDLayer;
427
+ if (args.blendingMode !== undefined) {
428
+ try { l.blendingMode = BlendingMode[args.blendingMode] || l.blendingMode; }
429
+ catch (e) {}
430
+ }
431
+ if (args.label !== undefined) l.label = args.label;
432
+ if (args.inPoint !== undefined) l.inPoint = args.inPoint;
433
+ if (args.outPoint !== undefined) l.outPoint = args.outPoint;
434
+ if (args.startTime !== undefined) l.startTime = args.startTime;
435
+ if (args.stretch !== undefined) l.stretch = args.stretch;
436
+ if (args.preserveTransparency !== undefined) l.preserveTransparency = args.preserveTransparency;
437
+ if (args.trackMatte) {
438
+ if (args.trackMatte.type) {
439
+ try { l.trackMatteType = TrackMatteType[args.trackMatte.type] || l.trackMatteType; }
440
+ catch (e2) {}
441
+ }
442
+ }
443
+ return __layerSummary(l);
444
+ };
445
+
446
+ OPS.parent_layer = function (args) {
447
+ var c = getCompById(args.compId);
448
+ var l = getLayerById(c, args.layerId);
449
+ if (args.parentLayerId === null) {
450
+ l.parent = null;
451
+ } else {
452
+ l.parent = getLayerById(c, args.parentLayerId);
453
+ }
454
+ return __layerSummary(l);
455
+ };
456
+
457
+ OPS.reorder_layer = function (args) {
458
+ var c = getCompById(args.compId);
459
+ var l = getLayerById(c, args.layerId);
460
+ l.moveTo(args.toIndex);
461
+ return __layerSummary(l);
462
+ };
463
+
464
+
465
+ // ===== transforms.jsx =====
466
+
467
+ // transforms.jsx — fast-path for setting common transform properties.
468
+
469
+ function __setOrKeyAtTime(prop, value, time, keyframe) {
470
+ if (keyframe && time !== undefined && time !== null) {
471
+ prop.setValueAtTime(time, value);
472
+ } else if (time !== undefined && time !== null && !keyframe) {
473
+ // Set value at time only meaningful if property has keyframes; else set static.
474
+ if (prop.numKeys > 0) prop.setValueAtTime(time, value);
475
+ else prop.setValue(value);
476
+ } else {
477
+ prop.setValue(value);
478
+ }
479
+ }
480
+
481
+ OPS.set_transform = function (args) {
482
+ var c = getCompById(args.compId);
483
+ var l = getLayerById(c, args.layerId);
484
+ var t = args.time;
485
+ var kf = !!args.keyframe;
486
+ var tr = l.property("Transform");
487
+ var p = args.properties || {};
488
+ if (p.position !== undefined) __setOrKeyAtTime(tr.property("Position"), p.position, t, kf);
489
+ if (p.scale !== undefined) __setOrKeyAtTime(tr.property("Scale"), p.scale, t, kf);
490
+ if (p.rotation !== undefined) {
491
+ var rotProp = l.threeDLayer ? tr.property("Z Rotation") : tr.property("Rotation");
492
+ __setOrKeyAtTime(rotProp, p.rotation, t, kf);
493
+ }
494
+ if (p.anchorPoint !== undefined) __setOrKeyAtTime(tr.property("Anchor Point"), p.anchorPoint, t, kf);
495
+ if (p.opacity !== undefined) __setOrKeyAtTime(tr.property("Opacity"), p.opacity, t, kf);
496
+ if (p.orientation !== undefined && l.threeDLayer) __setOrKeyAtTime(tr.property("Orientation"), p.orientation, t, kf);
497
+ if (p.xRotation !== undefined && l.threeDLayer) __setOrKeyAtTime(tr.property("X Rotation"), p.xRotation, t, kf);
498
+ if (p.yRotation !== undefined && l.threeDLayer) __setOrKeyAtTime(tr.property("Y Rotation"), p.yRotation, t, kf);
499
+ if (p.zRotation !== undefined && l.threeDLayer) __setOrKeyAtTime(tr.property("Z Rotation"), p.zRotation, t, kf);
500
+ return { ok: true };
501
+ };
502
+
503
+
504
+ // ===== keyframes.jsx =====
505
+
506
+ // keyframes.jsx — generic keyframe ops.
507
+
508
+ var __INTERP_MAP = {
509
+ linear: KeyframeInterpolationType.LINEAR,
510
+ bezier: KeyframeInterpolationType.BEZIER,
511
+ hold: KeyframeInterpolationType.HOLD,
512
+ };
513
+
514
+ function __findKeyIndexAtTime(prop, time, eps) {
515
+ eps = eps || 0.001;
516
+ for (var i = 1; i <= prop.numKeys; i++) {
517
+ if (Math.abs(prop.keyTime(i) - time) < eps) return i;
518
+ }
519
+ return -1;
520
+ }
521
+
522
+ function __applyInterpolationToKey(prop, keyIndex, interp) {
523
+ if (!interp) return;
524
+ var inT = interp["in"] && __INTERP_MAP[interp["in"]] ? __INTERP_MAP[interp["in"]] : prop.keyInInterpolationType(keyIndex);
525
+ var outT = interp["out"] && __INTERP_MAP[interp["out"]] ? __INTERP_MAP[interp["out"]] : prop.keyOutInterpolationType(keyIndex);
526
+ prop.setInterpolationTypeAtKey(keyIndex, inT, outT);
527
+ if (interp.easeIn || interp.easeOut) {
528
+ // AE's setTemporalEaseAtKey expects an array of KeyframeEase per dimension —
529
+ // BUT spatial properties (Position, Anchor Point) use a single ease entry that
530
+ // applies along the motion path, regardless of 2D/3D. Non-spatial multi-dim
531
+ // properties (Scale, Color) need one entry per dimension.
532
+ var dim;
533
+ if (prop.isSpatial) {
534
+ dim = 1;
535
+ } else {
536
+ dim = (prop.value && prop.value.length) ? prop.value.length : 1;
537
+ }
538
+ var inEase = interp.easeIn || { influence: 33, speed: 0 };
539
+ var outEase = interp.easeOut || { influence: 33, speed: 0 };
540
+ var inArr = []; var outArr = [];
541
+ for (var d = 0; d < dim; d++) {
542
+ inArr.push(new KeyframeEase(inEase.speed, inEase.influence));
543
+ outArr.push(new KeyframeEase(outEase.speed, outEase.influence));
544
+ }
545
+ prop.setTemporalEaseAtKey(keyIndex, inArr, outArr);
546
+ }
547
+ }
548
+
549
+ OPS.add_keyframe = function (args) {
550
+ var c = getCompById(args.compId);
551
+ var l = getLayerById(c, args.layerId);
552
+ var prop = walkProperty(l, args.propertyPath);
553
+ prop.setValueAtTime(args.time, args.value);
554
+ if (args.interpolation) {
555
+ var idx = __findKeyIndexAtTime(prop, args.time);
556
+ if (idx > 0) __applyInterpolationToKey(prop, idx, args.interpolation);
557
+ }
558
+ return { ok: true, keyIndex: __findKeyIndexAtTime(prop, args.time) };
559
+ };
560
+
561
+ OPS.remove_keyframe = function (args) {
562
+ var c = getCompById(args.compId);
563
+ var l = getLayerById(c, args.layerId);
564
+ var prop = walkProperty(l, args.propertyPath);
565
+ var idx = __findKeyIndexAtTime(prop, args.time);
566
+ if (idx < 1) throw new Error("No keyframe at time " + args.time);
567
+ prop.removeKey(idx);
568
+ return { ok: true };
569
+ };
570
+
571
+ OPS.get_keyframes = noUndo(function (args) {
572
+ var c = getCompById(args.compId);
573
+ var l = getLayerById(c, args.layerId);
574
+ var prop = walkProperty(l, args.propertyPath);
575
+ var keys = [];
576
+ for (var i = 1; i <= prop.numKeys; i++) {
577
+ var ease = null;
578
+ try {
579
+ var inE = prop.keyInTemporalEase(i);
580
+ var outE = prop.keyOutTemporalEase(i);
581
+ ease = {
582
+ easeIn: { influence: inE[0].influence, speed: inE[0].speed },
583
+ easeOut: { influence: outE[0].influence, speed: outE[0].speed },
584
+ };
585
+ } catch (e) {}
586
+ var tangents = null;
587
+ if (prop.isSpatial) {
588
+ try { tangents = { inTangent: prop.keyInSpatialTangent(i), outTangent: prop.keyOutSpatialTangent(i) }; }
589
+ catch (e2) {}
590
+ }
591
+ keys.push({
592
+ index: i,
593
+ time: prop.keyTime(i),
594
+ value: prop.keyValue(i),
595
+ interpolation: {
596
+ "in": __invInterp(prop.keyInInterpolationType(i)),
597
+ "out": __invInterp(prop.keyOutInterpolationType(i)),
598
+ },
599
+ ease: ease,
600
+ tangents: tangents,
601
+ });
602
+ }
603
+ return keys;
604
+ });
605
+
606
+ function __invInterp(t) {
607
+ if (t === KeyframeInterpolationType.LINEAR) return "linear";
608
+ if (t === KeyframeInterpolationType.BEZIER) return "bezier";
609
+ if (t === KeyframeInterpolationType.HOLD) return "hold";
610
+ return "unknown";
611
+ }
612
+
613
+ OPS.set_interpolation = function (args) {
614
+ var c = getCompById(args.compId);
615
+ var l = getLayerById(c, args.layerId);
616
+ var prop = walkProperty(l, args.propertyPath);
617
+ __applyInterpolationToKey(prop, args.keyIndex, { "in": args["in"], "out": args["out"] });
618
+ return { ok: true };
619
+ };
620
+
621
+ OPS.set_temporal_ease = function (args) {
622
+ var c = getCompById(args.compId);
623
+ var l = getLayerById(c, args.layerId);
624
+ var prop = walkProperty(l, args.propertyPath);
625
+ __applyInterpolationToKey(prop, args.keyIndex, { easeIn: args.easeIn, easeOut: args.easeOut });
626
+ return { ok: true };
627
+ };
628
+
629
+ OPS.set_spatial_tangents = function (args) {
630
+ var c = getCompById(args.compId);
631
+ var l = getLayerById(c, args.layerId);
632
+ var prop = walkProperty(l, args.propertyPath);
633
+ if (!prop.isSpatial) throw new Error("Property is not spatial");
634
+ prop.setSpatialTangentsAtKey(args.keyIndex, args.inTangent, args.outTangent);
635
+ return { ok: true };
636
+ };
637
+
638
+
639
+ // ===== expressions.jsx =====
640
+
641
+ // expressions.jsx — get/set/toggle/clear expressions on any property.
642
+
643
+ OPS.get_expression = noUndo(function (args) {
644
+ var c = getCompById(args.compId);
645
+ var l = getLayerById(c, args.layerId);
646
+ var prop = walkProperty(l, args.propertyPath);
647
+ return { expression: prop.expression || "", enabled: !!prop.expressionEnabled };
648
+ });
649
+
650
+ OPS.set_expression = function (args) {
651
+ var c = getCompById(args.compId);
652
+ var l = getLayerById(c, args.layerId);
653
+ var prop = walkProperty(l, args.propertyPath);
654
+ prop.expression = args.expression || "";
655
+ prop.expressionEnabled = true;
656
+ return { ok: true };
657
+ };
658
+
659
+ OPS.toggle_expression = function (args) {
660
+ var c = getCompById(args.compId);
661
+ var l = getLayerById(c, args.layerId);
662
+ var prop = walkProperty(l, args.propertyPath);
663
+ prop.expressionEnabled = !!args.enabled;
664
+ return { ok: true };
665
+ };
666
+
667
+ OPS.clear_expression = function (args) {
668
+ var c = getCompById(args.compId);
669
+ var l = getLayerById(c, args.layerId);
670
+ var prop = walkProperty(l, args.propertyPath);
671
+ prop.expression = "";
672
+ return { ok: true };
673
+ };
674
+
675
+
676
+ // ===== effects.jsx =====
677
+
678
+ // effects.jsx — effect graph ops.
679
+
680
+ function __serializeEffect(eff) {
681
+ var out = { name: eff.name, matchName: eff.matchName, enabled: eff.enabled, index: eff.propertyIndex, params: [] };
682
+ for (var i = 1; i <= eff.numProperties; i++) {
683
+ var p = eff.property(i);
684
+ var entry = { name: p.name, matchName: p.matchName, propertyType: String(p.propertyType) };
685
+ try { entry.value = p.value; } catch (e) {}
686
+ if (p.canSetExpression && p.expression) entry.expression = p.expression;
687
+ if (p.numKeys > 0) {
688
+ entry.keyframes = [];
689
+ for (var k = 1; k <= p.numKeys; k++) {
690
+ entry.keyframes.push({ time: p.keyTime(k), value: p.keyValue(k) });
691
+ }
692
+ }
693
+ out.params.push(entry);
694
+ }
695
+ return out;
696
+ }
697
+
698
+ OPS.list_effects = noUndo(function (args) {
699
+ var c = getCompById(args.compId);
700
+ var l = getLayerById(c, args.layerId);
701
+ var fx = l.property("Effects");
702
+ if (!fx) return [];
703
+ var out = [];
704
+ for (var i = 1; i <= fx.numProperties; i++) out.push(__serializeEffect(fx.property(i)));
705
+ return out;
706
+ });
707
+
708
+ OPS.add_effect = function (args) {
709
+ var c = getCompById(args.compId);
710
+ var l = getLayerById(c, args.layerId);
711
+ var fx = l.property("Effects");
712
+ var eff = fx.addProperty(args.matchName);
713
+ return __serializeEffect(eff);
714
+ };
715
+
716
+ OPS.remove_effect = function (args) {
717
+ var c = getCompById(args.compId);
718
+ var l = getLayerById(c, args.layerId);
719
+ var fx = l.property("Effects");
720
+ fx.property(args.effectIndex).remove();
721
+ return { ok: true };
722
+ };
723
+
724
+ OPS.set_effect_param = function (args) {
725
+ var c = getCompById(args.compId);
726
+ var l = getLayerById(c, args.layerId);
727
+ var eff = l.property("Effects").property(args.effectIndex);
728
+ var p = null;
729
+ if (args.paramMatchName) {
730
+ for (var i = 1; i <= eff.numProperties; i++) {
731
+ if (eff.property(i).matchName === args.paramMatchName) { p = eff.property(i); break; }
732
+ }
733
+ }
734
+ if (!p && args.paramName) p = eff.property(args.paramName);
735
+ if (!p) throw new Error("Effect param not found");
736
+ if (args.keyframe && args.time !== undefined) p.setValueAtTime(args.time, args.value);
737
+ else if (args.time !== undefined && p.numKeys > 0) p.setValueAtTime(args.time, args.value);
738
+ else p.setValue(args.value);
739
+ return { ok: true };
740
+ };
741
+
742
+ OPS.set_effect_enabled = function (args) {
743
+ var c = getCompById(args.compId);
744
+ var l = getLayerById(c, args.layerId);
745
+ l.property("Effects").property(args.effectIndex).enabled = !!args.enabled;
746
+ return { ok: true };
747
+ };
748
+
749
+ OPS.list_available_effects = noUndo(function (args) {
750
+ // app.effects is an array of {displayName, matchName, category, version} on modern AE.
751
+ var out = [];
752
+ try {
753
+ var fx = app.effects;
754
+ for (var i = 0; i < fx.length; i++) {
755
+ out.push({ displayName: fx[i].displayName, matchName: fx[i].matchName, category: fx[i].category });
756
+ }
757
+ } catch (e) {
758
+ // Fallback: return a curated subset of common match names.
759
+ out = [
760
+ { displayName: "Gaussian Blur", matchName: "ADBE Gaussian Blur 2", category: "Blur & Sharpen" },
761
+ { displayName: "Fast Box Blur", matchName: "ADBE Box Blur2", category: "Blur & Sharpen" },
762
+ { displayName: "Glow", matchName: "ADBE Glo2", category: "Stylize" },
763
+ { displayName: "Drop Shadow", matchName: "ADBE Drop Shadow", category: "Perspective" },
764
+ { displayName: "Curves", matchName: "ADBE CurvesCustom", category: "Color Correction" },
765
+ { displayName: "Levels", matchName: "ADBE Easy Levels2", category: "Color Correction" },
766
+ { displayName: "Fill", matchName: "ADBE Fill", category: "Generate" },
767
+ { displayName: "CC Light Sweep", matchName: "CC Light Sweep", category: "Generate" },
768
+ ];
769
+ }
770
+ return out;
771
+ });
772
+
773
+
774
+ // ===== text.jsx =====
775
+
776
+ // text.jsx — text layer styling.
777
+
778
+ OPS.set_text = function (args) {
779
+ var c = getCompById(args.compId);
780
+ var l = getLayerById(c, args.layerId);
781
+ if (!(l instanceof TextLayer)) throw new Error("Layer is not a TextLayer");
782
+ var src = l.property("Source Text");
783
+ var td = src.value;
784
+ if (args.text !== undefined) td.text = args.text;
785
+ if (args.font !== undefined) td.font = args.font;
786
+ if (args.size !== undefined) td.fontSize = args.size;
787
+ if (args.fillColor) { td.applyFill = true; td.fillColor = [args.fillColor[0], args.fillColor[1], args.fillColor[2]]; }
788
+ if (args.strokeColor) { td.applyStroke = true; td.strokeColor = [args.strokeColor[0], args.strokeColor[1], args.strokeColor[2]]; }
789
+ if (args.strokeWidth !== undefined) td.strokeWidth = args.strokeWidth;
790
+ if (args.tracking !== undefined) td.tracking = args.tracking;
791
+ if (args.leading !== undefined) td.leading = args.leading;
792
+ if (args.justification !== undefined) {
793
+ var jmap = {
794
+ left: ParagraphJustification.LEFT_JUSTIFY,
795
+ center: ParagraphJustification.CENTER_JUSTIFY,
796
+ right: ParagraphJustification.RIGHT_JUSTIFY,
797
+ full: ParagraphJustification.FULL_JUSTIFY_LASTLINE_LEFT,
798
+ };
799
+ if (jmap[args.justification]) td.justification = jmap[args.justification];
800
+ }
801
+ if (args.applyFill !== undefined) td.applyFill = args.applyFill;
802
+ if (args.applyStroke !== undefined) td.applyStroke = args.applyStroke;
803
+ if (args.fauxBold !== undefined) td.fauxBold = args.fauxBold;
804
+ if (args.fauxItalic !== undefined) td.fauxItalic = args.fauxItalic;
805
+ if (args.allCaps !== undefined) td.allCaps = args.allCaps;
806
+ if (args.smallCaps !== undefined) td.smallCaps = args.smallCaps;
807
+ if (args.baselineShift !== undefined) td.baselineShift = args.baselineShift;
808
+ src.setValue(td);
809
+ return { ok: true };
810
+ };
811
+
812
+ OPS.add_text_animator = function (args) {
813
+ var c = getCompById(args.compId);
814
+ var l = getLayerById(c, args.layerId);
815
+ if (!(l instanceof TextLayer)) throw new Error("Layer is not a TextLayer");
816
+ var anims = l.property("Text").property("Animators");
817
+ var anim = anims.addProperty("ADBE Text Animator");
818
+ var typeMap = {
819
+ position: "ADBE Text Position 3D",
820
+ scale: "ADBE Text Scale 3D",
821
+ rotation: "ADBE Text Rotation",
822
+ opacity: "ADBE Text Opacity",
823
+ tracking: "ADBE Text Tracking Amount",
824
+ skew: "ADBE Text Skew",
825
+ fillColor: "ADBE Text Fill Color",
826
+ strokeColor: "ADBE Text Stroke Color",
827
+ };
828
+ var propsGroup = anim.property("ADBE Text Animator Properties");
829
+ if (typeMap[args.type]) {
830
+ try { propsGroup.addProperty(typeMap[args.type]); }
831
+ catch (e) {}
832
+ }
833
+ if (args.range) {
834
+ var selectors = anim.property("ADBE Text Selectors");
835
+ if (selectors.numProperties === 0) selectors.addProperty("ADBE Text Selector");
836
+ var sel = selectors.property(1);
837
+ if (args.range.start !== undefined) sel.property("ADBE Text Percent Start").setValue(args.range.start);
838
+ if (args.range.end !== undefined) sel.property("ADBE Text Percent End").setValue(args.range.end);
839
+ if (args.range.offset !== undefined) sel.property("ADBE Text Percent Offset").setValue(args.range.offset);
840
+ }
841
+ return { ok: true, animatorName: anim.name };
842
+ };
843
+
844
+
845
+ // ===== shapes.jsx =====
846
+
847
+ // shapes.jsx — shape layer paths/fills/strokes/repeaters.
848
+
849
+ function __makeShape(vertices, inT, outT, closed) {
850
+ var s = new Shape();
851
+ s.vertices = vertices;
852
+ if (inT) s.inTangents = inT;
853
+ if (outT) s.outTangents = outT;
854
+ s.closed = closed !== false;
855
+ return s;
856
+ }
857
+
858
+ OPS.set_shape_path = function (args) {
859
+ var c = getCompById(args.compId);
860
+ var l = getLayerById(c, args.layerId);
861
+ var prop = walkProperty(l, args.shapePath);
862
+ // The shape path is usually a "Path" property whose value is a Shape.
863
+ var pathProp = prop;
864
+ // If user passed a group, dig down to "Path"
865
+ if (pathProp.propertyType === PropertyType.NAMED_GROUP || pathProp.propertyType === PropertyType.INDEXED_GROUP) {
866
+ try { pathProp = pathProp.property("Path"); } catch (e) {}
867
+ }
868
+ var shape = __makeShape(args.vertices, args.inTangents, args.outTangents, args.closed);
869
+ pathProp.setValue(shape);
870
+ return { ok: true };
871
+ };
872
+
873
+ var __SHAPE_MATCH = {
874
+ rect: "ADBE Vector Shape - Rect",
875
+ ellipse: "ADBE Vector Shape - Ellipse",
876
+ star: "ADBE Vector Shape - Star",
877
+ path: "ADBE Vector Shape - Group",
878
+ fill: "ADBE Vector Graphic - Fill",
879
+ stroke: "ADBE Vector Graphic - Stroke",
880
+ trim: "ADBE Vector Filter - Trim",
881
+ repeater: "ADBE Vector Filter - Repeater",
882
+ merge: "ADBE Vector Filter - Merge",
883
+ group: "ADBE Vector Group"
884
+ };
885
+
886
+ // Friendly key -> candidate property identifiers, tried in order. AE's own
887
+ // matchNames are inconsistent (and some are misspelled upstream, e.g. the star
888
+ // "Roundess" keys), so each entry is a list and the raw key is always tried
889
+ // last. A key that resolves to nothing is reported, never silently dropped.
890
+ var __SHAPE_ALIASES = {
891
+ rect: {
892
+ size: ["ADBE Vector Rect Size", "Size"],
893
+ position: ["ADBE Vector Rect Position", "Position"],
894
+ roundness: ["ADBE Vector Rect Roundness", "Roundness"]
895
+ },
896
+ ellipse: {
897
+ size: ["ADBE Vector Ellipse Size", "Size"],
898
+ position: ["ADBE Vector Ellipse Position", "Position"]
899
+ },
900
+ star: {
901
+ starType: ["ADBE Vector Star Type", "Type"],
902
+ points: ["ADBE Vector Star Points", "Points"],
903
+ position: ["ADBE Vector Star Position", "Position"],
904
+ rotation: ["ADBE Vector Star Rotation", "Rotation"],
905
+ innerRadius: ["ADBE Vector Star Inner Radius", "Inner Radius"],
906
+ outerRadius: ["ADBE Vector Star Outer Radius", "Outer Radius"],
907
+ innerRoundness: ["ADBE Vector Star Inner Roundess", "ADBE Vector Star Inner Roundness", "Inner Roundness"],
908
+ outerRoundness: ["ADBE Vector Star Outer Roundess", "ADBE Vector Star Outer Roundness", "Outer Roundness"]
909
+ },
910
+ fill: {
911
+ color: ["ADBE Vector Fill Color", "Color"],
912
+ opacity: ["ADBE Vector Fill Opacity", "Opacity"],
913
+ fillRule: ["ADBE Vector Fill Rule", "Fill Rule"]
914
+ },
915
+ stroke: {
916
+ color: ["ADBE Vector Stroke Color", "Color"],
917
+ opacity: ["ADBE Vector Stroke Opacity", "Opacity"],
918
+ width: ["ADBE Vector Stroke Width", "Stroke Width"],
919
+ lineCap: ["ADBE Vector Stroke Line Cap", "Line Cap"],
920
+ lineJoin: ["ADBE Vector Stroke Line Join", "Line Join"],
921
+ miterLimit: ["ADBE Vector Stroke Miter Limit", "Miter Limit"]
922
+ },
923
+ trim: {
924
+ start: ["ADBE Vector Trim Start", "Start"],
925
+ end: ["ADBE Vector Trim End", "End"],
926
+ offset: ["ADBE Vector Trim Offset", "Offset"]
927
+ },
928
+ repeater: {
929
+ copies: ["ADBE Vector Repeater Copies", "Copies"],
930
+ offset: ["ADBE Vector Repeater Offset", "Offset"]
931
+ },
932
+ merge: {
933
+ mode: ["ADBE Vector Merge Type", "Mode"]
934
+ }
935
+ };
936
+
937
+ // Keys consumed by the path builder rather than set as plain properties.
938
+ var __PATH_KEYS = { vertices: 1, points: 1, inTangents: 1, outTangents: 1, closed: 1 };
939
+
940
+ function __resolveShapeProp(node, type, key) {
941
+ var candidates = [];
942
+ var table = __SHAPE_ALIASES[type];
943
+ if (table && table[key]) {
944
+ var aliases = table[key];
945
+ for (var i = 0; i < aliases.length; i++) candidates.push(aliases[i]);
946
+ }
947
+ candidates.push(key);
948
+ for (var j = 0; j < candidates.length; j++) {
949
+ try {
950
+ var pp = node.property(candidates[j]);
951
+ if (pp) return pp;
952
+ } catch (e) {}
953
+ }
954
+ return null;
955
+ }
956
+
957
+ function __shapeTypeList() {
958
+ var names = [];
959
+ for (var k in __SHAPE_MATCH) { if (__SHAPE_MATCH.hasOwnProperty(k)) names.push(k); }
960
+ return names.join(", ");
961
+ }
962
+
963
+ OPS.add_shape_content = function (args) {
964
+ var c = getCompById(args.compId);
965
+ var l = getLayerById(c, args.layerId);
966
+ var parent = l.property("Contents");
967
+ if (args.parentGroupPath && args.parentGroupPath.length > 0) {
968
+ parent = walkProperty(l, args.parentGroupPath);
969
+ }
970
+ var content = args.content || {};
971
+ var type = content.type;
972
+ var match = __SHAPE_MATCH[type];
973
+ if (!match) {
974
+ throw new Error("Unknown shape content type: " + String(type) + ". Expected one of: " + __shapeTypeList() + ".");
975
+ }
976
+
977
+ var node = parent.addProperty(match);
978
+ var applied = [];
979
+ var failed = [];
980
+
981
+ try {
982
+ // A "path" node is a Vector Group whose Path property holds a Shape; the
983
+ // vertex keys have to be folded into one setValue rather than set directly.
984
+ if (type === "path" && (content.vertices || content.points)) {
985
+ var verts = content.vertices || content.points;
986
+ var pathProp = __resolveShapeProp(node, "path", "ADBE Vector Shape");
987
+ if (!pathProp) pathProp = __resolveShapeProp(node, "path", "Path");
988
+ if (!pathProp) {
989
+ failed.push("vertices (no Path property on the created group)");
990
+ } else {
991
+ pathProp.setValue(__makeShape(verts, content.inTangents, content.outTangents, content.closed));
992
+ applied.push("vertices");
993
+ }
994
+ }
995
+
996
+ for (var k in content) {
997
+ if (!content.hasOwnProperty(k)) continue;
998
+ if (k === "type") continue;
999
+ if (type === "path" && __PATH_KEYS[k]) continue;
1000
+ // `name` is a node attribute, not a child property.
1001
+ if (k === "name") {
1002
+ node.name = String(content[k]);
1003
+ applied.push("name");
1004
+ continue;
1005
+ }
1006
+ var target = __resolveShapeProp(node, type, k);
1007
+ if (!target) { failed.push(k); continue; }
1008
+ try {
1009
+ target.setValue(content[k]);
1010
+ applied.push(k);
1011
+ } catch (e) {
1012
+ failed.push(k + " (" + e.message + ")");
1013
+ }
1014
+ }
1015
+ } catch (e) {
1016
+ try { node.remove(); } catch (e2) {}
1017
+ throw e;
1018
+ }
1019
+
1020
+ // All-or-nothing: a partially built node that reports success is worse than a
1021
+ // clear failure, because the caller cannot tell what actually landed.
1022
+ if (failed.length > 0) {
1023
+ try { node.remove(); } catch (e3) {}
1024
+ throw new Error(
1025
+ "add_shape_content could not apply these keys on '" + type + "': " + failed.join(", ") +
1026
+ ". The node was removed, so nothing changed. Check the property names with get_layer_full, " +
1027
+ "or set them afterwards with set_shape_property."
1028
+ );
1029
+ }
1030
+
1031
+ return { ok: true, name: node.name, matchName: match, index: node.propertyIndex, applied: applied };
1032
+ };
1033
+
1034
+ OPS.set_shape_property = function (args) {
1035
+ var c = getCompById(args.compId);
1036
+ var l = getLayerById(c, args.layerId);
1037
+ var node = walkProperty(l, args.contentPath);
1038
+ var pp = node.property(args.property);
1039
+ if (!pp) throw new Error("No property: " + args.property);
1040
+ if (args.keyframe && args.time !== undefined) pp.setValueAtTime(args.time, args.value);
1041
+ else if (args.time !== undefined && pp.numKeys > 0) pp.setValueAtTime(args.time, args.value);
1042
+ else pp.setValue(args.value);
1043
+ return { ok: true };
1044
+ };
1045
+
1046
+
1047
+ // ===== masks.jsx =====
1048
+
1049
+ // masks.jsx — layer mask ops.
1050
+
1051
+ OPS.add_mask = function (args) {
1052
+ var c = getCompById(args.compId);
1053
+ var l = getLayerById(c, args.layerId);
1054
+ var masksGroup = l.property("Masks");
1055
+ var m = masksGroup.addProperty("ADBE Mask Atom");
1056
+ var maskPath = m.property("ADBE Mask Shape");
1057
+ var shape = new Shape();
1058
+ shape.vertices = args.vertices;
1059
+ if (args.inTangents) shape.inTangents = args.inTangents;
1060
+ if (args.outTangents) shape.outTangents = args.outTangents;
1061
+ shape.closed = args.closed !== false;
1062
+ maskPath.setValue(shape);
1063
+ if (args.mode) {
1064
+ try { m.maskMode = MaskMode[args.mode] || m.maskMode; } catch (e) {}
1065
+ }
1066
+ return { ok: true, maskIndex: m.propertyIndex };
1067
+ };
1068
+
1069
+ OPS.set_mask = function (args) {
1070
+ var c = getCompById(args.compId);
1071
+ var l = getLayerById(c, args.layerId);
1072
+ var m = l.property("Masks").property(args.maskIndex);
1073
+ if (args.vertices) {
1074
+ var pathProp = m.property("ADBE Mask Shape");
1075
+ var sh = pathProp.value;
1076
+ if (args.vertices) sh.vertices = args.vertices;
1077
+ if (args.inTangents) sh.inTangents = args.inTangents;
1078
+ if (args.outTangents) sh.outTangents = args.outTangents;
1079
+ if (args.closed !== undefined) sh.closed = args.closed;
1080
+ pathProp.setValue(sh);
1081
+ }
1082
+ if (args.mode) { try { m.maskMode = MaskMode[args.mode] || m.maskMode; } catch (e) {} }
1083
+ if (args.inverted !== undefined) m.inverted = args.inverted;
1084
+ if (args.expansion !== undefined) m.property("ADBE Mask Offset").setValue(args.expansion);
1085
+ if (args.feather !== undefined) m.property("ADBE Mask Feather").setValue(args.feather);
1086
+ if (args.opacity !== undefined) m.property("ADBE Mask Opacity").setValue(args.opacity);
1087
+ return { ok: true };
1088
+ };
1089
+
1090
+ OPS.remove_mask = function (args) {
1091
+ var c = getCompById(args.compId);
1092
+ var l = getLayerById(c, args.layerId);
1093
+ l.property("Masks").property(args.maskIndex).remove();
1094
+ return { ok: true };
1095
+ };
1096
+
1097
+
1098
+ // ===== markers.jsx =====
1099
+
1100
+ // markers.jsx — comp markers and layer markers.
1101
+
1102
+ function __mkMarkerValue(args) {
1103
+ var mv = new MarkerValue(args.comment || "");
1104
+ if (args.duration !== undefined) mv.duration = args.duration;
1105
+ if (args.label !== undefined) mv.label = args.label;
1106
+ if (args.chapter) mv.chapter = args.chapter;
1107
+ if (args.url) mv.url = args.url;
1108
+ if (args.frameTarget) mv.frameTarget = args.frameTarget;
1109
+ return mv;
1110
+ }
1111
+
1112
+ OPS.add_marker = function (args) {
1113
+ var c = getCompById(args.compId);
1114
+ var mv = __mkMarkerValue(args);
1115
+ if (args.layerId !== undefined && args.layerId !== null) {
1116
+ var l = getLayerById(c, args.layerId);
1117
+ var mProp = l.property("Marker");
1118
+ mProp.setValueAtTime(args.time, mv);
1119
+ } else {
1120
+ var compMarkers = c.markerProperty;
1121
+ compMarkers.setValueAtTime(args.time, mv);
1122
+ }
1123
+ return { ok: true };
1124
+ };
1125
+
1126
+ OPS.remove_marker = function (args) {
1127
+ var c = getCompById(args.compId);
1128
+ if (args.layerId !== undefined && args.layerId !== null) {
1129
+ var l = getLayerById(c, args.layerId);
1130
+ l.property("Marker").removeKey(args.markerIndex);
1131
+ } else {
1132
+ c.markerProperty.removeKey(args.markerIndex);
1133
+ }
1134
+ return { ok: true };
1135
+ };
1136
+
1137
+
1138
+ // ===== vision.jsx =====
1139
+
1140
+ // vision.jsx — saveFrameToPng wrapper, returns the temp file path which the
1141
+ // panel then reads and base64-encodes.
1142
+
1143
+ function __tmpPngPath() {
1144
+ var folder = Folder.temp;
1145
+ var name = "ae-mcp-" + (new Date().getTime()) + "-" + Math.floor(Math.random() * 1e6) + ".png";
1146
+ return folder.fsName + "/" + name;
1147
+ }
1148
+
1149
+ OPS.screenshot_frame = noUndo(function (args) {
1150
+ var c = getCompById(args.compId);
1151
+ var t = (args.time !== undefined && args.time !== null) ? args.time : c.time;
1152
+ var path = __tmpPngPath();
1153
+ var f = new File(path);
1154
+ // saveFrameToPng is async-ish; the panel polls the file's existence/size.
1155
+ c.saveFrameToPng(t, f);
1156
+ return { path: path, width: c.width, height: c.height, time: t, compId: c.id };
1157
+ });
1158
+
1159
+ OPS.screenshot_layer = noUndo(function (args) {
1160
+ var c = getCompById(args.compId);
1161
+ var l = getLayerById(c, args.layerId);
1162
+ var t = (args.time !== undefined && args.time !== null) ? args.time : c.time;
1163
+ // Capture all current solo states; solo target; capture; restore.
1164
+ var prevSolo = [];
1165
+ for (var i = 1; i <= c.numLayers; i++) {
1166
+ var ll = c.layer(i);
1167
+ prevSolo.push({ idx: i, solo: ll.solo });
1168
+ ll.solo = false;
1169
+ }
1170
+ l.solo = true;
1171
+ var path = __tmpPngPath();
1172
+ var f = new File(path);
1173
+ try {
1174
+ c.saveFrameToPng(t, f);
1175
+ } finally {
1176
+ // restore
1177
+ l.solo = false;
1178
+ for (var j = 0; j < prevSolo.length; j++) {
1179
+ try { c.layer(prevSolo[j].idx).solo = prevSolo[j].solo; } catch (e) {}
1180
+ }
1181
+ }
1182
+ return { path: path, width: c.width, height: c.height, time: t, compId: c.id, layerId: l.id };
1183
+ });
1184
+
1185
+
1186
+ // ===== batch.jsx =====
1187
+
1188
+ // batch.jsx — execute many ops in one ExtendScript pass.
1189
+ // For huge batches, registers a job and processes in chunks via _continue_job.
1190
+
1191
+ OPS.run_batch = function (args) {
1192
+ var ops = args.ops || [];
1193
+ var transactional = args.transactional !== false;
1194
+ var name = args.undoGroupName || "AE MCP Batch";
1195
+ // Short batches: run inline synchronously. Inline stays sub-second for
1196
+ // typical create/keyframe ops up to a few hundred; the async-job overhead
1197
+ // (jobId envelope, polling, progress notifications) is only worth it for
1198
+ // genuinely long jobs.
1199
+ if (ops.length <= 500) {
1200
+ var results = []; var errors = [];
1201
+ for (var i = 0; i < ops.length; i++) {
1202
+ var step = ops[i];
1203
+ try {
1204
+ var handler = OPS[step.op];
1205
+ if (!handler) throw new Error("Unknown op: " + step.op);
1206
+ results.push(handler(step.args || {}));
1207
+ } catch (e) {
1208
+ errors.push({ index: i, op: step.op, error: e.message });
1209
+ if (transactional) throw new Error("Batch failed at op[" + i + "] " + step.op + ": " + e.message);
1210
+ }
1211
+ }
1212
+ return { results: results, errors: errors, total: ops.length };
1213
+ }
1214
+ // Long batches: register job, return jobId; panel polls _continue_job.
1215
+ var jobId = __newJobId();
1216
+ JOBS[jobId] = {
1217
+ cursor: 0,
1218
+ total: ops.length,
1219
+ ops: ops,
1220
+ results: [],
1221
+ errors: [],
1222
+ cancelled: false,
1223
+ transactional: transactional,
1224
+ name: name,
1225
+ };
1226
+ // Wrap async run already inside an undoGroup chain. We open it now, the
1227
+ // continuations stay inside it until finalization.
1228
+ app.beginUndoGroup(name);
1229
+ JOBS[jobId].undoOpen = true;
1230
+ return { jobId: jobId, async: true, total: ops.length };
1231
+ };
1232
+ // run_batch is allowed to manage its own undo (we open/close it manually for long jobs).
1233
+ OPS.run_batch.__meta = { noUndo: true };
1234
+
1235
+ // Continuation step. Returns one chunk's worth of progress.
1236
+ OPS._continue_job = noUndo(function (args) {
1237
+ var jobId = args.jobId;
1238
+ var j = JOBS[jobId];
1239
+ if (!j) throw new Error("No job: " + jobId);
1240
+ if (j.cancelled) {
1241
+ if (j.undoOpen) { app.endUndoGroup(); j.undoOpen = false; }
1242
+ return { done: true, cancelled: true, jobId: jobId, results: j.results, errors: j.errors };
1243
+ }
1244
+ var chunkSize = args.chunkSize || 25;
1245
+ var endAt = Math.min(j.cursor + chunkSize, j.total);
1246
+ for (; j.cursor < endAt; j.cursor++) {
1247
+ var step = j.ops[j.cursor];
1248
+ try {
1249
+ var handler = OPS[step.op];
1250
+ if (!handler) throw new Error("Unknown op: " + step.op);
1251
+ j.results.push(handler(step.args || {}));
1252
+ } catch (e) {
1253
+ j.errors.push({ index: j.cursor, op: step.op, error: e.message });
1254
+ if (j.transactional) {
1255
+ if (j.undoOpen) { app.endUndoGroup(); j.undoOpen = false; }
1256
+ // Attempt rollback via undo
1257
+ try { app.executeCommand(app.findMenuCommandId("Undo")); } catch (e2) {}
1258
+ return { done: true, failed: true, jobId: jobId, error: e.message, atIndex: j.cursor, results: j.results, errors: j.errors };
1259
+ }
1260
+ }
1261
+ }
1262
+ if (j.cursor >= j.total) {
1263
+ if (j.undoOpen) { app.endUndoGroup(); j.undoOpen = false; }
1264
+ return { done: true, jobId: jobId, results: j.results, errors: j.errors, total: j.total };
1265
+ }
1266
+ return { done: false, jobId: jobId, progress: j.cursor, total: j.total };
1267
+ });
1268
+
1269
+ OPS._cancel_job = noUndo(function (args) {
1270
+ var j = JOBS[args.jobId];
1271
+ if (!j) return { ok: false, error: "No such job" };
1272
+ j.cancelled = true;
1273
+ return { ok: true };
1274
+ });
1275
+
1276
+ OPS._get_job = noUndo(function (args) {
1277
+ var j = JOBS[args.jobId];
1278
+ if (!j) return null;
1279
+ return { cursor: j.cursor, total: j.total, cancelled: j.cancelled, errorCount: j.errors.length };
1280
+ });
1281
+
1282
+
1283
+ // ===== explore.jsx =====
1284
+
1285
+ // explore.jsx — rich one-shot inspection. The whole reason this MCP exists.
1286
+
1287
+ function __serializeProperty(p, deep) {
1288
+ var out = {
1289
+ name: p.name,
1290
+ matchName: p.matchName,
1291
+ propertyType: String(p.propertyType),
1292
+ isTimeVarying: p.isTimeVarying,
1293
+ canSetExpression: p.canSetExpression,
1294
+ };
1295
+ try { out.value = p.value; } catch (e) {}
1296
+ if (p.canSetExpression && p.expression) out.expression = p.expression;
1297
+ if (p.numKeys > 0) {
1298
+ out.keyframes = [];
1299
+ for (var k = 1; k <= p.numKeys; k++) {
1300
+ var entry = { index: k, time: p.keyTime(k), value: p.keyValue(k) };
1301
+ try {
1302
+ entry["in"] = String(p.keyInInterpolationType(k));
1303
+ entry["out"] = String(p.keyOutInterpolationType(k));
1304
+ } catch (e1) {}
1305
+ try {
1306
+ var inE = p.keyInTemporalEase(k);
1307
+ var outE = p.keyOutTemporalEase(k);
1308
+ entry.easeIn = { influence: inE[0].influence, speed: inE[0].speed };
1309
+ entry.easeOut = { influence: outE[0].influence, speed: outE[0].speed };
1310
+ } catch (e2) {}
1311
+ if (p.isSpatial) {
1312
+ try {
1313
+ entry.inTangent = p.keyInSpatialTangent(k);
1314
+ entry.outTangent = p.keyOutSpatialTangent(k);
1315
+ } catch (e3) {}
1316
+ }
1317
+ out.keyframes.push(entry);
1318
+ }
1319
+ }
1320
+ return out;
1321
+ }
1322
+
1323
+ function __serializeTransformGroup(tg) {
1324
+ var out = {};
1325
+ for (var i = 1; i <= tg.numProperties; i++) {
1326
+ var p = tg.property(i);
1327
+ out[p.name] = __serializeProperty(p);
1328
+ }
1329
+ return out;
1330
+ }
1331
+
1332
+ function __serializeEffects(layer) {
1333
+ var fx = layer.property("Effects");
1334
+ if (!fx || fx.numProperties === 0) return [];
1335
+ var arr = [];
1336
+ for (var i = 1; i <= fx.numProperties; i++) arr.push(__serializeEffect(fx.property(i)));
1337
+ return arr;
1338
+ }
1339
+
1340
+ function __serializeMasks(layer) {
1341
+ var masks = layer.property("Masks");
1342
+ if (!masks || masks.numProperties === 0) return [];
1343
+ var out = [];
1344
+ for (var i = 1; i <= masks.numProperties; i++) {
1345
+ var m = masks.property(i);
1346
+ var entry = {
1347
+ index: i,
1348
+ name: m.name,
1349
+ mode: String(m.maskMode),
1350
+ inverted: m.inverted,
1351
+ };
1352
+ try { entry.shape = __serializeProperty(m.property("ADBE Mask Shape")); } catch (e1) {}
1353
+ try { entry.opacity = __serializeProperty(m.property("ADBE Mask Opacity")); } catch (e2) {}
1354
+ try { entry.expansion = __serializeProperty(m.property("ADBE Mask Offset")); } catch (e3) {}
1355
+ try { entry.feather = __serializeProperty(m.property("ADBE Mask Feather")); } catch (e4) {}
1356
+ out.push(entry);
1357
+ }
1358
+ return out;
1359
+ }
1360
+
1361
+ function __serializeMarkers(layer) {
1362
+ var mp = layer.property("Marker");
1363
+ if (!mp || mp.numKeys === 0) return [];
1364
+ var out = [];
1365
+ for (var i = 1; i <= mp.numKeys; i++) {
1366
+ var mv = mp.keyValue(i);
1367
+ out.push({
1368
+ index: i,
1369
+ time: mp.keyTime(i),
1370
+ comment: mv.comment,
1371
+ duration: mv.duration,
1372
+ label: mv.label,
1373
+ chapter: mv.chapter,
1374
+ url: mv.url,
1375
+ frameTarget: mv.frameTarget,
1376
+ });
1377
+ }
1378
+ return out;
1379
+ }
1380
+
1381
+ function __serializeText(layer) {
1382
+ try {
1383
+ var td = layer.property("Source Text").value;
1384
+ return {
1385
+ text: td.text,
1386
+ font: td.font,
1387
+ fontSize: td.fontSize,
1388
+ fillColor: td.applyFill ? [td.fillColor[0], td.fillColor[1], td.fillColor[2]] : null,
1389
+ strokeColor: td.applyStroke ? [td.strokeColor[0], td.strokeColor[1], td.strokeColor[2]] : null,
1390
+ strokeWidth: td.strokeWidth,
1391
+ tracking: td.tracking,
1392
+ leading: td.leading,
1393
+ justification: String(td.justification),
1394
+ fauxBold: td.fauxBold,
1395
+ fauxItalic: td.fauxItalic,
1396
+ allCaps: td.allCaps,
1397
+ smallCaps: td.smallCaps,
1398
+ };
1399
+ } catch (e) { return null; }
1400
+ }
1401
+
1402
+ function __serializeShapeContents(group, depth) {
1403
+ if (!group || !group.numProperties) return [];
1404
+ var out = [];
1405
+ for (var i = 1; i <= group.numProperties; i++) {
1406
+ var p = group.property(i);
1407
+ var entry = { name: p.name, matchName: p.matchName, index: i };
1408
+ if (p.propertyType === PropertyType.NAMED_GROUP || p.propertyType === PropertyType.INDEXED_GROUP) {
1409
+ if (depth > 0) entry.children = __serializeShapeContents(p, depth - 1);
1410
+ } else {
1411
+ try { entry.value = p.value; } catch (e) {}
1412
+ }
1413
+ out.push(entry);
1414
+ }
1415
+ return out;
1416
+ }
1417
+
1418
+ OPS.get_layer_full = noUndo(function (args) {
1419
+ var c = getCompById(args.compId);
1420
+ var l = getLayerById(c, args.layerId);
1421
+ var out = {
1422
+ id: l.id,
1423
+ index: l.index,
1424
+ name: l.name,
1425
+ enabled: l.enabled,
1426
+ solo: l.solo,
1427
+ locked: l.locked,
1428
+ shy: l.shy,
1429
+ threeDLayer: l.threeDLayer,
1430
+ label: l.label,
1431
+ inPoint: l.inPoint,
1432
+ outPoint: l.outPoint,
1433
+ startTime: l.startTime,
1434
+ stretch: l.stretch,
1435
+ blendingMode: String(l.blendingMode),
1436
+ preserveTransparency: l.preserveTransparency,
1437
+ parent: l.parent ? { layerId: l.parent.id, name: l.parent.name } : null,
1438
+ sourceType: __layerKind(l),
1439
+ transform: __serializeTransformGroup(l.property("Transform")),
1440
+ effects: __serializeEffects(l),
1441
+ masks: __serializeMasks(l),
1442
+ markers: __serializeMarkers(l),
1443
+ };
1444
+ // Visual bounds at the comp's current time — cheap to fetch and removes a
1445
+ // class of "I need to screenshot to know where this renders" round-trips.
1446
+ // Coordinates are in the layer's local space (origin at the Anchor Point).
1447
+ try {
1448
+ var __rect = l.sourceRectAtTime(c.time, false);
1449
+ out.sourceRect = { left: __rect.left, top: __rect.top, width: __rect.width, height: __rect.height, time: c.time };
1450
+ } catch (__e) {}
1451
+ if (l instanceof TextLayer) out.text = __serializeText(l);
1452
+ if (l instanceof ShapeLayer) {
1453
+ try { out.shape = { contents: __serializeShapeContents(l.property("Contents"), 4) }; }
1454
+ catch (e) {}
1455
+ }
1456
+ if (l.source && l.source instanceof CompItem) {
1457
+ out.precomp = { compId: l.source.id, compName: l.source.name };
1458
+ if (args.includeChildren) {
1459
+ out.children = [];
1460
+ for (var i = 1; i <= l.source.numLayers; i++) out.children.push(__layerSummary(l.source.layer(i)));
1461
+ }
1462
+ } else if (l.source && l.source instanceof FootageItem) {
1463
+ var src = l.source;
1464
+ out.footage = {
1465
+ itemId: src.id,
1466
+ name: src.name,
1467
+ hasAlpha: src.hasAlpha,
1468
+ duration: src.duration,
1469
+ width: src.width,
1470
+ height: src.height,
1471
+ };
1472
+ try { if (src.file) out.footage.path = src.file.fsName; } catch (e2) {}
1473
+ }
1474
+ return out;
1475
+ });
1476
+
1477
+ OPS.get_project_summary = noUndo(function (args) {
1478
+ var p = app.project;
1479
+ var items = [];
1480
+ for (var i = 1; i <= p.numItems; i++) {
1481
+ var it = p.item(i);
1482
+ items.push({
1483
+ id: it.id,
1484
+ name: it.name,
1485
+ type: (it instanceof CompItem) ? "comp" : (it instanceof FootageItem) ? "footage" : (it instanceof FolderItem) ? "folder" : "unknown",
1486
+ });
1487
+ }
1488
+ return {
1489
+ path: p.file ? p.file.fsName : null,
1490
+ numItems: p.numItems,
1491
+ activeItemId: p.activeItem ? p.activeItem.id : null,
1492
+ items: items,
1493
+ };
1494
+ });
1495
+
1496
+ OPS.find_layers = noUndo(function (args) {
1497
+ var out = [];
1498
+ var comps = [];
1499
+ if (args.compId) comps.push(getCompById(args.compId));
1500
+ else {
1501
+ for (var i = 1; i <= app.project.numItems; i++) {
1502
+ var it = app.project.item(i);
1503
+ if (it instanceof CompItem) comps.push(it);
1504
+ }
1505
+ }
1506
+ var pat = args.namePattern ? new RegExp(args.namePattern, "i") : null;
1507
+ for (var ci = 0; ci < comps.length; ci++) {
1508
+ var c = comps[ci];
1509
+ for (var li = 1; li <= c.numLayers; li++) {
1510
+ var l = c.layer(li);
1511
+ if (pat && !pat.test(l.name)) continue;
1512
+ if (args.type && __layerKind(l) !== args.type) continue;
1513
+ if (args.hasEffectMatchName) {
1514
+ var fx = l.property("Effects");
1515
+ var hit = false;
1516
+ if (fx) {
1517
+ for (var fi = 1; fi <= fx.numProperties; fi++) {
1518
+ if (fx.property(fi).matchName === args.hasEffectMatchName) { hit = true; break; }
1519
+ }
1520
+ }
1521
+ if (!hit) continue;
1522
+ }
1523
+ var s = __layerSummary(l);
1524
+ s.compId = c.id;
1525
+ s.compName = c.name;
1526
+ out.push(s);
1527
+ }
1528
+ }
1529
+ return out;
1530
+ });
1531
+
1532
+
1533
+ // ===== raw.jsx =====
1534
+
1535
+ // raw.jsx — escape hatch. Eval arbitrary ExtendScript and return the value.
1536
+
1537
+ OPS.run_jsx = function (args) {
1538
+ var code = args.code || "";
1539
+ // We wrap in a function so `return` works.
1540
+ var wrapper = "(function(){ " + code + " })()";
1541
+ var result;
1542
+ try { result = eval(wrapper); }
1543
+ catch (e) { throw e; }
1544
+ // ExtendScript objects can be unserializable; coerce sparingly.
1545
+ if (typeof result === "undefined") return null;
1546
+ if (result === null) return null;
1547
+ var t = typeof result;
1548
+ if (t === "number" || t === "string" || t === "boolean") return result;
1549
+ if (result instanceof Array) return result;
1550
+ // Object: pull plain own props
1551
+ try {
1552
+ var out = {};
1553
+ for (var k in result) {
1554
+ if (result.hasOwnProperty(k)) {
1555
+ var v = result[k];
1556
+ var vt = typeof v;
1557
+ if (v === null || vt === "number" || vt === "string" || vt === "boolean") out[k] = v;
1558
+ }
1559
+ }
1560
+ return out;
1561
+ } catch (e2) { return String(result); }
1562
+ };