-
Issue with Aligning Keyframes from Multiple Properties to CTI in After Effects
Hi everyone,
I’m working on an After Effects script that aligns selected keyframes (or layers) to the playhead (Current Time Indicator). While it works well when keyframes are selected from a single property, the script only processes the first property when multiple properties with selected keyframes are involved.
Here’s the section of the script that handles the keyframe processing:
// Function to handle sequence arrangement
arrangeTPH.onClick = function () {
var comp = app.project.activeItem;
// Check if the active item is a composition
if (!(comp && comp instanceof CompItem)) {
alert("Please select an active composition.");
return;
}
var layers = comp.selectedLayers; // Get the selected layers
var cti = comp.time; // Current Time Indicator (CTI)
// Check if there are selected layers or keyframes
if (layers.length === 0) {
alert("Please select at least one layer or keyframes.");
return;
}
app.beginUndoGroup("Arrange to Playhead"); // Start undo group
var keyframesMoved = false; // Flag to check if keyframes were moved
// Function to process keyframes for a given property
function processKeyframes(prop, cti) {
// Skip if the property is not time-varying or has no selected keyframes
if (!prop.isTimeVarying || prop.selectedKeys.length === 0) {
return false;
}
var selectedKeys = prop.selectedKeys.slice(); // Copy selected keyframe indices
var firstKeyTime = prop.keyTime(selectedKeys[0]); // Get the time of the first keyframe
var offset = cti - firstKeyTime; // Calculate the time offset
var keyframesData = []; // Array to store keyframe data
// Save data for all selected keyframes
for (var i = 0; i < selectedKeys.length; i++) {
var keyIndex = selectedKeys[i];
keyframesData.push({
time: prop.keyTime(keyIndex) + offset, // Adjusted keyframe time
value: prop.keyValue(keyIndex), // Keyframe value
inEase: prop.keyInTemporalEase(keyIndex), // Temporal ease (in)
outEase: prop.keyOutTemporalEase(keyIndex), // Temporal ease (out)
inInterp: prop.keyInInterpolationType(keyIndex), // Interpolation type (in)
outInterp: prop.keyOutInterpolationType(keyIndex), // Interpolation type (out)
spatial: prop.propertyValueType === PropertyValueType.TwoD_SPATIAL ||
prop.propertyValueType === PropertyValueType.ThreeD_SPATIAL ? {
inTangent: prop.keyInSpatialTangent(keyIndex), // Spatial in-tangent
outTangent: prop.keyOutSpatialTangent(keyIndex), // Spatial out-tangent
autoBezier: prop.keySpatialAutoBezier(keyIndex), // Auto Bezier flag
continuous: prop.keySpatialContinuous(keyIndex), // Continuous spatial flag
roving: prop.keyRoving(keyIndex) // Roving keyframe flag
} : null
});
}
// Remove the selected keyframes from the property
for (var i = selectedKeys.length - 1; i >= 0; i--) {
prop.removeKey(selectedKeys[i]);
}
// Add the new keyframes with updated times
for (var i = 0; i < keyframesData.length; i++) {
var data = keyframesData[i];
var newKeyIndex = prop.addKey(data.time);
prop.setValueAtKey(newKeyIndex, data.value);
prop.setInterpolationTypeAtKey(newKeyIndex, data.inInterp, data.outInterp);
prop.setTemporalEaseAtKey(newKeyIndex, data.inEase, data.outEase);
// Handle spatial properties if applicable
if (data.spatial) {
prop.setSpatialTangentsAtKey(newKeyIndex, data.spatial.inTangent, data.spatial.outTangent);
prop.setSpatialContinuousAtKey(newKeyIndex, data.spatial.continuous);
prop.setSpatialAutoBezierAtKey(newKeyIndex, data.spatial.autoBezier);
prop.setRovingAtKey(newKeyIndex, data.spatial.roving);
}
}
return true;
}
// Process all selected layers
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
var selectedProperties = layer.selectedProperties; // Get selected properties of the layer
if (selectedProperties && selectedProperties.length > 0) {
for (var j = 0; j < selectedProperties.length; j++) {
var prop = selectedProperties[j];
if (processKeyframes(prop, cti)) {
keyframesMoved = true;
}
}
}
}
// If no keyframes were moved, align selected layers to the playhead
if (!keyframesMoved) {
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
var offset = cti - layer.inPoint; // Calculate time offset for the layer
layer.startTime += offset; // Adjust the layer's start time
}
}
app.endUndoGroup(); // End undo group
alert(keyframesMoved ? "Keyframes aligned to the playhead." : "Layers aligned to the playhead.");
};
What the script does:
- Aligns selected keyframes of a property to the playhead by calculating an offset based on the first keyframe.
- If no keyframes are selected, it adjusts the layer’s starTime to align with the playhead.
The problem: When multiple properties (e.g., Position, Scale, Opacity) are selected with keyframes, the script processes only the first property and ignores the rest.
What I’ve tried:
- Iterating over all selectedProperties of a layer.
- Adding checks to ensure that each property is processed independently.
Request: Could you help me figure out why only the first property is being processed and how to ensure that all selected properties are included?
Any advice, corrections, or improvements to the logic are greatly appreciated.
Thanks in advance! 😊