Search code examples
pythonvideotkinteronclickframe

How to go frame by frame on mouse click in tkinter (Python3.x)?


I have code from a few sources on stack overflow that I have put together. It plays a video and records click positions in the window. I would like it to go frame by frame and advance after each click. I am unsure how to begin.

video_name = input_file()
video = imageio.get_reader(video_name)

def stream(label):
    for image in video.iter_data():
        image_frame = Image.fromarray(image)
        frame_image = ImageTk.PhotoImage(image_frame)
        label.config(image=frame_image)
        label.image = frame_image


if __name__ == "__main__":
    def leftClick(event):
        x = root.winfo_pointerx() - root.winfo_rootx()
        y = root.winfo_pointery()- root.winfo_rooty()
        print(x, y)
    root = tk.Tk()
    my_label = tk.Label(root)
    my_label.bind("<Button-1>", leftClick)
    my_label.bind("<Button-3>", rightClick)
    my_label.pack()
    thread = threading.Thread(target=stream, args=(my_label,))
    thread.daemon = 1
    thread.start()
    root.mainloop()

Thank you for any suggestions you might have for me.


Solution

  • You should use video.get_data(...) to load frame instead of using for loop. Also you do not need to use thread to read frame from video, use click event handler. Below is an modified version of your code as an example:

    frame_index = 0 # current displayed frame index in the video file
    
    def stream(label, offset=0):
        global frame_index
        try:
            # read a frame image from video file
            image = video.get_data(frame_index+offset)
            # convert the image to tkinter supported format
            image_frame = Image.fromarray(image)
            frame_image = ImageTk.PhotoImage(image_frame)
            label.config(image=frame_image)
            label.image = frame_image
            # update frame_index if image is loaded successfully
            frame_index += offset
        except Exception as ex:
            print('Error loading frame:', ex)
    
    if __name__ == "__main__":
        video_name = input_file()
        video = imageio.get_reader(video_name)
        root = tk.Tk()
        my_label = tk.Label(root)
        my_label.bind("<Button-1>", lambda e: stream(my_label, +1))
        my_label.bind("<Button-3>", lambda e: stream(my_label, -1))
        my_label.pack()
        stream(my_label) # load first frame
        root.mainloop()
        video.close() # close the reader
    

    Note that mouse left click is used to show next image in the video, mouse right click is used to show previous image in the file.