このサイトのコードをもとに、オリジナルのGUIアプリを作成しようとしています。
しかし、動画を最初に選択し、もう一度動画を選択すると挙動がおかしいです。
具体的には、ボタンをクリックすると動画が再生されるはずなのですが、
ボタンをクリックすると、内部ではフレームが進み、
もう一度クリックすると、止めたところのフレーム画像が表示されます。
mainloop等などについて自分でも調べたのですが、原因がわかりません。
わかる方がおられましたら、教えていただけませんでしょうか。
コード
Python
1class Model(): 2 3 def __init__(self): 4 5 # 動画オブジェクト参照用 6 self.video = None 7 8 # 画像処理の設定 9 self.gray = False 10 self.flip= False 11 12 # 読み込んだフレーム 13 self.frames = None 14 15 # PIL画像オブジェクト参照用 16 self.image = None 17 18 # Tkinter画像オブジェクト参照用 19 self.image_tk = None 20 21 22 def create_video(self, path): 23 '動画オブジェクトの生成を行う' 24 25 # pathの動画から動画オブジェクト生成 26 self.video = cv2.VideoCapture(path) 27 28 def advance_frame(self): 29 'フレームを読み込んで1フレーム進める' 30 31 if not self.video: 32 return 33 34 # フレームの読み込み 35 ret, self.frame = self.video.read() 36 37 return ret 38 39 def reverse_video(self): 40 '動画を先頭に戻す' 41 42 self.video.set(cv2.CAP_PROP_POS_FRAMES, 0) 43 44 def create_image(self, size): 45 'フレームの画像を作成' 46 47 t1 = time.time() 48 49 # フレームを読み込み 50 frame = self.frame 51 if frame is None: 52 print("None") 53 54 # 設定に応じて画像処理 55 if self.gray: 56 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 57 if self.flip: 58 frame = cv2.flip(frame, 1) 59 60 # PIL イメージに変換 61 rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 62 pil_image = Image.fromarray(rgb_frame) 63 64 # 指定サイズに合わせて画像をリサイズ 65 66 # 拡大率を計算 67 ratio_x = size[0] / pil_image.width 68 ratio_y = size[1] / pil_image.height 69 70 if ratio_x < ratio_y: 71 ratio = ratio_x 72 else: 73 ratio = ratio_y 74 75 # リサイズ 76 self.image = pil_image.resize( 77 ( 78 int(ratio * pil_image.width), 79 int(ratio * pil_image.height) 80 ) 81 ) 82 t2 = time.time() 83 84 print(f"経過時間:{t2-t1}") 85 86 def get_image(self): 87 'Tkinter画像オブジェクトを取得する' 88 89 if self.image is not None: 90 # Tkinter画像オブジェクトに変換 91 self.image_tk = ImageTk.PhotoImage(self.image) 92 return self.image_tk 93 94 def get_fps(self): 95 '動画のFPSを取得する' 96 97 if self.video is None: 98 return None 99 100 return self.video.get(cv2.CAP_PROP_FPS) 101 102 def set_gray(self): 103 self.gray = not self.gray 104 105 def set_flip(self): 106 self.flip = not self.flip 107 108class View(): 109 110 def __init__(self, app, model): 111 112 self.master = app 113 self.model = model 114 115 # アプリ内のウィジェットを作成 116 self.create_widgets() 117 118 def create_widgets(self): 119 'アプリ内にウィジェットを作成・配置する' 120 121 # キャンバスのサイズ 122 canvas_width = 500 123 canvas_height = 300 124 125 # キャンバスとボタンを配置するフレームの作成と配置 126 self.main_frame = tkinter.Frame( 127 self.master 128 ) 129 self.main_frame.pack() 130 131 # キャンバスを配置するフレームの作成と配置 132 self.canvas_frame = tkinter.Frame( 133 self.main_frame 134 ) 135 self.canvas_frame.grid(column=1, row=1) 136 137 # ユーザ操作用フレームの作成と配置 138 self.operation_frame = tkinter.Frame( 139 self.main_frame 140 ) 141 self.operation_frame.grid(column=2, row=1) 142 143 144 # キャンバスの作成と配置 145 self.canvas = tkinter.Canvas( 146 self.canvas_frame, 147 width=canvas_width, 148 height=canvas_height, 149 bg="#EEEEEE", 150 ) 151 self.canvas.pack() 152 153 # ファイル読み込みボタンの作成と配置 154 self.load_button = tkinter.Button( 155 self.operation_frame, 156 text="動画選択" 157 ) 158 self.load_button.pack() 159 160 # グレーON/OFFボタンの作成と配置 161 self.gray_button = tkinter.Button( 162 self.operation_frame, 163 text="モノクロON/OFF" 164 ) 165 self.gray_button.pack() 166 167 # フリップ/OFFボタンの作成と配置 168 self.flip_button = tkinter.Button( 169 self.operation_frame, 170 text="フリップON/OFF" 171 ) 172 self.flip_button.pack() 173 174 175 def draw_image(self): 176 '画像をキャンバスに描画' 177 178 image = self.model.get_image() 179 180 if image is not None: 181 # キャンバス上の画像の左上座標を決定 182 sx = (self.canvas.winfo_width() - image.width()) // 2 183 sy = (self.canvas.winfo_height() - image.height()) // 2 184 185 # キャンバスに描画済みの画像を削除 186 objs = self.canvas.find_withtag("image") 187 for obj in objs: 188 self.canvas.delete(obj) 189 190 # 画像をキャンバスの真ん中に描画 191 self.canvas.create_image( 192 sx, sy, 193 image=image, 194 anchor=tkinter.NW, 195 tag="image" 196 ) 197 198 def select_open_file(self, file_types): 199 'オープンするファイル選択画面を表示' 200 201 # ファイル選択ダイアログを表示 202 file_path = tkinter.filedialog.askopenfilename( 203 initialdir=".", 204 filetypes=file_types 205 ) 206 return file_path 207 208 def draw_play_button(self): 209 '再生ボタンを描画' 210 211 # キャンバスのサイズ取得 212 width = self.canvas.winfo_width() 213 height = self.canvas.winfo_height() 214 215 # 円の直径を決定 216 if width > height: 217 diameter = height 218 else: 219 diameter = width 220 221 # 端からの距離を計算 222 distance = diameter / 10 223 224 # 円の線の太さを計算 225 thickness = distance 226 227 # 円の描画位置を決定 228 sx = (width - diameter) // 2 + distance 229 sy = (height - diameter) // 2 + distance 230 ex = width - (width - diameter) // 2 - distance 231 ey = height - (height - diameter) // 2 - distance 232 233 # 丸を描画 234 self.canvas.create_oval( 235 sx, sy, 236 ex, ey, 237 outline="white", 238 width=thickness, 239 tag="oval" 240 ) 241 242 # 頂点座標を計算 243 x1 = sx + distance * 3 244 y1 = sy + distance * 2 245 x2 = sx + distance * 3 246 y2 = ey - distance * 2 247 x3 = ex - distance * 2 248 y3 = height // 2 249 250 # 三角を描画 251 self.canvas.create_polygon( 252 x1, y1, 253 x2, y2, 254 x3, y3, 255 fill="white", 256 tag="triangle" 257 ) 258 259 def delete_play_button(self): 260 self.canvas.delete("oval") 261 self.canvas.delete("triangle") 262 263class Controller(): 264 265 def __init__(self, app, model, view): 266 self.master = app 267 self.model = model 268 self.view = view 269 270 271 # 動画再生中かどうかの管理 272 self.playing = False 273 274 # フレーム進行する間隔 275 self.frame_timer = 0 276 277 # 描画する間隔 278 self.draw_timer = 50 279 280 self.set_events() 281 282 def set_events(self): 283 '受け付けるイベントを設定する' 284 285 # キャンバス上のマウス押し下げ開始イベント受付 286 self.view.canvas.bind( 287 "<ButtonRelease-1>", 288 self.button_press 289 ) 290 291 # 動画選択ボタン押し下げイベント受付 292 self.view.load_button['command'] = self.push_load_button 293 294 # モノクロON/OFFボタン押し下げイベント受付 295 self.view.gray_button['command'] = self.push_gray_button 296 297 # フリップON/OFFボタン押し下げイベント受付 298 self.view.flip_button['command'] = self.push_flip_button 299 300 def draw(self): 301 '一定間隔で画像等を描画' 302 303 # 再度タイマー設定 304 self.master.after(self.draw_timer, self.draw) 305 306 # 動画再生中の場合 307 if self.playing: 308 # フレームの画像を作成 309 self.model.create_image( 310 ( 311 self.view.canvas.winfo_width(), 312 self.view.canvas.winfo_height() 313 ) 314 ) 315 316 # 動画1フレーム分をキャンバスに描画 317 self.view.draw_image() 318 319 def frame(self): 320 '一定間隔でフレームを進める' 321 322 # 再度タイマー設定 323 self.master.after(self.frame_timer, self.frame) 324 325 # 動画再生中の場合 326 if self.playing: 327 # 動画を1フレーム進める 328 ret = self.model.advance_frame() 329 330 # フレームが進められない場合 331 if not ret: 332 # フレームを最初に戻す 333 self.model.reverse_video() 334 self.model.advance_frame() 335 336 337 def push_load_button(self): 338 '動画選択ボタンが押された時の処理' 339 340 file_types = [ 341 ("WMVファイル", "*.wmv"), 342 ("MP4ファイル", "*.mp4"), 343 ] 344 345 # ファイル選択画面表示 346 file_path = self.view.select_open_file(file_types) 347 348 if len(file_path) != 0: 349 350 # 動画オブジェクト生成 351 self.model.create_video(file_path) 352 353 # 最初のフレームを表示 354 self.model.advance_frame() 355 self.model.create_image( 356 ( 357 self.view.canvas.winfo_width(), 358 self.view.canvas.winfo_height() 359 ) 360 ) 361 self.model.reverse_video() 362 self.view.draw_image() 363 364 # 再生ボタンの表示 365 self.view.delete_play_button() 366 self.view.draw_play_button() 367 368 # FPSに合わせてフレームを進める間隔を決定 369 fps = self.model.get_fps() 370 self.frame_timer = int(1 / fps * 1000 + 0.5) 371 372 # フレーム進行用のタイマースタート 373 self.master.after(self.frame_timer, self.frame) 374 375 # 画像の描画用のタイマーセット 376 self.master.after(self.draw_timer, self.draw) 377 378 def button_press(self, event): 379 'マウスボタン押された時の処理' 380 381 # 動画の再生/停止を切り替える 382 if not self.playing: 383 self.playing = True 384 385 # 再生ボタンの削除 386 self.view.delete_play_button() 387 else: 388 self.playing = False 389 390 # 再生ボタンの描画 391 self.view.draw_play_button() 392 393 def push_gray_button(self): 394 self.model.set_gray() 395 396 def push_flip_button(self): 397 self.model.set_flip() 398 399 400 401app = tkinter.Tk() 402 403app.title("動画再生アプリ") 404 405model = Model() 406view = View(app, model) 407controller = Controller(app, model, view) 408 409app.mainloop()
回答1件
あなたの回答
tips
プレビュー