概要
pythonでGUIアプリが作れるtkinterに動画を再生する方法をまとめた記事です
openCVを使うため、音声の出力はないです
完成形は最低限(動画の表示と再生,停止の制御のみ)ですのでこんな感じです
python 3.8.3
opencv-python 4.4.0.44
pillow 7.2.0
ソース
import cv2
from PIL import Image, ImageTk
import tkinter as tk
from tkinter import messagebox
import threading as th
lock = th.Lock()
class VideoPlayer(tk.Frame):
def __init__(self,master=None):
super().__init__(master,width=1000,height=500)
master.minsize(width=1000,height=500)
self.config(bg="#000000")
self.pack(expand=True,fill=tk.BOTH)
self.video = None
self.playing = False
self.video_thread = None
self.create_video_button()
def create_video_button(self):
self.video_button = tk.Button(
self,
width = 100,
height = 25,
bg="#DDDDDD",
relief="flat",
command = self.push_play_button,
)
self.video_button.pack(expand=True,fill=tk.BOTH)
def get_video(self,path):
self.video = cv2.VideoCapture(path)
def push_play_button(self):
if self.video == None:
messagebox.showerror('エラー','動画データがありません')
return
self.playing = not self.playing
if self.playing:
self.video_thread = th.Thread(target=self.video_frame_timer)
self.video_thread.setDaemon(True)
self.video_thread.start()
else:
self.video_thread = None
def next_frame(self):
global lock
lock.acquire()
ret, self.frame = self.video.read()
if not ret:
messagebox.showerror("エラー","次のフレームがないので停止します")
self.playing = False
else:
rgb = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
pil = Image.fromarray(rgb)
x = self.video_button.winfo_width()/pil.width
y = self.video_button.winfo_height()/pil.height
ratio = x if x<y else y #三項演算子 xとyを比較して小さい方を代入
pil = pil.resize((int(ratio*pil.width),int(ratio*pil.height)))
image = ImageTk.PhotoImage(pil)
self.video_button.config(image=image)
self.video_button.image = image
lock.release()
def video_frame_timer(self):
while self.playing:
self.next_frame()
if __name__ == "__main__":
root = tk.Tk()
path = "*.mp4"
app = VideoPlayer(master=root)
app.get_video(path)
root.mainloop()
画面をクリックするだけで、動画が再生される
なぜthreadなのか
tkinterにはafter()と呼ばれる、数秒間隔で実行してくれる関数がありますが、
問題点が複数あります
- 約数秒間隔
- 重い処理が挟まると再生も遅くなる
実はafter()は正確な秒数間隔で実行が行われていないみたいです
また、メインの中で重い処理を行うと、伴って動画の再生もスローになってしまいます
その点threadを使うことで、動画の再生はサブプロセスで動き、メインの処理の負荷を無視できます
主に関係のあるソース
def push_play_button(self):
self.playing = not self.playing
if self.playing:
self.video_thread = th.Thread(target=self.video_frame_timer)
self.video_thread.setDaemon(True)
self.video_thread.start()
else:
self.video_thread = None
def video_frame_timer(self):
while self.playing:
self.next_frame()
画面をクリックしたときthreadを作成し、
画面の更新はwhileで実行している
threadはdaemonにすることで、画面を閉じたときに勝手に終了してくれる
threadは上書きされるため、threadは常にメインとサブの2つのみ
canvasを使わない理由
先駆者様は動画を再生するためにcanvasを使っていますが、
処理が重いため、画面が白くちらつきました(処理が描画に間に合ってない)
labelやbuttonを用意して、イメージを更新することで同じことができ、
処理も軽いのでよっぽどの理由がない限りcanvasで作る必要はないと思います
(buttonで作ると再生ボタンも兼ねられるので個人的に好き)
まとめ
tkinter cpenCVで動画処理の記事はいっぱいありますが、
threadを使って動画データを処理する記事が思った以上になかったです
拙い記事ですが、ないよりましの精神で書きましたので、少しでも参考になれば幸いです