lucemia
lucemia

Reputation: 6617

How to convert kivy to a video file

I wrote a kivy app to render some animation on linux server. Is there a good way to convert the animation to a video file directly?

Currently I tried Xvfb + ffmpeg approach. However it has some issues I want to avoid such as:

Upvotes: 2

Views: 666

Answers (1)

Nykakin
Nykakin

Reputation: 8747

You can use kivy.uix.widget.Widget.export_to_png in order to save Widget into a image file after every frame and then build a movie using tools like ffmpeg or cv2 library, but that would slow down the animation since saving data to disk tooks time. So here's an alternative approach:

from functools import partial

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.animation import Animation
from kivy.graphics import Fbo, ClearColor, ClearBuffers, Scale, Translate

Builder.load_string('''
<MyWidget>:
    Button:
        size_hint: 0.4, 0.2
        pos_hint: {'center_x' : 0.5, 'center_y' : 0.5}
        text: 'click me'
        on_press: root.click_me(args[0])
''')

class MyWidget(FloatLayout):
    def click_me(self, button, *args):    
        anim = Animation(
            size_hint = (0.8, 0.4)
        )

        textures = []
        anim.bind(on_complete=partial(self.save_video, textures))
        anim.bind(on_progress=partial(self.save_frame, textures))            

        anim.start(button)   

    # modified https://github.com/kivy/kivy/blob/master/kivy/uix/widget.py#L607
    def save_frame(self, textures, *args):
        if self.parent is not None:
            canvas_parent_index = self.parent.canvas.indexof(self.canvas)
            if canvas_parent_index > -1:
                self.parent.canvas.remove(self.canvas)

        fbo = Fbo(size=self.size, with_stencilbuffer=True)

        with fbo:
            ClearColor(0, 0, 0, 1)
            ClearBuffers()
            Scale(1, -1, 1)
            Translate(-self.x, -self.y - self.height, 0)

        fbo.add(self.canvas)
        fbo.draw()
        textures.append(fbo.texture)  # append to array instead of saving to file
        fbo.remove(self.canvas)

        if self.parent is not None and canvas_parent_index > -1:
            self.parent.canvas.insert(canvas_parent_index, self.canvas)

        return True        

    def save_video(self, textures, *args):
        for i, texture in enumerate(textures):
            texture.save("frame{:03}.png".format(i), flipped=False)       

class MyApp(App):
    def build(self):        
        return MyWidget()

if __name__ == '__main__':
    MyApp().run()

I modified export_to_png method so it doesn't try to save the texture into file but instead it appends it into a list. Then when animation is over, I save all the data into the separate images. It'd be good to add some kind of "animation is saving..." modal, since during that time application is less responsive.

Upvotes: 3

Related Questions