tkinterを使用して動画再生をした。

## 目的
pythonを用いてGUIを操作し、動画再生をします。

## 実行環境
Python version: 3.9.13
OS: Windows 10

## GUIイメージ
動画ファイルを読み込んで、サイズを枠に合わせて縮小し、左のキャンバス上に映し出します。再生、停止、最初に戻る、閉じるボタンを作っています。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176158/391cf8f3-8131-7531-7691-75822b10d9d0.png)

### フレームイメージ
GUIの作成をするため、フレームの領域を分けました。フレームのエリアは、下記のように分けています。
![ャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176158/ebf4c7a6-9f4c-05a7-e5b2-419ccdfe97b2.png)

## プログラム
ファイルは、[github](https://github.com/yutti/study/tree/main/movie_viewer)に置いています。threadingを使って、フレームの処理と動画の処理を分けています。threadingは、動画を選択したときにスタートするようにしました。動画の処理は、if文とフラグを使ってTure/Falseで分岐するようにして捜査しています。

```run.py
import sys
import time
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter.filedialog import askopenfile
import cv2

from PIL import Image, ImageOps, ImageTk

# from GUI_control import GUI_control

import threading


class Set_gui:
    def __init__(self, main_window):

        # Variable setting
        self.file_filter = [("Movie file", ".mp4")]
        self.set_movie = True
        self.thread_set = False
        self.start_movie = False
        self.video_frame = None

        # Main window
        self.main_window = main_window
        self.main_window.geometry("1400x800")
        self.main_window.title("Movie Editor v0.10")

        # Sub window
        self.canvas_frame = tk.Frame(self.main_window, height=450, width=400)
        self.path_frame = tk.Frame(self.main_window, height=100, width=400)
        self.opr_frame = tk.Frame(self.main_window, height=100, width=400)

        # Widgetsmith
        self.canvas_frame.place(relx=0.05, rely=0.05)
        self.path_frame.place(relx=0.60, rely=0.2)
        self.opr_frame.place(relx=0.60, rely=0.5)

        # 1.1 canvas_frame (label)
        self.label = tk.Label(
            self.canvas_frame, text="Movie", bg="white", relief=tk.RIDGE
        )
        self.label.grid(row=0, column=0, sticky=tk.W + tk.E)

        # 1.2 canvas_frame (canvas)
        self.canvas = tk.Canvas(self.canvas_frame, width=700, height=500, bg="#A9A9A9")
        self.canvas.grid(row=1, column=0)

        # 2 path_frame
        self.button = self.opr_btn(self.path_frame, "File", self.on_click_path)
        self.button.grid(row=0, column=0, sticky=tk.W, padx=10, pady=10)

        self.path_stvar = tk.StringVar()
        self.path_entry = tk.Entry(
            self.path_frame, textvariable=self.path_stvar, width=70
        )
        self.path_entry.grid(row=1, column=0, sticky=tk.EW, padx=10)

        # 3 opr_frame
        self.button = self.opr_btn(self.opr_frame, "Start", self.on_click_start)
        self.button.grid(row=1, column=0, sticky=tk.SE, padx=10, pady=10)

        self.button = self.opr_btn(self.opr_frame, "Stop", self.on_click_stop)
        self.button.grid(row=1, column=1, sticky=tk.SE, padx=10, pady=10)

        self.button = self.opr_btn(self.opr_frame, "Reset", self.on_click_reset)
        self.button.grid(row=1, column=2, sticky=tk.SE, padx=10, pady=10)

        self.button = self.opr_btn(self.opr_frame, "Exit", self.on_click_close)
        self.button.grid(row=3, column=2, sticky=tk.SE, padx=10, pady=10)

    def opr_btn(self, set_frame, btn_name, act_command):
        return tk.Button(set_frame, text=btn_name, width=10, command=act_command)

    def on_click_path(self):
        self.movie_path = self.get_path()
        self.path_stvar.set(self.movie_path)
        self.run_one_frame()
        # Movie standby.
        self.thread_set = True
        self.thread_main = threading.Thread(target=self.main_thread_func)
        self.thread_main.start()

    def on_click_start(self):
        self.video_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
        self.start_movie = True

    def on_click_stop(self):
        self.start_movie = False

    def on_click_reset(self):
        self.start_movie = False
        self.video_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
        self.run_one_frame()

    def on_click_close(self):
        self.set_movie = False

        # Block the calling thread until the thread represented by this instance end.
        if self.thread_set == True:
            self.thread_main.join()

        self.video_cap.release()
        self.main_window.destroy()

    def main_thread_func(self):

        self.video_cap = cv2.VideoCapture(self.movie_path)
        ret, self.video_frame = self.video_cap.read()

        if self.video_frame is None:
            print("None")

        while self.set_movie:

            if self.start_movie:

                ret, self.video_frame = self.video_cap.read()

                if ret:
                    # convert color order from BGR to RGB
                    pil = self.cvtopli_color_convert(self.video_frame)

                    self.effect_img, self.canvas_create = self.resize_image(
                        pil, self.canvas
                    )
                    self.replace_canvas_image(
                        self.effect_img, self.canvas, self.canvas_create
                    )
                else:
                    self.start_movie = False

    def run_one_frame(self):
        self.video_cap = cv2.VideoCapture(self.movie_path)
        ret, self.video_frame = self.video_cap.read()

        if self.video_frame is None:
            print("None")

        else:
            ret, self.video_frame = self.video_cap.read()
            # convert color order from BGR to RGB
            pil = self.cvtopli_color_convert(self.video_frame)

            self.effect_img, self.canvas_create = self.resize_image(pil, self.canvas)
            # scale value intialize
            self.replace_canvas_image(self.effect_img, self.canvas, self.canvas_create)

    def replace_canvas_image(self, pic_img, canvas_name, canvas_name_create):
        canvas_name.photo = ImageTk.PhotoImage(pic_img)
        canvas_name.itemconfig(canvas_name_create, image=canvas_name.photo)

    def cvtopli_color_convert(self, video):
        rgb = cv2.cvtColor(video, cv2.COLOR_BGR2RGB)
        return Image.fromarray(rgb)

    # Model
    def resize_image(self, img, canvas):

        w = img.width
        h = img.height
        w_offset = 250 - (w * (500 / h) / 2)
        h_offset = 250 - (h * (700 / w) / 2)

        if w > h:
            resized_img = img.resize((int(w * (700 / w)), int(h * (700 / w))))
        else:
            resized_img = img.resize((int(w * (500 / h)), int(h * (500 / h))))

        self.pil_img = ImageTk.PhotoImage(resized_img)
        canvas.delete("can_pic")

        if w > h:
            resized_img_canvas = canvas.create_image(
                0, h_offset, anchor="nw", image=self.pil_img, tag="can_pic"
            )

        else:
            resized_img_canvas = canvas.create_image(
                w_offset, 0, anchor="nw", image=self.pil_img, tag="can_pic"
            )

        return resized_img, resized_img_canvas

    def get_path(self):
        return filedialog.askopenfilename(
            title="Please select image file,", filetypes=self.file_filter
        )


def main():

    # 　Tk MainWindow
    main_window = tk.Tk()

    # Viewクラス生成
    Set_gui(main_window)

    # 　フレームループ処理
    main_window.mainloop()


if __name__ == "__main__":
    main()

```