Why Node.js for After Effects?
- Server-Side Rendering: Imagine running your node.js server, equipped with After Effects, and executing render commands directly from the server. This opens up possibilities for automated, server-side video production pipelines.
- Node.js Local Preference: If you’re a node.js enthusiast working locally, you might prefer to bypass the ExtendScript toolkit for running After Effects scripts. Node.js offers a familiar and powerful environment.
- Modern JavaScript Syntax (ES6): Dive into creating and running After Effects scripts using the latest ES6 syntax. Embrace modern JavaScript features for more efficient and maintainable scripting.
- Rebellion (Optional): Okay, maybe not for rebellion, but for innovation and pushing the boundaries of After Effects automation!
Essential Requirements
Before you begin, ensure After Effects is installed on your system. Crucially, you need to enable script access within After Effects preferences:
Preferences -> General -> Allow Scripts to Write Files and Access Network
This setting is vital for Node.js to interact with After Effects and execute commands effectively.
Getting Started: Basic Usage
Initiating the after-effects
module in your Node.js project is straightforward:
var ae = require("after-effects");
With this single line, you’ve imported the module, ready to harness its capabilities. The subsequent examples assume ae
represents the after effects module.
To execute JavaScript code within After Effects from Node.js, use the following structure:
ae(() => alert("Hello!nFrom node.js"));
This simple command will display an alert box within After Effects, triggered directly from your Node.js environment. It demonstrates the fundamental communication bridge between the two platforms.
Scripting: Bridging Node.js and After Effects Environments
It’s critical to understand that After Effects scripting operates in a distinct engine separate from Node.js. Direct variable sharing between these environments isn’t possible:
var foo = "bar"; //this will NOT work: ae(() => alert(foo));
To pass data from Node.js to your After Effects script, you must provide it as an argument when executing the command:
var foo = "bar";
ae((foo_from_node) => alert(foo_from_node), foo);
The execute
method effectively converts your provided function into a string and transmits it to After Effects for parsing and execution. Consequently, any data you send needs to be JSON-serializable.
Retrieving data from After Effects follows a similar principle:
var project_name = ae(() => {
if (app.project.file) return app.project.file.name;
else return "(project not yet saved)";
});
console.log(project_name);
For comprehensive details on the After Effects JavaScript API, refer to the After Effects Scripting Guide.
Synchronous vs. Asynchronous Execution
By default, the ae()
shortcut executes code synchronously, pausing Node.js execution until completion. However, for non-blocking operations, you can execute code asynchronously:
// execute sends code to After Effects, returning a Promise
ae.execute(() => {
return app.project.activeItem.name;
})
.then(name => console.log(name))
.catch(err => console.log('No Active Item'));
The default shortcut is essentially a streamlined version of ae.executeSync
:
function save_current_project() {
app.project.save();
}
ae.executeSync(save_current_project); // is the same as ae(save_current_project);
Customizing Options for Enhanced Control
The ae
object provides several options to tailor its behavior:
ae.options.errorHandling = true;
ae.options.minify = false;
ae.options.program = null;
ae.options.includes = [
'./node_modules/after-effects/lib/includes/console.jsx',
'./node_modules/after-effects/lib/includes/es5-shim.jsx',
'./node_modules/after-effects/lib/includes/get.jsx'
];
These settings allow you to define default behaviors for error handling, code minification, program path, and included scripts.
Error Handling: Robust Script Execution
Enabling errorHandling
suppresses errors within After Effects, returning them as part of the Promise result:
ae.options.errorHandling = true;
ae.execute(() => { throw new Error("FooBar got FooBarred all the way to FooBar.") })
.then(result => console.log(result)) // empty
.catch(err => console.log(err)); // contains error
Disabling error handling will cause After Effects to display error popups, halting script execution until addressed.
Minification: Optimizing Code Transfer
The minify
option, when set to true
, minifies your code before sending it to After Effects. While disabled by default for local execution efficiency, you can enable it if desired:
ae.options.minify = true;
Program Path: Specifying After Effects Installation
By default, ae
searches for After Effects in the standard application directory for your platform. If you have a custom installation path or multiple versions, use the program
option:
ae.options.program = path.join('OtherAppDirectory', 'Adobe After Effects 2015');
Includes: Extending After Effects Functionality
The includes
option is an array that allows you to incorporate code from external files into your After Effects scripts. Default includes enhance functionality:
console.js: Enhanced Logging
Provides console.log
within the After Effects namespace. Logs generated using console.log
in After Effects will be output to your Node.js console upon script completion, provided you’ve enabled Preferences -> General -> Allow Scripts to Write Files and Access Network in After Effects.
es5-shim.js: Modern JavaScript Compatibility
After Effects’ JavaScript environment is based on an older, pre-ES5 version. Including es5-shim
adds compatibility for ES5 methods and functions, allowing you to use more modern JavaScript features:
ae.execute(() => {
[1,2,3,4].forEach(i => alert(i)); // won't throw an error
});
Furthermore, you can leverage ES6 syntax in your code, as it’s processed by babel before reaching After Effects.
get.js: jQuery-Inspired Selection API
Introduces a ‘get’ object, inspired by jQuery selectors, for simplified interaction with After Effects items:
ae.execute(() => {
// finds every composition with 'final' in the name
// and alerts it
get.comps(/Final/)
.each(comp => alert(comp.name));
});
See the ‘get’ API section below for detailed usage.
Include Considerations: Optimizing Your Workflow
The default include options are designed for quick setup and ease of use. However, for more refined workflows, consider these optimization strategies.
Persistent Environment: Leveraging Global Scope
The After Effects scripting environment persists between executions unless manually reset or After Effects is restarted. You can take advantage of this persistent environment:
ae(()=> console.log('includes ran'));
ae.options.includes = [];
// Once you've run your includes,
// you can disable them and still benefit from their usage in the namespace:
ae(()=> ["shim","still","exists"].forEach(str => alert(str)));
Access the global After Effects namespace through $.global
:
ae(() => $.global.whoKilledKenny = "you bastards");
var who = ae(() => $.global.whoKilledKenny);
console.log(who); // you bastards
Scripts Directory: Utilizing Existing Scripts
Retrieve the scripts directory associated with your After Effects installation using:
console.log(ae.scriptsDir);
This is useful for incorporating existing scripts located in the Scripts Directory. It will throw an error if After Effects cannot be found.
Startup Folder: Auto-Loading Scripts
Alternatively, copy the scripts from the lib
folder to the After Effects Scripts/Startup folder. These scripts will then be automatically loaded into the global namespace when After Effects starts, eliminating the need to include them with each command execution.
.jsx vs .js: Understanding File Extensions
In the After Effects Scripts Directory, you’ll notice files with the .jsx
extension. Historically, .jsx
was Adobe’s primary JavaScript format due to its built-in XML literal support:
var xml =
However, Node.js with Babel doesn’t inherently support this XML literal syntax. Therefore, if you include a .jsx
file, ae
assumes it’s written in Adobe JavaScript and won’t apply Babel or minification to avoid potential errors if XML literals are present.
Advanced Usage: Precompiling Commands for Performance
For frequently executed methods, precompile them as Command
objects. This avoids repeated Babel transformation and minification, enhancing performance:
var create_composition = new ae.Command(name => {
name = typeof name === "string" ? name : "New Comp";
app.project.items.addComp(name, 1920, 1080, 1, 24, 10);
});
Once created, Command
objects can be executed with varying arguments:
ae(create_composition, "First Comp");
// or
ae.execute(create_composition, "Super Comp");
// or
create_composition.executeSync("Oh we got ourselves a badass here");
// you get the idea
create_composition.execute("Breast Milk");
Command
objects inherit default options from ae.options
:
ae.options.minify = true;
var getNumItems = new ae.Command(() => app.project.numItems); // will minify
Command options are immutable after creation:
ae.options.errorHandling = false;
var breakForFun = new ae.Command(() => throw("This is MY house."));
ae.options.errorHandling = true;
breakForFun.executeSync(); // will still alert inside in AE
breakForFun.errorHandling = true; // throws error
Customize options when creating a Command
:
var getProjectName = new ae.Command(()=> app.project.file.name, { includes: null, minify: true });
Script Creation: Deploying Scripts within After Effects
Beyond executing code, you can generate scripts for direct use within After Effects:
ae.create(() => {
let say_name = item => alert(item.name);
say_name(app.project.activeItem);
}, "SayName.jsx");
This creates a .jsx
script file accessible in After Effects’ scripts folder. Relative paths are interpreted relative to the scripts folder:
ae.create(() => {
alert("After Effects totally just started.");
}, "Startup/SayHello.jsx");
For scripts outside the scripts folder, provide an absolute path:
ae.create(() => {
app.project.activeItem.remove();
}, path.join(__dirname, "Created Scripts", "DeleteActiveItem.jsx"));
Create scripts from Command
objects, pre-baking arguments:
var renameActiveItem = new ae.Command( name => app.project.activeItem.name = name);
ae.create(renameActiveItem, "RenameActiveItemLarry.jsx", "Larry");
File extensions are optional; .jsx
is the default. Synchronous script creation is also available via ae.createSync()
.
‘get’ API: Streamlined After Effects Object Selection
The get
method (when enabled) provides a jQuery-like selector for interacting with After Effects objects. It intelligently parses arguments to refine selections.
Argument Types: Defining Selection Criteria
Type: Object Type Filtering
Type arguments narrow selections to specific object types. Use constructors like CompItem
, Folder
, Layer
, or Property
to specify the desired object type.
Context: Selection Scope
Context arguments define the scope for selection. Accepts ItemCollection
, LayerCollection
, QueryResult
, or arrays of instances.
Selector: Refining by Properties
Selector arguments fine-tune results based on object properties. Use strings, regular expressions, numbers, or functions for precise filtering.
Making Selections: Flexible Querying
Arguments can be provided in any order and combination:
// selects every single item, layer and
// and property in the current project.
var everything = get();
Select objects of a specific type:
// CompItem is the constructor for composition objects.
var allComps = get(CompItem);
Further refine with selector arguments:
// String selectors match item names
var mainComp = get(CompItem, "Main");
// Regular expressions also work on names
var allCopies = get(FootageItem, /Copy$/);
// Number selectors match item labels
var pinkLayers = get(AVLayer, 4);
Context arguments narrow the search scope:
var solidsFolderContents = get(FootageItem, "Solids").children();
var redLabelledItems = get(1, solidsFolderContents);
Collections as contexts:
var activeComp = app.project.activeItem;
var activeCompLayers = get(AVLayer, activeComp.layers);
Multiple types for broader selections:
var allVectorLayers = get(TextLayer, ShapeLayer);
var allItems = get(CompItem, FolderItem, FootageItem);
Arrays for organized type definitions:
var allLayerTypes = [CameraLayer, LightLayer, TextLayer, AVLayer, ShapeLayer];
var allLayers = get(allLayerTypes);
var allRedLayers = get(1, allLayers);
Shortcuts for common selections:
var allRedLayers = get.layers(1);
var itemsNamedFinal = get.items("Final");
var compsNamedMain = get.comps("Main");
Chainable commands for complex queries:
var redFoldersNamedAssets = get("Assets").folders().filter(1);
children()
method for nested objects:
var textLayersInCompsNamedMain = get.comps("Main").children(TextLayer);
Function selectors for advanced logic:
var guideLayers = get.layers(lay => lay.guideLayer);
var longRedComps = get.comps(c => c.duration > 60 && c.label == 1);
// all layers in compositions in folders with more than five items
// that have inpoints close to the beginning of the timeline.
var sel = get.folders(fold => fold.numItems > 5)
.children(CompItem)
.children(lay => lay.inPoint
Working with Selections: Actions on Query Results
Selections can be ‘unboxed’ into arrays:
var gotComps = get.comps("Main");
var comps = gotComps.selection(); // all elements
var comp = gotComps.selection(0);// first element in selection
QueryResult methods for selection manipulation:
// .each iterates through every item in the selection
get.comps("Main").each(c => c.comment = "Approved");
// .set sets each item in a selection to the given value, if possible
get.layers(1).set("locked", true);
// .set can also take a function that returns a value
get(CompItem, AVLayer)
.set("width", i => i.width * 2)
.set("height", i => i.height * 2);
// .call looks for a matching method name, and calls it with any provided arguments
get.comps().call("setProxyToNone");
Version 0.4.0: Windows Compatibility
Version 0.4.0 introduces Windows support, although it’s still undergoing rigorous testing. Feedback on functionality, especially from Windows users, is highly appreciated.