Skip to main content
May 9, 2014
Question

convert Ae keyframes info to cubicbezier points

  • May 9, 2014
  • 2 replies
  • 7196 views

hey guys does anyone know how to convert the influence and speed of keyframes into cubic-bezier points? just wondering if there was a formula.

This topic has been closed for replies.

2 replies

Participant
July 7, 2017

OK! For anyone interested, I managed to make it work with multiple dimensions! As some properties cannot be separated (Like Scale).

I also made it work for any case of val1==val2.

Here is how is works:

For multiple dimensions, keyValue() returns an array instead of a float. So another for() made it work. Easy right?

When val1==val2, the trick was to add temporary keyframes at the extreme points of the curves, such as this one:

Correct me if I wrong, but in css for instance, a cubic-bezier the value to be animated needs to be different from from start to end.

Here is the final script.

//start--------------  

var curItem = app.project.activeItem;

var selectedLayers = curItem.selectedLayers;

var selectedProperties = curItem.selectedProperties;

if (selectedLayers == 0) {

   alert("Please Select at least one Layer");

}

else if (selectedLayers != 0) {

   for (var i = 0; i < selectedLayers.length; i++) {

   for (var f in selectedProperties) {

   var currentProperty = selectedProperties[f];

   if (currentProperty.numKeys > 1) {

   for (var i = 1; i < currentProperty.numKeys; i++) {

   var t1 = currentProperty.keyTime(i);

   var t2 = currentProperty.keyTime(i + 1);

   var val1 = currentProperty.keyValue(i);

   var val2 = currentProperty.keyValue(i + 1);

   //converting float to array

   if (val1.constructor !== Array) {

   var temp = new Array();

   temp[0] = val1;

   val1 = temp;

  }

   if (val2.constructor !== Array) {

   var temp = new Array();

   temp[0] = val2;

   val2 = temp;

  }

   //multi dimension values

   for (var y = 0; y < val1.length; y++) {

   var delta_t = t2 - t1;

   var c_val1 = parseFloat(val1[y]);

   var c_val2 = parseFloat(val2[y]);

   var delta = c_val2 - c_val1;

   avSpeed = Math.abs(delta) / (delta_t);

   /* $.write("val1 " + c_val1 + "--");

  $.write("val2 " + c_val2 + "\n");

  $.write("avSpeed " + avSpeed + "\n");*/

   $.writeln('dimension ' + y + ':\n');

   //values dispatch

   if (c_val1 != c_val2) {

   getBezier(currentProperty, i, c_val1, c_val2, avSpeed);

  }

   else { //if (c_val1 == c_val2)

   var minmax = getMinMaxPoints(t1, t2, currentProperty, curItem);

   var maxp_val = minmax.maxPoint.value;

  

   var hasmax = true;

   var hasmin = true;

   var minp_val = minmax.minPoint.value;

   if (!(c_val1 < maxp_val) || !(c_val1 > minp_val)) {

   if (c_val1 < maxp_val) {

   //create temp keyframe to "cut" the curve

   var tempkey_i = currentProperty.addKey(minmax.maxPoint.time);

   var tempavSpeed = Math.abs(maxp_val - c_val1) / (minmax.maxPoint.time - t1);

   //get the 2 bezier-curves

   getBezier(currentProperty, i, c_val1, maxp_val, tempavSpeed);//part going up

   getBezier(currentProperty, tempkey_i, maxp_val, c_val1, tempavSpeed);//part going down

   currentProperty.removeKey(tempkey_i);//remove temp key

  }else {

   hasmax = false;

  }

   if (c_val1 > minp_val) {

   var tempkey_i = currentProperty.addKey(minmax.minPoint.time);

   var tempavSpeed = Math.abs(maxp_val - c_val1) / (minmax.minPoint.time - t1);

   getBezier(currentProperty, i, c_val1, minp_val, tempavSpeed);//part going down

   getBezier(currentProperty, tempkey_i, minp_val, c_val1, tempavSpeed);//part going up

   currentProperty.removeKey(tempkey_i);//remove temp key

  }else{

   hasmin = false;

  }

   if(!hasmax || !hasmin)//if flat curve

   $.writeln("keyframe " + i + " has no animation\n\n");

  } else { //if (c_val1 < maxp_val && c_val1 > minp_val)

   //sort values

   $.write('equal minmax ');

   var first_time, sec_time, first_val, sec_val;

   if (minmax.maxPoint.time < minmax.minPoint.time) {

   //max value if first:

   first_time = minmax.maxPoint.time;

   first_val = maxp_val;

   sec_time = minmax.minPoint.time;

   sec_val = minp_val;

  } else {

   //min value if first:

   first_time = minmax.minPoint.time;

   first_val = minp_val;

   sec_time = minmax.maxPoint.time;

   sec_val = maxp_val;

  }

   // we have 2 extreme points, we need to "cut" twince (it gives 3 curves)

   var tempkey_i = currentProperty.addKey(first_time);

   var tempkey2_i = currentProperty.addKey(sec_time);

   var tempavSpeed_1 = Math.abs(first_val - c_val1) / (first_time - t1);//avspeed 1rst curve

   var tempavSpeed_2 = Math.abs(sec_val - first_val) / (sec_time - first_time);//avspeed 1nd curve

   var tempavSpeed_3 = Math.abs(c_val2 - sec_val) / (t2 - sec_time);//avspeed 3rd curve

   //get the 3 bezier-curves

   getBezier(currentProperty, i, c_val1, first_val, tempavSpeed_1);

   getBezier(currentProperty, tempkey_i, first_val, sec_val, tempavSpeed_2);

   getBezier(currentProperty, tempkey2_i, sec_val, c_val2, tempavSpeed_3);

   currentProperty.removeKey(tempkey_i);//remove temp key

   currentProperty.removeKey(tempkey2_i);//remove temp key

  }

  }//end value dispatch

  }

  }

  }

  }

  }

}

