Removing frames from animation (via interface or Python?)

I’m experimenting moving my gif making workflow over to Krita. (Making gifs from video clips, that is.) For this, I need to be able to decrease the frame density. So I’ve imported a short clip at 24 FPS, but for size restrictions, I want to keep only, say, every 2nd or 3rd frame of my animation. I don’t want to have to select the frames to be deleted all manually.

  1. I don’t see a way to do this currently. Is this correct?
  2. If I were to write a plugin for this, how do I even access frames via Python? I have my layer node and it has the animated property, but where are the frames stored?

I can’t find any action to specify a pattern of keyframes for deletion.

I can’t think of how to do that in Krita, but I have one idea for a workaround. I don’t know what video editor you use, I’ve been using Kdenlive mostly. If you import the clip and export it at 200% speed but at the original frame rate, then that will delete every other frame. Still not sure how to get every 3rd frame… maybe 300%? Seems worth a try.

This seems interesting and possibly useful,
Krita Scripting School
especially the part about adding and removing frames.

Ooooh, this is very useful, thank you!

application.action('remove_frames_and_pull').trigger()

is what I want. Very neat!

Now wondering: In the UI I am able to select multiple frames and once and remove them in one go. Does anyone know if I can select multiple frames in Python? The tutorial linked above only shows how to set the current frame/playhead.

(I can work with deleting one frame at a time, though the code might be a bit easier/more performant if I can select and delete them all in one go.)

(EDIT: I moved the post to the plugins section, since we are now in coding territory :wink: )

If you can find a way of selecting frames in a plugin then that would be the way to go.
Good luck :slight_smile:

a bit hackish way to select animation frames…

from krita import Krita
from PyQt5.QtCore import QItemSelectionModel, QItemSelection
from PyQt5.QtWidgets import QTableView


def find_timeline_docker():
    app = Krita.instance()
    for docker in app.dockers():
        if docker.objectName() == 'TimelineDocker':
            return docker


def find_kis_anim_timeline_view():
    timeline_docker = find_timeline_docker()
    for view in timeline_docker.findChildren(QTableView):
        if view.metaObject().className() == 'KisAnimTimelineFramesView':
            return view


def select_anim_frames(frames):
    view = find_kis_anim_timeline_view()
    model = view.model()
    s_model = view.selectionModel()

    first_row = 0
    last_row = model.rowCount() - 1

    new_selection = QItemSelection()
    for frame in frames:
        top_left_index = model.index(first_row, frame)
        bottom_right_index = model.index(last_row, frame)
        new_selection.select(top_left_index, bottom_right_index)

    s_model.clear()
    s_model.select(new_selection, QItemSelectionModel.Select | QItemSelectionModel.Columns)


# testing

app = Krita.instance()
doc = app.activeDocument()

# select every second frame in animation range
select_anim_frames(range(doc.fullClipRangeStartTime(), doc.fullClipRangeEndTime() + 1, 2))

# select first and last frame in animation
# select_anim_frames([doc.fullClipRangeStartTime(), doc.fullClipRangeEndTime() + 1])

# ignore animation range and just select frames 1001, 1234, 900
# select_anim_frames([1001, 1234, 900])

/AkiR

Oof, that’s impressive! (Not sure I want to do that, though…)

You don’t really need a plugin for that. Why not lower the FPS on import and set a skip interval?

Otherwise, you can just export the frames, then reimport them if you want more fine tuned control

On import I don’t know yet how long the result will be (I might clip it during editing) or how well the exported gif compressess, so I don’t know how many frames I need to drop. Sometimes it takes several tries with several exports to get the best trade-off between fie size and smoothness. So I want to avoid any extra steps. It’s fine though, I don’t mind the coding, since once it works I’ll get a lot of use out of it. :wink: