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 (using
create_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()