function getMinMaxPoints(t1, t2, currentProperty, curItem) {

   var P = currentProperty;

   var t = t1;

   var min = 999999;

   var max = -999999;

   var tmin = 0;

   var tmax = 0;

   while (t <= t2) {

   var val = P.valueAtTime(t, true);

   if (val > max) {

   max = val;

   tmax = t;

  }

   if (val < min) {

   min = val;

   tmin = t;

  }

   t += curItem.frameDuration;

  }

   if (max == -999999)

   max = P.valueAtTime(t1, true);

   if (min == 999999)

   min = P.valueAtTime(t1, true);

   var maxPoint = {

   value: max,

   time: tmax

  }

   var minPoint = {

   value: min,

   time: tmin

  }

   return { minPoint: minPoint, maxPoint: maxPoint }

}

function getBezier(currentProperty, i, c_val1, c_val2, avSpeed) {

   if (c_val1 < c_val2) {

   x1 = currentProperty.keyOutTemporalEase(i)[0].influence / 100;

   y1 = x1 * currentProperty.keyOutTemporalEase(i)[0].speed / avSpeed;

   x2 = 1 - currentProperty.keyInTemporalEase(i + 1)[0].influence / 100;

   y2 = 1 - (1 - x2) * (currentProperty.keyInTemporalEase(i + 1)[0].speed / avSpeed);

  }

   if (c_val2 < c_val1) {

   x1 = currentProperty.keyOutTemporalEase(i)[0].influence / 100;

   y1 = (-x1) * currentProperty.keyOutTemporalEase(i)[0].speed / avSpeed;

   x2 = currentProperty.keyInTemporalEase(i + 1)[0].influence / 100;

   y2 = 1 + x2 * (currentProperty.keyInTemporalEase(i + 1)[0].speed / avSpeed);

   x2 = 1 - x2;

  }

   $.writeln("keyframe: " + i + " Cubic-bezier[" + x1.toFixed(2) + ", " + y1.toFixed(2) + ", " + x2.toFixed(2) + ", " + y2.toFixed(2) + "]\n\n");

}

//end--------------  

I made a couple CSS test using “animation-timing-function”. The result looks pretty good!

What do you guys think?

UQg
Legend
May 10, 2014

For what kind of property do you need it ?

May 10, 2014

ideally i would like to be able to convert any layer property, but at least position, scale, rotation, and opacity.

UQg
Legend
May 10, 2014

if prop is your property, and idx the index of a key (not the last one)

you need:

//    t1 = prop.keyTime(idx),

//    t2 = prop.keyTime(idx+1),

//    val1 = prop.keyValue(idx),

//    val2 = prop.keyValue(idx+1),

//    delta_t = t2-t1,

//    easeOut1 = prop.keyOutTemporalEase(idx);

//    easeIn2 = prop.keyInTemporalEase(idx+1);

for each coordinate k (k=0,1, 2) of the property value, the 2D Bézier segment representing the coordinate k over time has :

  • vertices:

               [t1, val1], [t2, val2]

  • out tangent at the first vertex:

               (easeOut1.influence /100) * delta_t * [1, easeOut1.speed];

  • in tangent at the 2nd vertex:

                (-easeIn2.influence /100) * delta_t * [1, easeIn2.speed];

Xavier.