BEGINNER TUTORIAL : Add Slider/Expression to a comp

Moderator: Paul Tuersley

Post Reply
User avatar
Disciple
Posts: 137
Joined: June 5th, 2004, 8:05 am
Location: Los Angeles, CA
Contact:

This is a "work in progress tutorial". It follows the various steps I had to go through to create
a simple script that fulfilled a simple but real-world need. The tutorial was revised 3rd Aug 04
by Paul Tuersley, who's helped clear a few things up a bit.

The project I was working on was an intro sequence for a client. It was composed of 80 video
layers scaled down to about 4% and laid out to write the client's name. An adjustment layer
with levels was set on top of the 80 layers to brighten the whole thing a bit.

Image

The effect I needed to achieve was a progressive wiggle of all layers so that they would slowly
start moving, and then jump all over the place after 15 seconds. This meant adding a wiggle
expression to each layer, and connecting it to a slider control that would change the amount
of wiggle over time.

One solution would have been to apply the slider to the 80 layers, and then copy paste the
wiggle expression to all the layers position stream. But that seemed a bit tedious, and with
scripting, all this type of stuff can be done instantly.

As this was my first script, I needed to figure out some stuff. But before that, I got some
basic code from other scripts to make sure all I was doing was nice and clean.



1/ I wanted to make it so that I could undo everything that happens in this script using just
one undo. To do this I needed to create an 'undo group' :

The code is : app.beginUndoGroup("AddExpressionAndSlider");

This groups all subsequent actions that this script performs into a single undo and lets you
name that undo as it will appear in the Edit>Undo menu. I also needed to put the line
app.endUndoGroup(); later in the script to end this undo group.



2/ I wanted this script to work on the currently selected comp, so for that I copied a snippet
from a J.C. Burns script:

var curItem = app.project.activeItem;
if (curItem == null || !(curItem instanceof CompItem)){
alert("Please establish a comp as the active item and run the script again");
}

This code checks to see if a comp is selected. In plain english it says: if nothing is selected,
or the currently selected item isn't a comp, put up an alert to say a comp must be selected
before running this script.

The first line defines a new curItem variable as app.project.activeItem
What the scripting guide tells us about activeItem :
"The project attribute activeItem returns the item that is currently active and is to be acted
upon, or a null if no item is currently selected or if multiple items are selected. ".

This tells us that my newly defined curItem will either be the currently selected item (which
hopefully will be a comp), or a null. (In scripting, null basically means nothing)

The last three lines form an if statement. if statements can be used when you want a script
to make a decision, they basically say if ( this condition is true ) { run this code }.

The if statement checks whether something (a condition) is true. If the condition is true, the
code inside the if statement (which is placed inside { } brackets) will be run.

In this script there are actually two conditions being checked.

The first checks if "curItem" is "==" equal to "null". (Remember: "The project attribute
activeItem returns.....a null if no item is currently selected....")

The second condition asks if curItem is an instanceof CompItem, which would normally
return 'true' if curItem is a comp. But the "!" exclaimation mark at the beginning has the
effect of inverting the result of the condition, so it will now be true if curItem is not a comp.

These two conditions are separated by "||" which in javascript means "or", so it's like I'm
saying if ( curItem is a null "or" curItem is not a comp ) { run this code }.

If either condition is true the enclosed code will be run. In this case it will display an alert
that says "Please establish a comp as the active item and run the script again".



3/ My script needs a comp to have been selected for it to work, and now it will warn the
user if no comp was selected. Next I need to tell it what to do if a comp was selected.

Back to that last if statement again, I previously left out the fact that with an if statement
you also have the option of saying:
if ( a comp was not selected ) { run this code } else ( run this code instead }


So now I need to write what is essentially the core of the script, the 'else' code that will run
if a comp was selected. For that I need to establish what my script needs to do exactly.

So I first represent it in my mind as a series of operations. My script needs to :
a) Go through each of the layers in the active comp.
b) Check whether each layer is a footage layer.
c) If it is a footage layer, add a slider and an expression to it.

To go through each of the layers in the active comp, I need to create a 'loop'. This is typically
the way that a script can repetitively perform some action/s on a group of similar objects, in
this case a group of layers.

My friend Paul Tuersley helped me out here, as I had no clue how I needed to write this loop.
Here is the code he sent me :

