動画再生
この記事はPythonで動画に関するアプリを作る際に大変役立つと思います。
ネットにこれがっている同じような記事とは全然違います。

今回はアプリっぽくなるように再生ボタンを実装します。

状態遷移は、
空き
再生中

の2つになります。

空き:
タイマーもスレッドも動作していません。

空き→再生中:
録画ボタンをクリックで遷移します。
タイマーとスレッドが動作します。

再生中→空き
録画ボタン(停止と表示されている)をクリックで遷移します。
再生が終わると遷移します。
タイマー、スレッドを終了します。

キモになるところ:
画像の読込はFPSの時間だけ待たないとあっという間に超高速再生で終わってしまいます。表示するタイマー時間も同様に扱います。

    def ThreadVideoCapture(self):
        while True:
            try:
                if self.quit == True:
                    break

                ret, self.frame = self.videoPlayer.read()
                if ret: 
                    self.dataExist = True
                    time.sleep(1/self.fps)
                else:
                    self.dataExist = False
                    break
            except:
                break
        self.termated = True
        self.videoPlayer.release()
        print("terminate thread")
全ソースです。

import tkinter
from tkinter.constants import SEL_FIRST
import cv2
import PIL.Image, PIL.ImageTk
import time
from datetime import datetime
import threading

class videoApp:
    def __init__(self, window, window_title, cameraId=0):
        self.window = window
        self.window.title(window_title)
        self.cameraId = cameraId
        
         # open video source (by default this will try to open the computer webcam)
        self.playDevice = MyVideoPlay()

         # Create a canvas that can fit the above video source size
        self.canvas = tkinter.Canvas(self.window, width = 640, height = 480)
        self.canvas.pack()
        
        #録再ボタン
        self.playing = False
        self.btmPlay = tkinter.Button(
            self.window,
            text='再生',
            command=self.play_click)
        self.btmPlay.pack()

 
        # self.showFhoto()
        self.window.mainloop()
        self.playDevice.terminateReq()

    def play_click(self):
        if self.playing == False:
            self.playing = True
            self.btmPlay.configure(text='停止')

            vFile  = 'my_video.avi'
            self.playDevice.setVideoFile(vFile)
            self.canvas.configure(width=self.playDevice.width, height=self.playDevice.height)

            #スレッドの開始
            self.playDevice.termated = False
            #スレッドの作成
            self.threadVideo = threading.Thread(target=self.playDevice.ThreadVideoCapture)
            #スレッドの開始
            self.threadVideo.start()
            
            self.showFhoto()
        else:
            self.playing = False
            self.btmPlay.configure(text='再生')
            #スレッドの終了
            self.playDevice.terminateReq()
            self.threadVideo.join()
            

    def showFhoto(self):
        ret, frame = self.playDevice.get_frame()
 
        if ret:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(frame))
            self.canvas.create_image(0, 0, image = self.photo, anchor = tkinter.NW)
        if self.playDevice.termated == False:
            tm = 1000 / self.playDevice.fps
            tm = int(tm)
            self.window.after(tm, self.showFhoto)
        else:
            self.btmPlay.configure(text='再生')
            self.playing = False
            self.playDevice.terminateReq()
            self.threadVideo.join()


    def __del__(self):
        if self.playing == True:
            self.playDevice.terminateReq()
            self.threadVideo.join()

class MyVideoPlay:
    def __init__(self):
 
        self.quit = False
        self.termated = False
        self.dataExist = False
    
    def setVideoFile(self, vFile):
         # Open the video source
        self.videoPlayer = cv2.VideoCapture(vFile)
        if not self.videoPlayer.isOpened():
            raise ValueError("Unable to open video source")
         # Get video source width and height
        self.width = self.videoPlayer.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.videoPlayer.get(cv2.CAP_PROP_FRAME_HEIGHT)

        # Video info
        self.fps = self.videoPlayer.get(cv2.CAP_PROP_FPS)
        self.quit = False
        self.termated = False
        self.dataExist = False
 


    def terminateReq(self):
        self.quit = True

    def get_frame(self):
        if self.dataExist:
            return True, self.frame
        return False, None

    def ThreadVideoCapture(self):
        while True:
            try:
                if self.quit == True:
                    break

                ret, self.frame = self.videoPlayer.read()
                if ret: 
                    self.dataExist = True
                    time.sleep(1/self.fps)
                else:
                    self.dataExist = False
                    break
            except:
                break
        self.termated = True
        self.videoPlayer.release()
        print("terminate thread")

    def __del__(self):
        if self.videoPlayer.isOpened():
            self.videoPlayer.release()
            

if __name__ == '__main__':
    videoApp(tkinter.Tk(), "Video Playing")
ビデオの録画・再生ができると面白いアプリができそうです。