Python Video is stuttering Python Video is stuttering tkinter tkinter

Python Video is stuttering


You mix two different method to work with long-running function.

First using only after()

def player(canvas):    b = video.get_next_data()    frame_image = ImageTk.PhotoImage(Image.fromarray(b))    canvas.create_image(600, 400.5, image= frame_image)    # run it again after 16ms    root.after(16, lambda:player(canvas))# --- main ---root = tk.Tk()root.geometry("1920x1080")canvas = tk.Canvas(root, width="1920", height="1080")canvas.pack()# start playerplayer(canvas)root.mainloop()

Second using threading

def player(canvas):    while True        b = video.get_next_data()        frame_image = ImageTk.PhotoImage(Image.fromarray(b))        canvas.create_image(600, 400.5, image= frame_image)        time.sleep(0.16)# --- main ---root = tk.Tk()root.geometry("1920x1080")canvas = tk.Canvas(root, width="1920", height="1080")canvas.pack()thread = threading.Thread(target=player, args=(canvas,))thread.daemon = 60thread.start()root.mainloop()

Because tkinter is not thread-safe` so second method may not works sometimes.


Other problem - already resolved in code above - after expects "callback" - it means function name without () and arguments. To use function which needs arguments then you have to use lambda to create function without arguments.

root.after(16, lambda:player(canvas))

Last problem - for every video frame you create new PhotoImage and new object on canvas (usingcreate_image`) so finally you have many objects on canvas (one above another) and many objects in memory.

You would have to create one PhotoImage on Canvas and replace data in this object.

I can't test it but it could be something like this

def player(canvas_image):    b = video.get_next_data()    frame_image = ImageTk.PhotoImage(Image.fromarray(b))    if canvas_image is None:        # create first canvas image        canvas_image = canvas.create_image(600, 400.5, image=frame_image)    else:        # replace image in canvas object        canvas.itemconfig(canvas_image, image=frame_image)    root.after(16, lambda:player(canvas_image))# --- main ---root = tk.Tk()root.geometry("1920x1080")canvas = tk.Canvas(root, width="1920", height="1080")canvas.pack()canvas_image = Noneplayer(canvas_image)root.mainloop()

or using two variables

def player(frame_image, canvas_image):    b = video.get_next_data()    if canvas_image is None:        # create first canvas image        frame_image = ImageTk.PhotoImage(Image.fromarray(b))        canvas_image = canvas.create_image(600, 400.5, image=frame_image)    else:        # replace image in canvas object        frame_image.paste(Image.fromarray(b))        canvas.itemconfig(canvas_image, image=frame_image)    root.after(16, lambda:player(frame_image, canvas_image))# --- main ---root = tk.Tk()root.geometry("1920x1080")canvas = tk.Canvas(root, width="1920", height="1080")canvas.pack()frame_image = Nonecanvas_image = Noneplayer(frame_image, canvas_image)root.mainloop()

EDIT: version with global variables

def player(): # without `frame_image`, `canvas_image`    global frame_image    global canvas_image    b = video.get_next_data()    if canvas_image is None:        # create first canvas image        frame_image = ImageTk.PhotoImage(Image.fromarray(b))        canvas_image = canvas.create_image(600, 400.5, image=frame_image)    else:        # replace image in canvas object        frame_image.paste(Image.fromarray(b))        canvas.itemconfig(canvas_image, image=frame_image)    root.after(16, player) # without `frame_image`, `canvas_image`# --- main ---root = tk.Tk()root.geometry("1920x1080")canvas = tk.Canvas(root, width="1920", height="1080")canvas.pack()frame_image = Nonecanvas_image = Noneplayer() # without `frame_image`, `canvas_image`root.mainloop()