for (var b = 1; b <= curItem.numLayers; ++b){

There are a number of different types of loop available in Javascript, but the most comon is
probably the for loop. To create a for loop, you need at least three parts, the "initialization" of
the loop, the "testing" of some condition to see if the loop should continue, and something
that will "increment" the loop.

So first we initialize the loop by defining a new variable b with a value of 1. (we want to access
each of the layers in a comp, and as layers are numbered from 1, it makes sense to start the
loop with a value of 1.)

Secondly, there is the condition being tested:
is b "<=" less than or equal to "curItem.numLayers" the number of layers in curItem
(remember, curItem is the selected comp)

So that says, if the value of b isn't greater than the number of layers in the comp, run the
rest of the code inside the for loop.

We'll look at the 'rest of the code' in a minute, but there's still the third part of the for loop
to explain.

Once the 'rest of the code' has been run, the third part of the for loop, "++b" adds 1 to the
current value of b. Then the condition is tested again, with the loop continuing until the
condition becomes false, which in this case will be when b becomes greater than the number
of layers in the comp.

So now we move on to the code in the loop that will be repeated.

var curLayer = curItem.layer(b);

This line defines the variable curLayer as curItem.layer(b). The first time the loop runs we
made it so that b = 1, so saying curItem.layer(b) will give us layer 1 from the selected comp.

Because b is incremented by 1 each time the loop runs, the second time the loop runs the
curLayer variable will be redefined as layer 2 and so on, through each layer in the comp.



4/ Now I needed a way of checking whether each layer was a footage layer and for this I used
another if statement.

if (curLayer.matchName == "ADBE AV Layer") {

So we're saying, if ( the current layer's matchName is "ADBE AV Layer" ) {run this code}.

But what is a matchName?

matchName will give you After Effects' internal name for any given layer or property type, so
you can figure out what kind of layer or property you are dealing with. For example, a layer
might also be an "ADBE Camera Layer", an "ADBE Light Layer" or an "ADBE Text Layer", all
fairly self explainatory really.

So an "ADBE AV Layer" is basically any kind of layer that might contain audio or video, in fact
any kind of layer that isn't a Camera, Light or Text layer.



5/ Remember that we're still inside the for loop, looking at one particular layer at a time.
We've just checked whether the layer is a footage (AV) layer, so now we need to write the
code that will be run if it is.

We start by adding a slider control effect to the layer:
var slider = curLayer.Effects.addProperty("ADBE Slider Control");

Let's look at the second part of that curLayer.Effects.addProperty("ADBE Slider Control");

This shows you how you can add an effect to a layer. First you specify the layer (we've already
defined curLayer as being the current layer we're looking at from the active comp).

When navigating through After Effects' heirachy of comps, layers, effects, etc, you use dots "."
to separate the specific parts. So curLayer.Effects brings you to the point in the heirachy where
you have access to a layer's effects. At this point you can use .addProperty() to add an effect.

Notice the similarities between "ADBE Slider Control" and the matchName stuff we we're talking
about earlier. Each effect also has it's own unique identifier name, and while you could just use
the effect's original name (i.e just "Slider Control"), the benefit of using the matchName is that
it never changes, even when using After Effects in a different language.

Though you don't actually need the "var slider =" bit at the start when you add an effect. It's a
good opportunity to define a variable (in this case called "slider") that will represent this effect.
Especially as we aren't finished with this effect yet.



6/ The next three lines are:
slider.property(1).setValueAtTime(0, 0);
slider.property(1).setValueAtTime(10, 2);
slider.property(1).setValueAtTime(20, 360);


To add keyframes to the effect I had to go through some trial and error. I knew I had to use
setValueAtTime to add the kf's, and as Slider Control only has one property (the slider value,
versus effects with multiple properties) I figured I could just use slider.setValueAtTime.

But that doeen't work. So Paul helped me out here again. In fact you have to use the index
of the property you are affecting. In this case there is only one property, so I used property(1).

The rest is simple. setValueAtTime is followed by parentheses in which the first value is the time
(in seconds) at which you want to add a keyframe and the second value is the value you want
to set that keyframe to.

As you can see from my code, three keyframes are being created. One at 0 seconds with a value
of 0, one at 10 seconds with a value of 2, and one at 20 seconds with a value of 360.

There is also a slightly different method that can be used. setValuesAtTimes() lets you create all
the keyframes in just one line, but the trick is that you need to create two arrays, one for the
times and one for the values. But I wasn't too bold and just did it the simple way.



7/ So the script will now add a slider control effect to the layer, along with three keyframes. now
I just need to add the wiggle expression to the Position property.

var expString = "wiggle(effect(1)(1), effect(1)(1))";

In this line, I defined a new variable 'expString' that contains the expression I want to use.

If you're already familiar with the wiggle expression, you'll know that it require two values to
work, these are wiggle(wiggles_per_second, wiggle_amount).

In this case they're both the same 'effect(1)(1)' which says, look at 'effect(1)' the first effect
on this layer and read '(1)' the first parameter of that effect.

This is just an alternate way of saying the following: 'effect("Slider Control")("Slider")' although
there is a problem doing it that way....

In scripting, our newly defined expString is an example of a string. A string is basically just a type
of variable that, rather than storing a value, is able to store a series of alphanumeric characters.
For something to be a string, it has to be placed between quotes, so for example, 400 is a value
whereas "400" is a string made up of the characters 4, 0 and 0.

The problem being that as you can see effect("Slider Control")("Slider") contains a few quotes
itself, which will cause problems unless you tell scripting not to think of them as quotes. You can
do this by putting back slashes "\" in front of each quote to be ignored.

So to sum up, I could either have written:
var expString = "wiggle(effect(1)(1), effect(1)(1))";

or:
var expString = "wiggle(effect(\"Slider Control\")(\"Slider\"),effect(\"Slider Control\")(\"Slider\"))";


The next line is:
curLayer.property("position").expression = expString


This shouldn't be too hard to follow, We're accessing the position property on the current layer,
and setting that property's expression to equal the string that we just stored in expString.



8/ So that's the script nearly finished, just a bit of tidying up to do.

Firstly, we've just finished the code that was run inside the second if statement that ensured this
would only be performed on an AV layer. So we need a closing bracket to signify the end of the
code inside that if statement:
}

Secondly, we're also still inside the for loop, so we need another closing bracket to signify the end
of the code being run inside the loop.
}

Thirdly, that whole looping section was contained inside the else part of the very first if statement
that checked whether a comp was selected. So we need yet another closing bracket.
}

We also need to close the undo group that we started at the beginning of the script, using:
app.endUndoGroup();


And finally, the entire script is enclosed in one more set of brackets, as you can see in the finished
script below:


{
// create an undo group
app.beginUndoGroup("AddExpressionandSlider");

var curItem = app.project.activeItem;

// check if comp is selected
if (curItem == null || !(curItem instanceof CompItem)){

// if no comp selected, display an alert
alert("Please establish a comp as the active item and run the script again");

} else {

// otherwise, loop through each layer in the selected comp
for (var b = 1; b <= curItem.numLayers; ++b){

// define the layer in the loop we're currently looking at
var curLayer = curItem.layer(b);

// check if that layer is a footage layer
if (curLayer.matchName == "ADBE AV Layer"){

// add a slider and three keyframes
var slider = curLayer.Effects.addProperty("ADBE Slider Control");
slider.property(1).setValueAtTime(0, 0);
slider.property(1).setValueAtTime(10, 2);
slider.property(1).setValueAtTime(20, 360);

// add an expression to the Position property.
var expString = "wiggle(effect(1)(1), effect(1)(1))";
curLayer.property("position").expression = expString;
}

}

}
// close the undo group
app.endUndoGroup();
}


I hope this helps beginners get a better view of what is going on inside the scripting language.
For me writing this was a great learning experience, and I think writing these scripts is a great
way to advance.

The client liked the final product, and I felt much better having done it with scripting than having
had to copy/paste code on all my layers.

Alex
laracroft
Posts: 3
Joined: November 10th, 2009, 12:22 pm

Thanks Disciple for this.
I am Lara Croft and I am new for this. This tutorial really help me to learn.
If you have another tutorial please post it here.
Thanks again.
User avatar
Apoal1
Posts: 2
Joined: August 8th, 2008, 3:31 am

Thanks you!
I am noob for it but seeing UndoGroup, as in scripts than I could used, give trust in this post for create my first personal script :)
I like a lot your step by step explanation of script's language (|| is or !? ok :p), and i have a question on this tutorial.
Our var slider add a slider property, so when we write:
var slider = curLayer.Effects.addProperty("ADBE Slider Control");
also when we write:
slider.property(1).setValueAtTime(...
why After Effects don't add a slider2, 3, 4 ? In facts AE edit the first slider,
so i don't understand what we did when we create this var, I thought that it was only reduction of language.
stib
Posts: 21
Joined: December 10th, 2006, 10:23 pm
Contact:

Thanks for the tutorial, but one thing struck me: you can just apply one slider to a null, and then link the wiggle expression to it, rather than having separate sliders on lots of layers. So your expression would look like:

Code: Select all

freq=thisComp.layer("Null 1").effect("Slider Control")("Slider");
amp=thisComp.layer("Null 1").effect("Slider Control 2")("Slider")
wiggle(freq , amp)
You can see it in action below. If I was using this in anything complicated I'd name the slider effects something else except "Slider Control" and "Slider Control 2" (e.g. in this case "amp" and "freq"). I often have a null with half a dozen sliders all controlling other layers. It makes it much easier to change things, and you can leave it at the top of your comp so it's easily accessible.

Image

Then you just need to copy the expression for that property (right-click>copy expression only) and paste to all the relevant layers. Much less edifying for us, but much much faster than writing a script.
cranky_dan
Posts: 1
Joined: October 7th, 2012, 10:01 pm

Thanks for this!

This is my first excursion into AE scripting and expressions. I appreciate it.

Dan
Mike A
Posts: 5
Joined: July 2nd, 2009, 4:17 am

It might be 13 years old - but still a very useful read for scripting newbies like myself : )
Thanks Disciple!
Post Reply