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

Select Next Keyframe (in selected property) script

Community Beginner ,
Jul 20, 2014 Jul 20, 2014

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

TOPICS
Scripting
4.0K
Translate
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

correct answers 1 Correct answer

Advocate , Jul 21, 2014 Jul 21, 2014

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:

  1. Open After Effects.
  2. Open the script editor (File>Scripts>Open Script File)
  3. Copy paste the code below
  4. Save it in your Scripts folder (not the ScriptUI Panels folder, there is no UI) under a name for which you are sure it will be the first in your scripts
...
Translate
Advocate ,
Jul 20, 2014 Jul 20, 2014

Hi Oliver,

i think you should define precisely what you expect to happen in special situations like:

  • the layer has several keyframes selected
  • the last key is selected and you ask to select the next one (select no key? select only the last? do nothing ???)
  • same with first key already selected and you ask to select the previous.
  • Finally what to do in case several layers are selected and the key times dont match ? In particular if you ask the CTI to jump to next key time, where should it go ?

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.

Translate
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 ,
Jul 20, 2014 Jul 20, 2014

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:

  • If there are several keyframes selected, determine which of the selected keyframes has a higher time value and then select the next one (higher time value, closest in time, not selected) in the property. (And viceversa for the 'Previous' shortcut).
  • If there are no unselected keyframes to jump to, do nothing.
  • If multiple properties/layers are selected, either do nothing or, ideally, determine the keyframe with higher time value and select the next one (closest in time, not selected) in any of the selected layers/property. (I can see how this one gets complicated, jumping between layers/properties).
  • If no keyframe is selected, of course, do nothing.

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

Translate
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
Advocate ,
Jul 21, 2014 Jul 21, 2014

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:

  1. Open After Effects.
  2. Open the script editor (File>Scripts>Open Script File)
  3. Copy paste the code below
  4. Save it in your Scripts folder (not the ScriptUI Panels folder, there is no UI) under a name for which you are sure it will be the first in your scripts collections (for the test, i used $01_SelectCurrentKeys.jsx)
  5. Close After Effects.
  6. Then Open your After Effects Shortcuts.txt (see this thread for details on where to find it, how to edit it: Re: Custom hotkeys)
  7. Choose a shortCut for "ExecuteScriptMenuItem01" (that's the hard part all combinations are taken already)
  8. Save and relaunch AE:

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();

Translate
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 ,
Jul 21, 2014 Jul 21, 2014

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

Translate
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
Advocate ,
Jul 21, 2014 Jul 21, 2014

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.     

Translate
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 ,
Jul 21, 2014 Jul 21, 2014

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

(Here's the draft for the SelectPrevKeys one).

Translate
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
Advocate ,
Jul 21, 2014 Jul 21, 2014

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

Translate
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 ,
Jul 21, 2014 Jul 21, 2014

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

Translate
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
Advocate ,
Jul 21, 2014 Jul 21, 2014

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

  1. max= -Infinity by min=Infinity
  2. the line if (p.numKeys && ... by  if (p.numKeys && p.selectedKeys.length>0) min=Math.min(min, p.keyTime(p.selectedKeys[0]));
  3. the line if (-Infinity<max) comp.time = max; by if (min<Infinity) comp.time = min;

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.

Translate
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 ,
Jul 22, 2014 Jul 22, 2014

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


Translate
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
Advocate ,
Jul 22, 2014 Jul 22, 2014

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

Translate
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 ,
Jul 22, 2014 Jul 22, 2014

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

Translate
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 ,
Jun 14, 2018 Jun 14, 2018
LATEST

This is great, but is there any way to also select every property that has a key assigned to it before this runs? Thanks!

Translate
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