Creative Communities of the World Forums

The peer to peer support community for media production professionals.

Activity Forums Adobe After Effects Expressions Issue with Aligning Keyframes from Multiple Properties to CTI in After Effects

  • Issue with Aligning Keyframes from Multiple Properties to CTI in After Effects

    Posted by Vincenzo Imbimbo on January 13, 2025 at 1:27 pm

    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:

    1. Aligns selected keyframes of a property to the playhead by calculating an offset based on the first keyframe.
    2. 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! 😊

    Vincenzo Imbimbo replied 1 month ago 2 Members · 2 Replies
  • 2 Replies
  • Dan Ebberts

    January 13, 2025 at 2:07 pm

    I haven’t tested this at all, but my hunch is that your keyframe processing code changes what’s selected, so you may need to collect the selected properties for all layers before you start manipulating things.

  • Vincenzo Imbimbo

    January 13, 2025 at 10:51 pm

    Hi Dan,

    Thank you so much for your suggestion—it worked perfectly!

    The problem indeed stemmed from the fact that manipulating keyframes was affecting the state of the selected properties. Following your advice, I modified my script to first collect all selected properties and their keyframes before making any changes. By processing the collected data in a separate step, I avoided the issue of altered selections during iteration.

    Thanks again for pointing me in the right direction—it was a game-changer!

We use anonymous cookies to give you the best experience we can.
Our Privacy policy | GDPR Policy