• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Dynamic keyframe value using expression?

New Here ,
Oct 09, 2018 Oct 09, 2018

Copy link to clipboard

Copied

Hi, I'll try my best to explain myself here.

I currently have an animation of a Shape Layer using two keyframes controlling the width of the the shape from 0 px to 100 px.

Now what I want to do is to have the second keyframe value be controlled by an expression (so I can link it & make it "dynamic") WHILE retaining the animation curves (velocity) from my predefined keyframes. Is this at all possible? In my mind it seems pretty basic as I just want to change the value of a specific keyframe while retaining the timing and speed of the predefined keyframes.

All I've seen are solutions where they replace the whole animation with some default ease or linear curve. It's crucial for me here to retain the predefined animation.

I'm thinking a solution would be as simple as this in the expression window:

key(2).value = [x, y]; //where I then would be able to set x to sourceRectAtTime().width of a specific object

Thanks in advance!

TOPICS
Expressions

Views

21.1K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Oct 10, 2018 Oct 10, 2018

Copy link to clipboard

Copied

I'm thinking a solution would be as simple as this in the expression window:

No, it isn't. Keyframe values are read-only to begin with. Any expression can only overwrite/ modify the actual values, not persistently set them in any form. It's inherent in how AE evaluates property streams:

Property base value --> Keyframe value --> Expression result

Furthermore, retaining custom interpolation curves can only be done by referencing an existing curve using valueAtTime() and manipulating the actual time that is used for sampling the value. So if you wanted to reference your existing interpolation, it would still be something like

thisLayer.transform.position.valueAtTime(myTime)[0];

for the X position for instance. myTime could then be defined as your retimed original animation:

myTime=linear(time, key(1).time,key(2).time,newStartTime,newEndTime);

In any case, you need to adapt your thinking. It's more complex than just a single line bit of code.

Mylenium

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Jan 27, 2021 Jan 27, 2021

Copy link to clipboard

Copied

Hello,  sorry for being this late (again). But I had the same problem, and thanks to @chrish18591230  I gave it a different try that worked for me. Here it is :

 

prop = thisProperty;
firstValue = prop.key(1).value;
lastValue = prop.key(2).value;

newValue = 500;//this value is to be changed for a controller or whatever

linear(value, firstValue, lastValue, firstValue, newValue);

 

If you link the controller to the newValue, you will be able to have what you want. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Feb 28, 2023 Feb 28, 2023

Copy link to clipboard

Copied

Right in the money!!!!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Oct 21, 2019 Oct 21, 2019

Copy link to clipboard

Copied

My apologies for posting on a thread that's been inactive for a year, but I was facing this exact same problem in the middle of a big project with a short timeline. Now that the project is over and I've had some time to think about it without a deadline hanging over my head, I think I've solved the problem and wanted to add my solution here in case it helps anyone in the future.

 

 

//reference to the property you've animated
prop = thisProperty
//reference to the first keyframe of the property you've animated
firstKey = prop.key(1).time;
//reference to the last keyframe of the property you've animated
lastKey = prop.key(prop.numKeys).time;
//the total distance traveled between your two keyframes
distanceTotal = prop.valueAtTime(lastKey) - prop.valueAtTime(firstKey);
//the distanced that's already been traveled on any given frame
distanceCurrent = prop.valueAtTime(lastKey) - prop.valueAtTime(time);
//the percentage of the total animation that's been completed on a given frame
percentage = 1 - distanceCurrent/distanceTotal;
//reference to a slider on a control layer that is used to modify the value of our
//final keyframe
offset = thisComp.layer("controls").effect("property offset")("Slider");
//sets the value of the property to be it's current value plus a percentage of the value
//we set with our slider
value + offset*percentage;

 

 

In my example, I was modifying the X Position after having separated dimensions, but this could easily be adapted to any property by just changing what property is being referenced by pos. It works with both positive and negative numbers entered on the slider, allowing you to either increase or decrease the animation, and it handles animations with more than just two keyframes, distributing the offset correctly across the whole animation.

 

The only limitation is that the slider is used to offset the position of your final keyframe, not to set an absolute value for it, but I would think that for the purposes of adjusting templates, that's how you would want it to work anyway.

 

Hopefully this will be useful to someone!

 

*EDIT* I adjusted my expression to just reference the property it's on, so now you can just paste it onto any property you want to adjust and all you'd have to change is relinking offset to your slider control.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Oct 21, 2019 Oct 21, 2019

Copy link to clipboard

Copied

