Helpful Scripts
Case Study: Pre-comp to Layer Duration
Here's an idea for a helpful script conjured up by Brian Maffitt of Total Training fame. When you pre-compose one or more layers, the resulting new comp takes on the length of the current comp and you lose visibility to the duration of the embedded clips. This script will pre-compose one or more selected layers with the "Move all attributes..." option and the resulting new comp's In point will be the earliest In point of the selected layers and the comp's Out point will be the latest Out point of the selected layers. If you only select one layer, the new comp's duration and start time matches that of the layer.
Most of the code design was done by Keiko Yamada of Adobe. My contribution was getting it to work for layers not starting at time zero and to span the duration of multiple layers.
The Complete Script
The first thing we should do is take a look at the code in its entirety:
//
// preCompToLayerDur.jsx (revised 7/26/04)
//
// This script will pre-compose selected layers with "Move all attributes..."
// The in point of the new comp will be the earliest in point of the selected
// layers and the out point will be the latest out point of the selected layers.
//
// The new comp will thus be trimmed so that its duration reflects the composite
// duration of the included layers.
//
// Based on an idea by Brian Maffitt
// Original code design by Keiko Yamada
// Final tweaks and enhancements by Dan Ebberts
//
{
// create undo group
app.beginUndoGroup("Pre-Compose to Layer Duration");
// select the active item in the project window
// and make sure it's a comp
var myComp = app.project.activeItem;
if(myComp instanceof CompItem) {
// make sure one or more layers are selected
var myLayers = myComp.selectedLayers;
if(myLayers.length > 0){
// set new comp's default in and out points to those of first layer
var newInPoint = myLayers[0].inPoint;
var newOutPoint = myLayers[0].outPoint;
// create new comp name of "comp " plus name of first layer
var newCompName = "comp ";
var layerName = myLayers[0].name;
if(layerName.length > 26) {
layerName = layerName.substring(0, 26);
}
newCompName += layerName;
// "precompose" expects an array of layer indices
var layerIndices = new Array();
for (var i = 0; i < myLayers.length; i++) {
layerIndices[layerIndices.length] = myLayers[i].index;
// make sure new comp in point is in point of earliest layer
// and new comp out point is out point of latest layer
if (myLayers[i].inPoint < newInPoint) newInPoint = myLayers[i].inPoint;
if (myLayers[i].outPoint > newOutPoint) newOutPoint = myLayers[i].outPoint;
}
// create the new comp
var newComp = myComp.layers.precompose(layerIndices, newCompName, true );
// set in and out points of new comp
var preCompLayer = myComp.selectedLayers[0];
preCompLayer.inPoint = newInPoint;
preCompLayer.outPoint = newOutPoint;
}else{
alert("select at least one layer to precompose.");
}
}else{
alert("please select a composition.");
}
app.endUndoGroup();
}
It's not as bad as it looks. A lot of the code consists of comments and white space. Even though you may be able to figure out how it works by just reading the code, we'll go through it chunk by chunk because there is a lot of useful stuff going on here.
Housekeeping
The first thing to notice is that we've enclosed the script in our standard scope-limiting braces and an undo group:
{
// create undo group
app.beginUndoGroup("Pre-Compose to Layer Duration");
[the rest of the script goes in here]
app.endUndoGroup();
}
If you're not sure why we do this, go back and read the lesson on Good Housekeeping.
Testing for Valid Conditions
The actual body of the script begins with two tests, and if they aren't met, the script will exit with one of two error messages. This is accomplished with two nested if/else constructs that enclose the rest of the script like this:
// select the active item in the project window
// and make sure it's a comp
var myComp = app.project.activeItem;
if(myComp instanceof CompItem) {
// make sure one or more layers are selected
var myLayers = myComp.selectedLayers;
if(myLayers.length > 0){
[the rest of the script goes in here]
}else{
alert("select at least one layer to precompose.");
}
}else{
alert("please select a composition.");
}
The first line of this code (ignoring the comments) is creating a new object called "myComp" that is set equal to whatever the active item is in the project window. We're assuming it's a comp, but we need to make sure, because if it isn't and we proceed ahead anyway, the script will abort - which is always bad form. So the next line of code tests to see if it is a comp by using the JavaScript operator "instanceof" to see if "myComp" is an object of type "CompItem". If it is, execution of the script continues with the next statement, if not, execution jumps to the alert at the end of the code (the "else" clause of our first if/else) and generates an error message dialog for the user that looks like this:
For the second test, we create a new object called "myLayers" that consists of the collection of selected layers in "myComp". We then test the length of "myLayers", which will be greater than zero if any layers are selected. If so, we continue on with the rest of the script. If not, we pop down to the alert that displays this dialog:
So if either of the requirements to run the script is not met, the appropriate helpful dialog is displayed and the script exits gracefully without doing anything.
Before and After
Before we get into the real guts of the code, let's take a look at how a successful running of the script should look. Here's our little comp with our two layers selected before we run the script:
Here's what it looks like after the script runs:
Notice that the two selected layers have been replaced by a precomp named "comp jellies.avi". The name of the new comp was constructed by concatenating the name of the first selected layer to "comp ". Notice that the new comp is trimmed to exactly fit the combined duration of the selected layers.
Back to the Code
OK - let's go through the chunks of code involved with setting up and creating the new precomp. Here's the first chunk:
// set new comp's default In and Out points to those of first layer
var newInPoint = myLayers[0].inPoint;
var newOutPoint = myLayers[0].outPoint;
Here we're just setting up two new variables that will end up being the In point and Out point of the new comp. For now we're just initializing them to the In and Out points of the first layer selected. Remember that "myLayers" is the object that contains the collection of selected layers. It turns out that these are in the order that they were selected - so myLayers[0] is the first layer that was selected.
A little later in the code we'll be looking at each selected layer and deciding if we should be using its In point or Out point so here we're just initializing these variables so we'll have something to compare to later.
Naming the Comp
The next block of code creates the name for the new comp. Here's the code:
// create new comp name of "comp " plus name of first layer
var newCompName = "comp ";
var layerName = myLayers[0].name;
if(layerName.length > 26) {
layerName = layerName.substring(0, 26);
}
newCompName += layerName;
First we create a new string variable named "newCompName" and initialize it to the string "comp ". Then we create another new string variable named "layerName" and set it to the name of the first-selected layer in the selected-layers collection "myLayers". We then check to see if the layer name is longer than 26 characters. We do this because AE has a maximum layer name length of 31 characters and we're going to use five of them with "comp ". If the layer name is longer than 26 characters we just use the first 26 characters. We then append the layer name (or the first 26 characters) to "comp " to create the new layer name.
Create an Array of Layer Indices
The "precompose" method that we're going to use a little farther down in the code requires, as one of its inputs, an array that contains the layer indices of the layers to be moved to the new comp. Here we'll build the array by looping through the collection of selected layers and picking up their layer index values and adding them to the array. This is also where we'll piggy-back on the loop through the selected layers to determine the earliest layer In point and the latest Out point. Here's the code:
// "precompose" expects an array of layer indices
var layerIndices = new Array();
for (var i = 0; i < myLayers.length; i++) {
layerIndices[layerIndices.length] = myLayers[i].index;
// make sure new comp In point is In point of earliest layer
// and new comp Out point is Out point of latest layer
if (myLayers[i].inPoint < newInPoint) newInPoint = myLayers[i].inPoint;
if (myLayers[i].outPoint > newOutPoint) newOutPoint = myLayers[i].outPoint;
}
Creating the Comp
Now we have everything necessary to create the new comp. Here we use the "precomp" method of the layers collection object to precompose the selected layers. "precomp" expects three parameters. The first is the array of layer indices of layers to be precomposed. The second is the name of the new comp. You set the third parameter to "true" if you want the precomp option "Move all attributes into the new composition", which we do in this case. Here's the code that creates the new comp:
// create the new comp
var newComp = myComp.layers.precompose(layerIndices, newCompName, true );
Setting the In and Out Points of the New Comp
At this point our selected layers have been replaced by our new comp. The last thing we need to do is set the In and Out Points of the new comp. It turns out that this new comp will be the selected layer in the old comp. So we create a new object "preCompLayer" and set it equal to the currently selected layer in the old comp (which is the new comp). Our variable "newInPoint" has the value that we need for the new comp's In Point and the variable "newOutPoint" has the value we need for the new comp's Out Point. Here's the code we need to set the inPoint and outPoint attributes for the new comp:
// set in and out points of new comp
var preCompLayer = myComp.selectedLayers[0];
preCompLayer.inPoint = newInPoint;
preCompLayer.outPoint = newOutPoint;
That's it - we're done!