Difference between revisions of "AfterEffects"

From cgwiki
 
(13 intermediate revisions by the same user not shown)
Line 28: Line 28:
 
* https://fendrafx.com/tutorial/after-effects-extendscript-training-complete-series/
 
* https://fendrafx.com/tutorial/after-effects-extendscript-training-complete-series/
  
=== Quick things: ===
+
=== Some projects break scripts ===
  
Extendscript Editor:
+
I had a project that used Object.keys() to work with dictionaries, but when I pasted the expression into another project, was told that function was undefined. Then found floor() didn't work, nor ceil(), nor a bunch of other things. Nothing in the comp or the preferences implied what the difference was, I was quite confused.
 +
 
 +
Luckily Nick Wood came to the rescue!
 +
 
 +
Under File -> Project Settings, go to the Expressions tab. It's likely the broken project has the expression engine set to 'Legacy Extendscript', while the working one is set to 'Javascript'.
 +
 
 +
Adjust both to be 'Javascript', things started working again. Hooray! Thanks Nick!
 +
 
 +
== Quick things: ==
 +
 
 +
=== Extendscript Editor ===
 
* F5 - run the script ( like ctrl-enter in a wrangle)
 
* F5 - run the script ( like ctrl-enter in a wrangle)
 
* ctrl-shift-c - clear the console
 
* ctrl-shift-c - clear the console
 
* $.writeln('foo'); - print something to the internal console in the extendscript editor
 
* $.writeln('foo'); - print something to the internal console in the extendscript editor
  
VSCode:
+
=== VSCode ===
 
* F1, 'Adobe After Effects' - run the script, the command prompt will store the last run, so its usually just F1, enter
 
* F1, 'Adobe After Effects' - run the script, the command prompt will store the last run, so its usually just F1, enter
 
* writeLn('foo'); - print something to the info panel within AE itself (make sure the panel is expanded first)
 
* writeLn('foo'); - print something to the info panel within AE itself (make sure the panel is expanded first)
 
* alert('foo'); - bring up an alert box
 
* alert('foo'); - bring up an alert box
  
 +
=== Misc ===
 
Find comp by name: https://gist.github.com/dokluch/1ca33a65dc67d4a3047f
 
Find comp by name: https://gist.github.com/dokluch/1ca33a65dc67d4a3047f
  
Line 46: Line 57:
 
Handy guide which is sorta kept more up to date than adobe's internal docs which for some reason are riddled with dead links: https://ae-scripting.docsforadobe.dev/
 
Handy guide which is sorta kept more up to date than adobe's internal docs which for some reason are riddled with dead links: https://ae-scripting.docsforadobe.dev/
  
=== Shapes ===
+
=== Why property names are so complex ===
 +
 
 +
Why does scripting use
 +
 
 +
property("ADBE Vector Shape")
 +
 
 +
and not
 +
 
 +
property("shape")
 +
 
 +
as shown in the AE layer property names?
 +
 
 +
Because of localization! "Shape" as a property name will change based on the language zone you're in, but "ADBE Vector Shape" won't. It's irritatingly long, but that's the reason. I've found sometimes you can cheat and the shorter names work, but then others won't. It's less hassle in the long run to just use the proper names.
 +
 
 +
== Shapes ==
  
 
[https://youtu.be/ZQgyVcSNF7Y?t=347 Fiddly. How? Fiddly.]
 
[https://youtu.be/ZQgyVcSNF7Y?t=347 Fiddly. How? Fiddly.]
  
 +
=== Get shape point positions ===
 
Get vertices from a shape layer path:
 
Get vertices from a shape layer path:
  
Line 57: Line 83:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Create a new shape, insert it into an existing shape path:
+
=== Update an existing shape path ===
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
Line 70: Line 96:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
=== Create a new layer and new shape ===
 +
 +
Paths created via scripting aren't visible by default, so you have to add stroke and width properties too:
 +
 +
<syntaxhighlight lang="javascript">
 +
// make a layer
 +
var layer = mainComp.layers.addShape();
 +
layer.name = 'my cool shape';
 +
 +
// make a shape
 +
var newshape = new Shape();
 +
newshape.closed = false;   
 +
newshape.vertices = [[0,0], [0,100], [100,100], [100,0]];;
 +
 +
// add a path, set its path values from the shape, set its colour and width
 +
var pathGroup = layer.content.addProperty("ADBE Vector Shape - Group");
 +
pathGroup.property("ADBE Vector Shape").setValue(newshape);
 +
var stroke = layer.content.addProperty("ADBE Vector Graphic - Stroke");
 +
stroke.property("ADBE Vector Stroke Color").setValue([1,0,0,1]);  // rgba, so this is solid red
 +
stroke.property("ADBE Vector Stroke Width").setValue(15);
 +
</syntaxhighlight>
  
=== Set Text Layers ===
+
== Set Text Layers ==
  
 
Less fiddly than shapes. Which is nice. I also included how to get the comp, which is a modified version of a script linked further up, without all that pesky type checking and correct coding. :)
 
Less fiddly than shapes. Which is nice. I also included how to get the comp, which is a modified version of a script linked further up, without all that pesky type checking and correct coding. :)
Line 89: Line 136:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== paths on disk ===
+
== Hex to RGBA ==
 +
 
 +
Infuriatingly several functions that are exposed to the expression editor aren't available in jsx scripting, hextorgb is one of them. Here's a standin function I adaped from a creativecow post. It assumes for regular web style #FF22BB RGB input, and returns an AE 4 value vector array with alpha set to 1.
 +
 
 +
<syntaxhighlight lang="javascript">
 +
function hex_to_rgba(hex){
 +
var rgb = parseInt(hex, 16);
 +
var red  = (rgb >>16) & 0xFF;
 +
var green = (rgb >>8) & 0xFF;
 +
var blue  = rgb & 0xFF;
 +
 +
var colorArray = [red/255.0, green/255.0, blue/255.0, 1.0];
 +
 +
return colorArray;
 +
}
 +
</syntaxhighlight>
 +
 
 +
== Paths on disk ==
  
 
Uses forward slashes, windows drive locations are gitbash style so /c/foo/bar:
 
Uses forward slashes, windows drive locations are gitbash style so /c/foo/bar:
Line 97: Line 161:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== includes ===
+
== Includes ==
  
 
Same pathing as above, can set includepath and include:
 
Same pathing as above, can set includepath and include:
Line 106: Line 170:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== toComp and fromComp in scripting ===
+
== toComp and fromComp in scripting ==
  
 
You can't, its an expression thing only, arrgh. Luckily someone has written their own module to do space transformation, LST:
 
You can't, its an expression thing only, arrgh. Luckily someone has written their own module to do space transformation, LST:
  
 
https://github.com/rendertom/LST
 
https://github.com/rendertom/LST
 +
 +
== Jsx javascript esoterica ==
 +
 +
Javascript can misbehave in odd ways, javascript in AE via jsx drops a few things that are supported in javacript expression, here's a few gotchas:
 +
 +
=== Spread operator ===
 +
 +
Something new to me. Math.max() takes an arbitrary number of arguments, and returns the biggest value:
 +
 +
Math.max(1,2,3,4);  // returns 4
 +
 +
But if you pass it an array, it gets confused:
 +
 +
myarray = [1,2,3,4]
 +
Math.max(myarray);  // returns fuck you
 +
 +
The spread operator is a neat trick; put 3 dots in front of the array, it unpacks the array into arguments:
 +
 +
myarray = [1,2,3,4]
 +
Math.max(...myarray);  // returns 4
 +
 +
Which is great, works in AE expressions. But not in jsx. Ugh. The workaround is to use this oddball .apply(null, thearray) call:
 +
 +
Math.max.apply(null, myarray); // returns 4
 +
 +
=== JSX isn't very strict about syntax ===
 +
 +
By accident I've left off semicolons, variable declaration prefixes, AE's javascript interpreter just doesn't care. Good if you're lazy, and frankly I think makes for more readable code. Compare:
 +
 +
var fonttype = jsondata.state.layout.body_font.name;
 +
var fontlayer = mainComp.layers.byName('font');
 +
var fontlayer.property("Text").property("Source Text").setValue(fonttype);
 +
// or...
 +
fonttype = jsondata.state.layout.body_font.name
 +
fontlayer = mainComp.layers.byName('font')
 +
fontlayer.property("Text").property("Source Text").setValue(fonttype)
 +
 +
I'm sure it'll come back to bite me at some point, but until then...
 +
 +
=== Arrow expressions don't work ===
 +
 +
I've seen this come up a lot, ways to modify arrays in place though a function (like the python list comprehension i like to abuse so much) :
 +
 +
numbers = [1, 2, 3];
 +
double = (x) => x * 2;
 +
numbers.map(double);
 +
 +
The arrow thing ( => ) is an inline or lamba expression. It works in AE expressions, but not JSX. No big deal though, it's ultimately a shortcut for writing this:
 +
 +
numbers = [1, 2, 3];
 +
function double(x) {
 +
  return x * 2;
 +
}
 +
numbers.map(double);
  
 
== Nexrender ==
 
== Nexrender ==
  
Handy command line interface to after effects that can control rendering, ffmpeg output, but most importantly allow for you to control AE, so its easy(ish) to swap layers, text, properties, you name it.
+
Handy command line interface to after effects that can control rendering and ffmpeg output, but most importantly allow for you to control AE, so its easy(ish) to swap layers, text, properties, you name it.
  
 
https://github.com/inlife/nexrender
 
https://github.com/inlife/nexrender
Line 140: Line 258:
 
     },
 
     },
 
</syntaxhighlight >
 
</syntaxhighlight >
 
  
 
== Expressions ==
 
== Expressions ==
  
