Copy link to clipboard
Copied
I find myself constantly crafting individual keyframes manually, so being able to automatically select the next/previous keyframe on a selected property with a shortcut (assigned to a script, since there's no such functionality in After Effects, being "Select Previous/Next Keyframes" the closest thing) instead of locating the keyframe and clicking on it, would save me, and everyone, hours of pointing and clicking.
After some research I thought It was not possible but after some more (looking at scripts like this AE ENHANCERS • View topic - Exponential Scaling and Re: How can I access the layer keyframes?) , I've started to wonder if it is scriptable.
Hope it is clear what I'm trying to achieve;
Basically I'd like that pressing Shift+K would take me to the next keyframe in the selected property AND select it (Shift+J for the previous one) and
pressing Ctrl+Shift+K would select the next keyframe in the selected property without actually moving the Current Time Indicator (Ctrl+Shift+J for the previous one).
(I think it makes sense and it is consistent with the almighty J and K shortcuts but I didn't know how to write the code or ask for this functionality. Haven't been lucky asking to fellow animators either).
Thanks,
Oliver
Hi again,
i don't know how to reference a new short cut. As i said it might be somehow possible with an AppleScript but i don't know how that works, and it won't work on Windows anyway.
Here is another "solution", it works and is actually quite useful:
Copy link to clipboard
Copied
Hi Oliver,
i think you should define precisely what you expect to happen in special situations like:
It's probably because of all these amibiguities that those shortcut do not exist.
If your "key" column is activated in the timeline panel, you can jump to prev/next key easily with the two buttons, and the action is always well-defined and unambiguous.
If you have a precise idea of what should happen in various situations, the code to achieve it through a UI button is actually not difficult. Through a shortcut it is another story (probably possible with a Mac, with Windows i dont think so).
Xavier.
Copy link to clipboard
Copied
Hey Xavier!
Well as you can imagine, it would mainly be select next/previous keyframe of the active property (a keyframe should be selected), or else, do nothing. For example:
My priority would be the select the next keyframe, rather the select AND jump to because, like you say, J and K do that somehow (without the selecting part but hey, better than nothing).
And I guess if this works as a script (or button), you could assign it a shortcut, like you can do with your first scripts if I'm correct?
And thankss a lot! (as you can see, I have no idea of how to script this:$ ).
Oliver
Copy link to clipboard
Copied
Hi again,
i don't know how to reference a new short cut. As i said it might be somehow possible with an AppleScript but i don't know how that works, and it won't work on Windows anyway.
Here is another "solution", it works and is actually quite useful:
What the shorcut will do: select the keyframe right under the CTI for all selected properties. Any other key is deselected.
It's not exactly what you asked but... i think it's better: Use J/K to navigate and that new shortcut to actually select.
If you want to stick to your original idea, it gives you a base to start with.
Note: a script cannot know if a property is revealed or not in the timeline panel, so you have to actually select properties for it to work.
Xavier.
$.global.script_doSelectCurrentKeys || (script_doSelectCurrentKeys = function script_doSelectCurrentKeys(){
var comp = app.project.activeItem, props, n, p, idx, DT;
if (comp instanceof CompItem){
DT = Math.min(0.01, comp.frameDuration/10.0);
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
; if (!p.numKeys) continue;
idx = p.nearestKeyIndex(comp.time);
if (Math.abs(p.keyTime(idx)-comp.time)<DT){
if (p.selectedKeys.length>0) p.selected = false;
p.setSelectedAtKey(idx, true);
}
else if (p.selectedKeys.length>5){
p.selected = false;
p.setSelectedAtKey(1, true);
p.setSelectedAtKey(1, false);
}
else{
while (p.selectedKeys.length) p.setSelectedAtKey(p.selectedKeys[0], false);
};
};
};
return;
});
script_doSelectCurrentKeys();
Copy link to clipboard
Copied
Woohoo! Almost there!!
To me it would be perfect if you could select the next keyframe without having to navigate to it first. Like executing the script and the next keyframe is selected even if the CTI stays on the previous one (now deselected).
Is that possible (I can't even tell which variable in the script is related to the CTI position)? That way you wouldn't have to navigate to it to change the keyframe, just use this shortcut then press Ctrl+Shift+K for example to enter its speed/influence values, (making the process even faster).
It's super cool that the script works with the property selected even if the layer is "folded", and that it works across properties in different layers!
And you're right about the shortcuts; almost everything is taken but, luckily, the most perfect ones for this script (Shift+K for selecting the next keyframe, and Shift+J for selecting the previous) are not (at least on v11).
Btw silly question, but what value should be inverted so it works for the previous keyframe?
Ah my scripting igorance makes me feel stupid but again, thanks a lot dude!!
Oliver
Copy link to clipboard
Copied
I think your select prev/next key stuff will be more a source of trouble than time saving, especially if the properties are folded, but well...
You will need 2 scripts like the one above, one for each shortcut (Shift+J / Shift+K) and even 2 more if you stick to your original idea with (Ctrl+Shift+J/K).
Give them names so that they always are in the same position in your script collection and bind those scripts.
For the code, it is very similar to the example above. For instance to select the next key:
$.global.script_doSelectNextKeys || (script_doSelectNextKeys = function script_doSelectNextKeys(){
var comp = app.project.activeItem, props, n, p, idx;
if (comp instanceof CompItem){
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
; // if the property has no key, or no selected key, skip
if (!p.numKeys || p.selectedKeys.length<1) continue;
// idx: the index of the last selected keys (safe, that array is always sorted)
idx = p.selectedKeys[p.selectedKeys.length-1];
// if the index of not the index of the last key, proceed
if (idx < p.numKeys){
// deselect the property to deselect all of its keys
p.selected = false;
// reselect the next key
p.setSelectedAtKey(idx+1, true);
}
// in any other situation, deselect all keys
else if (p.selectedKeys.length>5){
// faster way to deselect plenty of keys
p.selected = false;
p.setSelectedAtKey(1, true);
p.setSelectedAtKey(1, false);
}
else{
// default way to deselect keys
while (p.selectedKeys.length) p.setSelectedAtKey(p.selectedKeys[0], false);
};
};
};
return;
});
script_doSelectNextKeys();
To select the previous key, replace
idx = p.selectedKeys[p.selectedKeys.length-1];
// if the index of not the index of the last key, proceed
if (idx < p.numKeys){
// deselect the property to deselect all of its keys
p.selected = false;
// reselect the next key
p.setSelectedAtKey(idx+1, true);
}
by
idx = p.selectedKeys[0];
if (1<idx){
p.selected = false;
p.setSelectedAtKey(idx-1, true);
}
The rest is exactly the same, give another name to the function (script_doSelectNextKeys ---> script_doSelectPrevKeys) , to the script, etc.
Xavier.
Copy link to clipboard
Copied
Man this is beautiful!
I see what you mean, but it was originally thought to speed up changing key values through a single property, and if you have to aim at a time so the script uses the key under the current CTI position well, that's another extra mouse step and you could as well just click on the keyframe. However that version might be ideal when you want to select all keys in the properties at that given time. For sure I'll keep it and I'm going to see if it can have another shortcut, (the similar ones seem already taken, but maybe it could be Shift+U, Shift+I, or something).
Needs practical testing but I'd say this version is done! One more question though: can you make it not deselect the last one if there are no more 'next' keys? That way if you press the shortcut a lot of times to get you to the last keyframe fast you don't have to worry about getting "out" of the keys and having to click on the property again. (Don't know if that would be another condition before the else if of maybe remove one of the existing ones?).
Forever Thanks.
Oliver
Copy link to clipboard
Copied
To keep the last key in all situations, the code is actually shorter
$.global.script_doSelectNextKeys || (script_doSelectNextKeys = function script_doSelectNextKeys(){
var comp = app.project.activeItem, props, n, p, idx;
if (comp instanceof CompItem){
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
; if (!p.numKeys || p.selectedKeys.length<1) continue;
idx = p.selectedKeys[p.selectedKeys.length-1]; //(*)
p.selected = false; //(*)
p.setSelectedAtKey(Math.min(idx+1, p.numKeys), true); //(*)
};
};
return;
});
script_doSelectNextKeys();
For the prev key function, replace the lines marked with a (*) by
idx = p.selectedKeys[0];
p.selected = false;
p.setSelectedAtKey(Math.max(idx-1, 1), true);
Xavier
Copy link to clipboard
Copied
BOOOOOMM! Dude you nailed it!!
I'm so happy right now just toggling through the keyframes XD This would have saved me hours, and I'm sure it will!
It's a little crazy with a lot of keyframes spread in time in several layers but even then it still works like a charm, (plus it's intended for fewer properties or more evenly distributed sets of keyframes anyway).
On last question (I promise), for the other option (the one with the CTI jump as when pressing J/K), how can you make the CTI jump to the selected keyframe (if multiple, I guess it should jump to the one with higher time value)?
Thanks again for all you patience, (I know you're probably super busy with work and this bordering on abuse already), you're the man!
Oliver
Copy link to clipboard
Copied
To jump to the last selected key:
$.global.script_doJumpToLastSelectedKey || (script_doJumpToLastSelectedKey = function script_doJumpToLastSelectedKey(){
var comp = app.project.activeItem, props, n, p, max=-Infinity;
if (comp instanceof CompItem){
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
; if (p.numKeys && p.selectedKeys.length>0) max=Math.max(max, p.keyTime(p.selectedKeys[p.selectedKeys.length-1]));
};
if (-Infinity<max) comp.time = max;
};
return;
});
script_doJumpToLastSelectedKey();
For the first key instead, replace
That's it. One last thing, lots of things in this post now, it starts to be a little bit messy, so please quote answered, and preferably the one for select current time keys, it might be of interest to other users. The other ones i'm not sure ![]()
Xavier.
Copy link to clipboard
Copied
Yup, you're right! Marked as answered!
That last one is not working though, or it is, but what I meant is for it to select the next key And jump he CTI. I tried combining the last two versions but: Result: undefined.
$.global.script_doSelectNextKeys || (script_doSelectNextKeys = function script_doSelectNextKeys(){
var comp = app.project.activeItem, props, n, p, idx, max=-Infinity;
if (comp instanceof CompItem){
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
; if (p.numKeys && p.selectedKeys.length>0) max=Math.max(max, p.keyTime(p.selectedKeys[p.selectedKeys.length-1]));
idx = p.selectedKeys[p.selectedKeys.length-1]; //(*)
p.selected = false; //(*)
p.setSelectedAtKey(Math.min(idx+1, p.numKeys), true); //(*)
};
if (-Infinity<max) comp.time = max;
};
return;
});
script_doSelectNextKeys();
Oliver
Copy link to clipboard
Copied
Sorry to insist, but i think your shortcuts are super confusing!!! Should be better with elementary operations like J/K and select current imo...
$.global.script_doSelectNextKeys || (script_doSelectNextKeys = function script_doSelectNextKeys(){
var comp = app.project.activeItem, props, n, p, idx, max=-Infinity;
if (comp instanceof CompItem){
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
if (p.numKeys && p.selectedKeys.length>0){
// get index of the last selected key
idx = p.selectedKeys[p.selectedKeys.length-1];
// change to next key
if (idx<p.numKeys) ++idx;
// select only that key
p.selected = false;
p.setSelectedAtKey(idx, true);
// update the time
max=Math.max(max, p.keyTime(idx);
};
};
// this sets the composition time (CTI) to max
if (-Infinity<max) comp.time = max;
};
return;
});
script_doSelectNextKeys();
Try to figure out the prev case!!! The first key has index 1, and the first selected key is p.selectedKeys[0]...
Xavier
Copy link to clipboard
Copied
Here here!
SelectJumpNextKey:
In a selected keyframe/keyframes, will select the next one in time AND jump the CTI to the time of that keyframe or, if multiple selected, the last of the selected keyframes.
(If you don't want the time jump but only the selection, just remove the second $.global.script_doJumpToLastSelectedKey script).
$.global.script_doSelectNextKeys || (script_doSelectNextKeys = function script_doSelectNextKeys(){
var comp = app.project.activeItem, props, n, p, idx, max=-Infinity;
if (comp instanceof CompItem){
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
; if (p.numKeys && p.selectedKeys.length>0){
// get index of the last selected key
idx = p.selectedKeys[p.selectedKeys.length-1];
// change to next key
if (idx<p.numKeys) ++idx;
// select only that key
p.selected = false;
p.setSelectedAtKey(idx, true);
// update the time
max=Math.max(max, p.keyTime(idx));
};
};
// this sets the composition time (CTI) to max
if (-Infinity<max) comp.time = max;
};
return
});
script_doSelectNextKeys();
$.global.script_doJumpToLastSelectedKey || (script_doJumpToLastSelectedKey = function script_doJumpToLastSelectedKey(){
var comp = app.project.activeItem, props, n, p, max=-Infinity;
if (comp instanceof CompItem){
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
; if (p.numKeys && p.selectedKeys.length>0) max=Math.max(max, p.keyTime(p.selectedKeys[p.selectedKeys.length-1]));
};
if (-Infinity<max) comp.time = max;
};
return;
});
script_doJumpToLastSelectedKey();
And this (nevermind the more than possible coding aberrations on my part) is what I originally aimed for. I think that not only it is simply amazing ("It just works") but so intuitive (specially using Shift+J/K) that should be an After Effects default.
Your version (select keyframes under current time in the selected properties) is super great too, but it serves a whole different purpose.
Now, for the SelectJumpPrevKey, I just have to figure how to make this bit:
$.global.script_doJumpToFirstSelectedKey || (script_doJumpToFirstSelectedKey = function script_doJumpToFirstSelectedKey(){
var comp = app.project.activeItem, props, n, p, max=-Infinity;
if (comp instanceof CompItem){
props = comp.selectedProperties;
for (n=0; n<props.length; n++){
p = props
; if (p.numKeys && p.selectedKeys.length>0) max=Math.max(max, p.keyTime(p.selectedKeys[p.selectedKeys.length-1]));
};
if (-Infinity<max) comp.time = max;
};
return;
});
script_doJumpToFirstSelectedKey();
actually go to the first keyframe selected and not the last (which, as simple as it may seem, to me is like watching the Matrix code and pushing random buttons in russian to see what happens).
Sorry I couldn't make your last code work and insted just stitched to scripts together! for some reason, the CTI wasn't jumping.
Mega thanks!!
Oliver
Copy link to clipboard
Copied
This is great, but is there any way to also select every property that has a key assigned to it before this runs? Thanks!
Get ready! An upgraded Adobe Community experience is coming in January.
Learn more