That might work but I think you have created a recursive expression. I am pretty sure that the greater the distance between keyframes the longer it is going to take to make the calculations. I have not checked but I suspect that if the time between keyframes is 10 frames and it takes 10 seconds to render, when you change the time between keyframes to 60 frames, the time to render may increase to something like 100 seconds, but if you change the time between keyframes to 120 frames the time to render may increase to 500 seconds.  Make sure you check that out before you start sending things to the render cue. Any time you add .time to your expression and ask it to look forward or backward you are asking for an exponential increase in the time it takes to make the calculations.  If the times get rediculous the best thing you can do is to try using the Keyframe Assistant to convert the expression to keyframes. 

 

You might want to check out this thread by Dan Ebberts. Check the last entry.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Oct 21, 2019 Oct 21, 2019

Copy link to clipboard

Copied

You are correct, I went ahead and checked this in my test comp and here's what I discovered: In a 4 minute long composition there was a 1 minute difference in render time between a version with keyframes 1 second apart and a version with keyframes 4 minutes apart. There was a 1 second render time difference between versions with keyframes 1 second apart and keyframes 10 seconds apart. So your mileage may vary depending on what you are trying to do.

 

For my purposes, I was working on making dynamically resizeable templates for lower third graphics that only had animation lengths of a few seconds and I barely noticed the impact to render time. If you tried to apply this to a 10 minute long animation you'd definitely notice the increase and in that situation someone far smarter than me could probably come up with a more elegant solution.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 26, 2020 Oct 26, 2020

Copy link to clipboard

Copied

Also appologies for chiming in yet another year later, but... This worked really well for me! Thanks for sharing. I ended up setting my first keyframe value (the width of a rectangle shape) to 0 and the second keyframe to 1 (because the math errors out when you set it to 0 as well). Then I set up an expression on the offset slider control setting the offset equal to the sourceRectAtTime().width value of my text layer + a slider control for padding. Which looked something like this:

var s = thisComp.layer("Name  Placeholder Company  Placeholder");
var w = s.sourceRectAtTime().width;
var p = effect("Padding")("Slider");

w+p

The width of the box will technically be off by 1 pixel, but you'll never hear me complain about it!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jul 07, 2021 Jul 07, 2021

Copy link to clipboard

Copied

Hi there! I know I'm quite late to this thread, but since I'm having the same problem with a lower third I need to make into a mogrt I stumbled upon it and it seems promising. However, when I input the expression into the scale property of my shape layer (non uniform scaling), I get an error on the percentage line. "Error: second argument to div() must be a scalar".
I used a text layer outputting each of the expression lines' values to confirm that they all return numbers and the error really only occurs once the percentage line is hit. I'm stumped, can anyone PLEASE provide me with a clue or even a solution?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Jul 07, 2021 Jul 07, 2021

Copy link to clipboard

Copied

Keyframe values are read-only to begin with. You have to manually enable it.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Feb 05, 2022 Feb 05, 2022

Copy link to clipboard

Copied

Sir, I just logged in to say that you are a genius. Thank you so much.

 

I would like to comment my situation in case it is helpful for someone else:
I was looking for a way to change Keyframes values for data driven animations with json files.

In my Composition I already had my Speed Graph configured with the speeds between the Keyframes. The value I was trying to change was the Trim Path -> End of a donut chart, so the final Trim Path-> End Keyframe value of the chart, gets the value of my json file. So just changing some lines of your code it worked like a charm.

 

file = footage("mydata.json").sourceData;
target = file[0];

prop = thisProperty
firstKey = prop.key(1).time;

// Just referenced my last Keyframe.
lastKey = prop.key(2).time;

distanceTotal = prop.valueAtTime(lastKey) - prop.valueAtTime(firstKey);  
distanceCurrent = prop.valueAtTime(lastKey) - prop.valueAtTime(time);    
percentage = 1-distanceCurrent/distanceTotal;

//Extrapolates the percentage of the original animation value to the percentage of my new animation value
target*percentage;

 

So even if you change your Speed Graph, it will update the animation speed. Again thank you!!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Jan 11, 2023 Jan 11, 2023

Copy link to clipboard

Copied

Would be very useful if you can share your "mydata.json" file

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Jan 27, 2021 Jan 27, 2021

Copy link to clipboard

Copied

Hello,  sorry for being this late (again). But I had the same problem, and thanks to @chrish18591230  I gave it a different try that worked for me. Here it is :

 