Annoyingly while it seems its full javascript, its not. It's more like hscript/python expressions in that you can read from anywhere, but ony write to yourself (ie whatever the property/parameter it is that you're writing the expression in).  
+
AE expressions are limited in scope like hscript/python expressions in that you can read from anywhere, but ony write to yourself (ie whatever the property/parameter it is that you're writing the expression in).  
  
 
Debugging is also frustrating, as there's no console. You can print stuff kindasorta by forcing a fake expression with throw, eg
 
Debugging is also frustrating, as there's no console. You can print stuff kindasorta by forcing a fake expression with throw, eg
Line 151: Line 268:
  
 
You'll get an error, when you click in the expression field, the error (ie the thing you want) will be shown in a tooltip. This can't be copypasted though, so if you need to access these values, you need to create a text layer, and have the text expression fetch the thing you want. Frustrating.
 
You'll get an error, when you click in the expression field, the error (ie the thing you want) will be shown in a tooltip. This can't be copypasted though, so if you need to access these values, you need to create a text layer, and have the text expression fetch the thing you want. Frustrating.
 +
 +
If you need to set multiple bits of text, create layers, take bigger control over your comps, you need to use jsx scripts instead of expressions.
  
 
=== Set shape via expression and csv ===
 
=== Set shape via expression and csv ===

Latest revision as of 19:55, 8 August 2022

Open multiple After Effects sessions

Hit the windows key and R at the same time, type

afterfx -m

Scripting

Several tutorial sites mention a scripting interface, but I couldn't find it. Turns out its hidden, this page gave clues to where it is:

https://www.codeandmotion.com/blog/after-effects-absolute-beginners

Go to creative cloud, prefs, turn on 'show older apps'. Restart creative cloud, you'll find Extendscript Toolkit CC. Amusingly you can't launch it from the creative cloud launcher, but it should be available from your Windows start menu.

That same web page says to set After Effects as the target, this is done from the first dropdown in the top left, where it says 'ExtendScript Toolkit CC', change that to 'Adobe After Effects 2022'.

There's also a way to link directly to VSCode, but last time I tried it refused to work. Will try again one day, using this link as a guide: https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01

The older Extendscript editor works, but its, well, old. The newer VSCode stuff is much better. I had trouble getting it going the first time, but watching this video clarified. Basically, install 2 VSCode extensions:

  • Extendscript Debugger
  • Adobe Script Runner

With those in place, you can create a jsx file, at any time hit F1, find 'Adobe After Effects', the script will be saved and run within AE.

David Torno helpfully shared links to his pretty extensive free training series too, cool stuff:

Some projects break scripts

I had a project that used Object.keys() to work with dictionaries, but when I pasted the expression into another project, was told that function was undefined. Then found floor() didn't work, nor ceil(), nor a bunch of other things. Nothing in the comp or the preferences implied what the difference was, I was quite confused.

Luckily Nick Wood came to the rescue!

Under File -> Project Settings, go to the Expressions tab. It's likely the broken project has the expression engine set to 'Legacy Extendscript', while the working one is set to 'Javascript'.

Adjust both to be 'Javascript', things started working again. Hooray! Thanks Nick!

Quick things:

Extendscript Editor

  • F5 - run the script ( like ctrl-enter in a wrangle)
  • ctrl-shift-c - clear the console
  • $.writeln('foo'); - print something to the internal console in the extendscript editor

VSCode

  • F1, 'Adobe After Effects' - run the script, the command prompt will store the last run, so its usually just F1, enter
  • writeLn('foo'); - print something to the info panel within AE itself (make sure the panel is expanded first)
  • alert('foo'); - bring up an alert box

Misc

Find comp by name: https://gist.github.com/dokluch/1ca33a65dc67d4a3047f

Good overview of how to get scripting going in 2022, especially with vscode instead of the extendscript editor: https://www.youtube.com/watch?v=DTBtfFiyjNU

Handy guide which is sorta kept more up to date than adobe's internal docs which for some reason are riddled with dead links: https://ae-scripting.docsforadobe.dev/

Why property names are so complex

Why does scripting use

property("ADBE Vector Shape")

and not

property("shape")

as shown in the AE layer property names?

Because of localization! "Shape" as a property name will change based on the language zone you're in, but "ADBE Vector Shape" won't. It's irritatingly long, but that's the reason. I've found sometimes you can cheat and the shorter names work, but then others won't. It's less hassle in the long run to just use the proper names.

Shapes

Fiddly. How? Fiddly.

Get shape point positions

Get vertices from a shape layer path:

var myLayer = myComp.layer("Shape Layer 1");
points = myLayer.property("Contents").property("Path 2").property("ADBE Vector Shape").value.vertices;

Update an existing shape path

// make a shape
var myShape = new Shape();
myShape.vertices = [[0,0], [0,100], [100,100], [100,0]];
myShape.closed = true;

// find the existing shape in the comp, set the new shape
theshape = myLayer.property("Contents").property("Path 2").property("ADBE Vector Shape");
theshape.setValue(myShape );

Create a new layer and new shape

Paths created via scripting aren't visible by default, so you have to add stroke and width properties too:

// make a layer
var layer = mainComp.layers.addShape();
layer.name = 'my cool shape';

// make a shape
var newshape = new Shape();
newshape.closed = false;    
newshape.vertices = [[0,0], [0,100], [100,100], [100,0]];;

// add a path, set its path values from the shape, set its colour and width
var pathGroup = layer.content.addProperty("ADBE Vector Shape - Group");
pathGroup.property("ADBE Vector Shape").setValue(newshape);
var stroke = layer.content.addProperty("ADBE Vector Graphic - Stroke");
stroke.property("ADBE Vector Stroke Color").setValue([1,0,0,1]);  // rgba, so this is solid red
stroke.property("ADBE Vector Stroke Width").setValue(15);

Set Text Layers

Less fiddly than shapes. Which is nice. I also included how to get the comp, which is a modified version of a script linked further up, without all that pesky type checking and correct coding. :)

findItemByName=function(_name){
	for(var i=1;i<=app.project.numItems;i++)
	{
		var curItem=app.project.item(i);
		if (curItem.name==_name ) return curItem;
	}
	return null;
}

var myComp = findItemByName ("MAIN_COMP");
myComp.layer("text1").property("Text").property("Source Text").setValue("my amazing new text");

Hex to RGBA

Infuriatingly several functions that are exposed to the expression editor aren't available in jsx scripting, hextorgb is one of them. Here's a standin function I adaped from a creativecow post. It assumes for regular web style #FF22BB RGB input, and returns an AE 4 value vector array with alpha set to 1.

function hex_to_rgba(hex){
	var rgb = parseInt(hex, 16); 
	var red   = (rgb >>16) & 0xFF; 
	var green = (rgb >>8) & 0xFF; 
	var blue  = rgb & 0xFF;
	
	var colorArray = [red/255.0, green/255.0, blue/255.0, 1.0];
	
	return colorArray;
}

Paths on disk

Uses forward slashes, windows drive locations are gitbash style so /c/foo/bar:

var csvFile = File("/d/projects/myproj/data.csv");

Includes

Same pathing as above, can set includepath and include:

#includepath "/d/ae/libs/LST"
#include 'LST.js'

toComp and fromComp in scripting

You can't, its an expression thing only, arrgh. Luckily someone has written their own module to do space transformation, LST:

https://github.com/rendertom/LST

Jsx javascript esoterica

Javascript can misbehave in odd ways, javascript in AE via jsx drops a few things that are supported in javacript expression, here's a few gotchas:

Spread operator

Something new to me. Math.max() takes an arbitrary number of arguments, and returns the biggest value:

Math.max(1,2,3,4);  // returns 4

But if you pass it an array, it gets confused:

myarray = [1,2,3,4]
Math.max(myarray);  // returns fuck you

The spread operator is a neat trick; put 3 dots in front of the array, it unpacks the array into arguments:

myarray = [1,2,3,4]
Math.max(...myarray);  // returns 4

Which is great, works in AE expressions. But not in jsx. Ugh. The workaround is to use this oddball .apply(null, thearray) call:

Math.max.apply(null, myarray); // returns 4

JSX isn't very strict about syntax

By accident I've left off semicolons, variable declaration prefixes, AE's javascript interpreter just doesn't care. Good if you're lazy, and frankly I think makes for more readable code. Compare:

var fonttype = jsondata.state.layout.body_font.name;
var fontlayer = mainComp.layers.byName('font');
var fontlayer.property("Text").property("Source Text").setValue(fonttype);
// or...
fonttype = jsondata.state.layout.body_font.name
fontlayer = mainComp.layers.byName('font')
fontlayer.property("Text").property("Source Text").setValue(fonttype)

I'm sure it'll come back to bite me at some point, but until then...

Arrow expressions don't work

I've seen this come up a lot, ways to modify arrays in place though a function (like the python list comprehension i like to abuse so much) :

numbers = [1, 2, 3];
double = (x) => x * 2;
numbers.map(double);

The arrow thing ( => ) is an inline or lamba expression. It works in AE expressions, but not JSX. No big deal though, it's ultimately a shortcut for writing this:

numbers = [1, 2, 3];
function double(x) {
 return x * 2;
}
numbers.map(double);

Nexrender

Handy command line interface to after effects that can control rendering and ffmpeg output, but most importantly allow for you to control AE, so its easy(ish) to swap layers, text, properties, you name it.

https://github.com/inlife/nexrender

The docs above are pretty good, some extra tips cos I keep losing them in that big list:

  • layer searches are wildcarded across all comps by default, so if 'herovideo' is a layer in 6 comps, it will be replaced in all 6.
  • external jsx scripts can be called directly as part of the assets block in json:
   {
        "src": "file:///y:/worktemp/show_it_to_me_update_shapes_v03.jsx",
        "type": "script"
    }
  • text is replaced via a layer and property call, this is also wildcarded across all comps by default:
 {
        "type": "data",
        "layerName": "video_title",
        "property": "Source Text",
        "value": "this is the new text"
    },

Expressions

AE expressions are limited in scope like hscript/python expressions in that you can read from anywhere, but ony write to yourself (ie whatever the property/parameter it is that you're writing the expression in).

Debugging is also frustrating, as there's no console. You can print stuff kindasorta by forcing a fake expression with throw, eg

throw myvar;

You'll get an error, when you click in the expression field, the error (ie the thing you want) will be shown in a tooltip. This can't be copypasted though, so if you need to access these values, you need to create a text layer, and have the text expression fetch the thing you want. Frustrating.

If you need to set multiple bits of text, create layers, take bigger control over your comps, you need to use jsx scripts instead of expressions.

Set shape via expression and csv

Similar but different to the jsx version:

let pts = []

data = footage("data.csv")
ptslength = thisComp.layer("data")("Data")("Number of Rows")
for (let i = 0; i < ptslength; i++) {
	pts[i] = [data.dataValue([0,i])*200-800,data.dataValue([2,i])*.1-2500];
}
createPath(pts, [], [], false);