بنقرة واحدة
jsx-script-creator
// Create ExtendScript/JSX files that integrate with the Python-to-AE file-based command bridge
// Create ExtendScript/JSX files that integrate with the Python-to-AE file-based command bridge
| name | jsx-script-creator |
| description | Create ExtendScript/JSX files that integrate with the Python-to-AE file-based command bridge |
This skill covers creating .jsx ExtendScript files for the After Effects automation bridge. Scripts live in ae_automation/mixins/js/ and are executed by Python's runScript() method.
Python's runScript() in ae_automation/mixins/afterEffect.py does the following before sending a script to AE:
.jsx file from ae_automation/mixins/js/_remplacements dictjson2.js + framework.js (all utility functions are available)try { ... } catch(e) { _error = e.lineNumber + ' ' + e.toString(); } outputLogs(_error);Critical implication: Your scripts must NOT include top-level try/catch or error handling. The wrapper handles that. Your script body is inserted directly into the try block.
Use this format at the top of every new JSX file:
//
// <Descriptive Title>
// ------------------------------------------------------------
// Language: javascript
//
For simpler utility scripts, a shorter header is acceptable:
// <One-line description>
// Parameters: {param1}, {param2}, ...
Placeholders use {name} notation. The distinction between quoted and bare is essential:
"{paramName}"var comp = FindItemByName("{comp_name}");
var layer = FindLayerByComp("{comp_name}", "{layer_name}");
saveFile("{output_file}", JSON.stringify(data));
When Python does fileContent.replace("{comp_name}", "MyComp"), the result is "MyComp" -- a valid JS string literal.
{paramName}var c = app.project.items.addComp("{compName}", {compWidth}, {compHeight}, {pixelAspect}, {duration}, {frameRate});
layer.startTime = {startTime};
layer.stretch = {stretch};
When Python replaces {compWidth} with 1920, the result is the bare number 1920 -- a valid JS number literal.
"{param}"{param}"{param}" and parse at runtime with valueParser()Keys in the _remplacements dict must include the braces:
self.runScript("my_script.jsx", {
"{comp_name}": str(comp_name),
"{layer_name}": str(layer_name),
"{startTime}": str(start_time), # str() even for numbers -- it's string replacement
"{duration}": str(duration),
})
These functions are automatically available in every script (prepended by runScript()):
| Function | Signature | Description |
|---|---|---|
FindItemByName | FindItemByName(name) | Returns the AE project item matching name, or null |
FindItemIdByName | FindItemIdByName(name) | Returns 1-based index of item matching name, or null |
FindLayerByComp | FindLayerByComp(compName, layerName) | Finds a layer by name within a named composition |
FindLayerByLayerIndex | FindLayerByLayerIndex(compName, layerIndex) | Finds a layer by 1-based index within a named composition |
slugify | slugify(str) | Converts string to URL-safe lowercase slug |
saveFile | saveFile(fileName, fileContent) | Writes content to a file in CACHE_FOLDER |
print | print(log, log2, log3) | Appends timestamped log entry (up to 3 args) to internal log buffer |
outputLogs | outputLogs(finalLog, debug) | Writes accumulated logs to .log file (called automatically by wrapper) |
decodeHTMLEntities | decodeHTMLEntities(text) | Decodes &, <, >, ", ', ,, |
propertyParser | propertyParser(property, propertyName) | Traverses AE property hierarchy by splitting propertyName on . |
valueParser | valueParser(propertyValue) | Parses string value: detects [x,y,z] arrays or decodes HTML entities |
deselectAll | deselectAll() | Deselects all items in the AE project panel |
deselectAllLayers | deselectAllLayers() | Deselects all layers in the active composition |
CACHE_FODLER -- Path to cache directory (note: typo is intentional, do not fix)LOG_OUTPUT -- Boolean flag for log output_LOGS -- Internal log accumulator stringAfter Effects uses 1-based indexing everywhere:
// Project items: app.project.item(1) is the first item
for (var i = 1; i <= app.project.numItems; i++) {
var item = app.project.item(i);
}
// Layers: comp.layer(1) is the topmost layer
for (var i = 1; i <= comp.numLayers; i++) {
var layer = comp.layer(i);
}
New layers are always added at index 1 (topmost position in the layer stack).
To send data back from AE to Python:
var result = {};
result.items = [];
for (var i = 1; i <= app.project.numItems; i++) {
result.items.push({ name: app.project.item(i).name, index: i });
}
saveFile("output.json", JSON.stringify(result));
import json
from ae_automation import settings
random_name = self.runScript("my_script.jsx", replacements)
time.sleep(2) # wait for AE to finish
output_path = os.path.join(settings.CACHE_FOLDER, "output.json")
with open(output_path, "r") as f:
data = json.load(f)
//
// Update Layer Opacity
// ------------------------------------------------------------
// Language: javascript
//
function updateOpacity(compName, layerName, opacity) {
var comp = FindItemByName(compName);
if (!comp) {
print("Comp not found: " + compName);
return;
}
var layer = FindLayerByComp(compName, layerName);
if (!layer) {
print("Layer not found: " + layerName);
return;
}
layer.opacity.setValue(opacity);
print("Set opacity to " + opacity);
}
updateOpacity("{comp_name}", "{layer_name}", {opacity});
//
// Select Item By Name
// ------------------------------------------------------------
// Language: javascript
//
deselectAll();
var _id = FindItemIdByName("{itemName}");
if (_id !== null) {
app.project.item(_id).selected = true;
print("Selected: {itemName}");
}
After Effects ExtendScript uses ECMAScript 3. The following modern JS features are NOT available:
| Forbidden | Use instead |
|---|---|
let / const | var |
Arrow functions () => {} | function() {} |
Template literals `${x}` | String concatenation "" + x |
Array.forEach() | for loop with index |
Array.map() / filter() / reduce() | Manual for loop |
Array.indexOf() (unreliable) | Manual for loop comparison |
Object.keys() | for (var k in obj) with hasOwnProperty |
JSON.parse() / JSON.stringify() | Available via json2.js (auto-included) |
=== / !== | Works, but be aware ExtendScript has quirks |
try/catch at top level | Handled by wrapper -- do NOT add your own |
| Default parameters | Check with if (param === undefined) |
Here is a complete example of a script that collects composition info:
//
// Get Composition Details
// ------------------------------------------------------------
// Language: javascript
//
function getCompDetails(compName) {
var comp = FindItemByName(compName);
if (!comp) {
print("Composition not found: " + compName);
return;
}
var details = {
name: comp.name,
width: comp.width,
height: comp.height,
duration: comp.duration,
frameRate: comp.frameRate,
numLayers: comp.numLayers,
layers: []
};
for (var i = 1; i <= comp.numLayers; i++) {
var layer = comp.layer(i);
details.layers.push({
index: i,
name: layer.name,
inPoint: layer.inPoint,
outPoint: layer.outPoint,
enabled: layer.enabled
});
}
saveFile("comp_details.json", JSON.stringify(details));
print("Saved details for " + compName + " with " + comp.numLayers + " layers");
}
getCompDetails("{comp_name}");
And the Python side to call it:
def getCompDetails(self, comp_name):
self.runScript("get_comp_details.jsx", {
"{comp_name}": str(comp_name),
})
time.sleep(2)
output_path = os.path.join(settings.CACHE_FOLDER, "comp_details.json")
with open(output_path, "r") as f:
return json.load(f)
Manage After Effects from Claude — create configs, build compositions, render videos, diagnose issues, and optionally generate AI content via Prompture
Create and validate JSON automation configs for the After Effects automation pipeline
Add methods to existing mixins or create new mixins for the After Effects automation client
Write unit tests following the project's existing patterns and conventions