prop = thisProperty;
firstValue = prop.key(1).value;
lastValue = prop.key(2).value;

newValue = 500;//this value is to be changed for a controller or whatever

linear(value, firstValue, lastValue, firstValue, newValue);

 

If you link the controller to the newValue, you will be able to have what you want. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Jan 27, 2021 Jan 27, 2021

Copy link to clipboard

Copied

ok sorry, I wanted to move my reply, but I cannot delete my first post !....

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Sep 28, 2022 Sep 28, 2022

Copy link to clipboard

Copied

Thank you thank you! Just what I was looking for 🙂

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Aug 13, 2023 Aug 13, 2023

Copy link to clipboard

Copied

I'm loving this solution, but wondering if this can also work in an array?

Currently using trying to use on the 'Position' property of the Transform effect. What would be the solution for a property that has two dimensions, not just 1?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Aug 13, 2023 Aug 13, 2023

Copy link to clipboard

Copied

 

I tried something liks this, which works, but doesn't take up the easing applied to the keyframes (most likely because I had to chage to the ".time" to make the expression function. Let me know if you have any thoughts!Screenshot 2023-08-13 at 10.20.15 AM.png

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Aug 13, 2023 Aug 13, 2023

Copy link to clipboard

Copied

Just answering my own questions as I guess and check here...this method seems to work for me, tried to clean it up visually.

 

// Keyframe references
var firstValue = key(1).value[0];
var lastValue = key(2).value[0];

// New ending X position
var newValue = effect("Slider Control")("Slider");

x = linear(value[0],firstValue,lastValue,value[0],newValue);

[x,value[1]]

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Aug 13, 2023 Aug 13, 2023

Copy link to clipboard

Copied

UPDATE:

Now I'm trying to navigate instead of aligning to a slider, to assing to a Point Control effect, but I'm stumped...here is where I am, but might been the help of @Dan Ebberts on this one...

var ctrlA = effect("Transform")("Position")[0];
var ctrlB = effect("Transform")("Position")[1];

// Keyframe references
var FirstValue = key(1).value;
var LastValue = key(2).value;

// New ending position
var NewValueX = effect("Point Control")("Point")[0];
var NewValueY = effect("Point Control")("Point")[1];

x = linear(ctrlA,FirstValue[0],LastValue[0],ctrlA,NewValueY);
y = linear(ctrlB,FirstValue[1],LastValue[1],ctrlB,NewValueY);

[x,y]

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 13, 2023 Aug 13, 2023

Copy link to clipboard

Copied

I think I'd try something like this:

var firstValue = key(1).value;
var lastValue = key(2).value;
var newValue = effect("Point Control")("Point");
if (firstValue[0] < lastValue[0])
  x = linear(value[0],firstValue[0],lastValue[0],firstValue[0],newValue[0])
else
  x = linear(value[0],lastValue[0],firstValue[0],newValue[0],firstValue[0]);
if (firstValue[1] < lastValue[1])
  y = linear(value[1],firstValue[1],lastValue[1],firstValue[1],newValue[1])
else
  y = linear(value[1],lastValue[1],firstValue[1],newValue[1],firstValue[1]);
[x,y]

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Aug 13, 2023 Aug 13, 2023

Copy link to clipboard

Copied

@Dan Ebberts I would say yes that works, but functionally, I'm trying to use the key(2) to be the "end point" of the motion, and to make the key(1) the "start point", that's controlled by the Point Control. So the expression drives the animation, but the Point Control says where the origin point is and the key(2) is always the same spot.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 13, 2023 Aug 13, 2023

Copy link to clipboard

Copied

Like this then, I guess:

var firstValue = key(1).value;
var lastValue = key(2).value;
var newValue = effect("Point Control")("Point");
if (firstValue[0] < lastValue[0])
  x = linear(value[0],firstValue[0],lastValue[0],newValue[0],lastValue[0])
else
  x = linear(value[0],lastValue[0],firstValue[0],lastValue[0],newValue[0]);
if (firstValue[1] < lastValue[1])
  y = linear(value[1],firstValue[1],lastValue[1],newValue[1],lastValue[1])
else
  y = linear(value[1],lastValue[1],firstValue[1],lastValue[1],newValue[1]);
[x,y]

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Feb 05, 2024 Feb 05, 2024

Copy link to clipboard

Copied

LATEST

I have an additional question: This expression works for objects that have only two keyframes. What if I would want to add more keyframes after the substituted keyframes and would want the object to play through those keyframes (3,4,...n) using their unaltered values?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines