前提・実現したいこと
PythonのTkinterを利用しって動画の再生機能を作成しようとしています。
知識があまりないため既に同じことをされている方のサイトに載っていたソースコードを基にコーディングしたのですが、上手くいきません。
アドバイス等いただけるとありがたいです。
発生している問題・エラーメッセージ
動画の再生・停止ボタンを押して再生しても画面が更新されず動かない。
再度、再生・停止ボタンを押して再生をストップすると、画面がそのときのフレームに更新される。
該当のソースコード
python
1import tkinter as tk 2import tkinter.filedialog 3from PIL import Image, ImageTk 4import cv2 5import time 6 7 8class Model(): 9 def __init__(self): 10 self.width = 1100 11 self.height = 600 12 13 #参照用動画オブジェクト 14 self.viedo = None 15 16 # 読み込んだフレーム 17 self.frames = None 18 19 # PIL画像オブジェクト参照用 20 self.image = None 21 22 # Tkinter画像オブジェクト参照用 23 self.image_tk = None 24 25 self.now_frame = 0 # シークバー用の動画の再生フレームを確認する変数 26 self.image_index = [] # シークバー用のopencv画像を保存する配列 27 28 29 def create_video(self,path): 30 # pathの動画から動画オブジェクト生成 31 self.video = cv2.VideoCapture(path) 32 33 34 def advance_frame(self): 35 #フレームを読み込んで1フレーム進める 36 37 if not self.video: 38 return 39 40 # フレームの読み込み 41 ret, self.frame = self.video.read() 42 43 self.now_frame = self.video.get(cv2.CAP_PROP_POS_FRAMES) 44 #print(self.now_frame) #何フレーム目かを示す 45 46 return ret 47 48 49 def reverse_video(self): 50 #動画を先頭に戻す 51 52 self.video.set(cv2.CAP_PROP_POS_FRAMES, 0) 53 54 55 def create_image(self, size): 56 #フレームの画像を作成 57 58 t1 = time.time() 59 60 # フレームを読み込み 61 62 #cap.set(cv2.CAP_PROP_POS_FRAMES, 100) 63 print(self.now_frame) 64 65 frame = self.frame 66 if frame is None: 67 print("None") 68 69 # PIL イメージに変換 70 rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 71 pil_image = Image.fromarray(rgb_frame) 72 73 # 指定サイズに合わせて画像をリサイズ 74 75 # 拡大率を計算 76 ratio_x = size[0] / pil_image.width 77 ratio_y = size[1] / pil_image.height 78 79 if ratio_x < ratio_y: 80 ratio = ratio_x 81 else: 82 ratio = ratio_y 83 84 # リサイズ 85 self.image = pil_image.resize( 86 ( 87 int(ratio * pil_image.width), 88 int(ratio * pil_image.height) 89 ) 90 ) 91 t2 = time.time() 92 93 print(f"経過時間:{t2-t1}") 94 95 def get_image(self): 96 'Tkinter画像オブジェクトを取得する' 97 98 if self.image is not None: 99 # Tkinter画像オブジェクトに変換 100 self.image_tk = ImageTk.PhotoImage(self.image) 101 return self.image_tk 102 103 def get_fps(self): 104 '動画のFPSを取得する' 105 106 if self.video is None: 107 return None 108 109 return self.video.get(cv2.CAP_PROP_FPS) 110 111 112 113 114class View(): 115 def __init__(self,master,model): 116 self.master = master 117 self.model = model 118 119 self.create_widgets() 120 121 def create_widgets(self): 122 #メインフレーム 123 self.wrpFrm = tk.Frame(self.master) 124 self.wrpFrm.configure(bg="thistle1") 125 self.wrpFrm.pack(padx=3, pady=3, fill="both", expand=1) 126 127 # キャンバスのサイズ 128 canvas_width = 500 129 canvas_height = 300 130 131 self.canvas_frame = tk.Frame(self.wrpFrm) 132 self.canvas_frame.grid(column=1, row=1) 133 self.canvas_frame.config(background="white") 134 135 self.operation_frame = tk.Frame(self.wrpFrm) 136 self.operation_frame.grid(column=2, row=0) 137 138 # キャンバスの作成と配置 139 self.canvas = tkinter.Canvas( 140 self.canvas_frame, 141 width=canvas_width, 142 height=canvas_height, 143 bg="black", 144 ) 145 self.canvas.pack() 146 147 # ファイル読み込みボタンの作成と配置 148 self.load_button = tkinter.Button( 149 self.operation_frame, 150 text="動画選択" 151 ) 152 self.load_button.pack(anchor=tk.N, padx=5, pady=5) 153 154 # 再生ボタン 155 self.play_button = tkinter.Button(self.canvas_frame,text="▶") 156 self.play_button.pack() 157 158 159 def draw_image(self): 160 #画像をキャンバスに描画 161 162 image = self.model.get_image() 163 164 if image is not None: 165 # キャンバス上の画像の左上座標を決定 166 sx = (self.canvas.winfo_width() - image.width()) // 2 167 sy = (self.canvas.winfo_height() - image.height()) // 2 168 169 # キャンバスに描画済みの画像を削除 170 objs = self.canvas.find_withtag("image") 171 for obj in objs: 172 self.canvas.delete(obj) 173 174 # 画像をキャンバスの真ん中に描画 175 self.canvas.create_image( 176 sx, sy, 177 image=image, 178 anchor=tkinter.NW, 179 tag="image" 180 ) 181 182 183 184 def select_open_file(self, file_types): 185 #オープンするファイル選択画面を表示 186 187 # ファイル選択ダイアログを表示 188 file_path = tkinter.filedialog.askopenfilename( 189 initialdir=".", 190 filetypes=file_types 191 ) 192 return file_path 193 194 195 196class Controller(): 197 def __init__(self,master,model,view): 198 self.master = master 199 self.model = model 200 self.view = view 201 202 # 動画再生中かどうかの管理 203 self.playing = False 204 205 # フレーム進行する間隔 206 self.frame_timer = 0 207 208 # 描画する間隔 209 self.draw_timer = 50 210 211 self.set_events() 212 213 def set_events(self): 214 # ボタン・アクションにイベントを結びつける 215 216 #再生ボタンを押したとき 217 self.view.play_button["command"] = self.push_play_button 218 219 #動画選択ボタンを押したとき 220 self.view.load_button["command"] = self.push_load_button 221 222 def draw(self): 223 # 一定間隔で画像を描画 224 225 # 再度タイマー設定 226 self.master.after(self.draw_timer, self.draw) 227 228 # 動画再生フラグが立っている時 229 if self.playing: 230 # フレームの画像を生成 231 self.model.create_image((self.view.canvas.winfo_width(),self.view.canvas.winfo_height())) 232 233 234 # 動画1フレーム分をキャンバスに描画 235 self.view.draw_image() 236 237 238 def frame(self): 239 # 一定間隔でフレームを進める' 240 241 # 再度タイマー設定 242 self.master.after(self.frame_timer, self.frame) 243 244 # 動画再生中の場合 245 if self.playing: 246 # 動画を1フレーム進める 247 ret = self.model.advance_frame() 248 249 # フレームが進められない場合 250 if not ret: 251 # フレームを最初に戻す 252 self.model.reverse_video() 253 self.model.advance_frame() 254 255 def push_load_button(self): 256 #動画選択ボタンが押された時の処理 257 258 file_types = [ 259 ("MP4ファイル", "*.mp4"), 260 ("MOVファイル", "*.mov") 261 ] 262 263 # ファイル選択画面表示 264 file_path = self.view.select_open_file(file_types) 265 266 # もし動画が選択されたら... 267 if len(file_path) != 0: 268 269 # 動画オブジェクト生成 270 self.model.create_video(file_path) 271 272 # 最初のフレームを表示 273 self.model.advance_frame() 274 self.model.create_image( 275 ( 276 self.view.canvas.winfo_width(), 277 self.view.canvas.winfo_height() 278 ) 279 ) 280 self.model.reverse_video() 281 self.view.draw_image() 282 283 284 # FPSに合わせてフレームを進める間隔を決定 285 fps = self.model.get_fps() 286 self.frame_timer = int(1 / fps * 1000 + 0.5) 287 288 # フレーム進行用のタイマースタート 289 self.master.after(self.frame_timer, self.frame) 290 291 # 画像の描画用のタイマーセット 292 self.master.after(self.draw_timer, self.draw) 293 294 def push_play_button(self): 295 296 # 動画の再生/停止を切り替える 297 if not self.playing: 298 self.playing = True 299 print("再生ボタン押されたよ!") 300 301 else: 302 self.playing = False 303 print("停止したよ") 304 305 306 307 308 309class Application(tk.Frame): 310 def __init__(self, master): 311 super().__init__(master) 312 self.pack() 313 314 self.model = Model() 315 316 master.geometry(str(self.model.width)+"x"+str(self.model.height)) 317 master.title("動画切り出しソフト") 318 master.configure(bg="thistle1") 319 320 self.view = View(master,self.model) 321 self.controller = Controller(master,self.model,self.view) 322 323def main(): 324 win = tk.Tk() 325 app = Application(master=win) 326 app.mainloop() 327 328 329if __name__ == "__main__": 330 main() 331 332 333 334 335 336 337 338
試したこと
まずはフレームが正しく更新されていないことを疑いましたが、再生停止すると画面がその時のフレームに更新されることからフレーム自体は正しく更新されているようです。
また、draw_imageメソッドが正しく作動してないのかと思いましたが、print関数を使用して更新時間毎に作動していることも確認しました。
再生停止時のみ画面が更新されることが解決の糸口だと思っておりますが、なかなか解決まで至らず困っております。
アドバイス等いただけると助かります。
よろしくお願いします。
補足情報(FW/ツールのバージョンなど)
参考にしたサイト:https://daeudaeu.com/play_movie/
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/01/22 15:53
2021/01/22 16:00