質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.54%

複数Windowで動画再生

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 2,329
退会済みユーザー

退会済みユーザー

前提・実現したいこと

下記それぞれのPATHに30fpsの画像(YYYYMMDD_HHMMSS.mmm.jpg ...)が1000枚ずつ格納されている。
'./src/img/0/'
'./src/img/1/'
'./src/img/2/'
'./src/img/3/'

この格納された画像PATHごとにViewerを作り、4つのViewerで30fpsの動画再生を同時に行う。

発生している問題・エラーメッセージ

一つの画像Viewerでは動画再生ができたものの複数Windowを作り
同時再生するためにはどのようにすべきかわからない。

該当のソースコード

# -*- coding: UTF-8 -*
from tkinter import *
import tkinter
import PIL.Image
import PIL.ImageTk
import os
# import pygame
# import re, sys

# フレーム速度 [ms]
TIME = 33
# ファイル格納場所
PATH = './src/img/0/'

class App(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        # ディクレクトリ内のファイルのリストを取得する
        self.ls = os.listdir(PATH)

        # ディクレクトリ内のファイルリストで余分なファイルを削除
        # b = self.ls.remove(os.path.basename(__file__))

        # numはリストの番号情報を保持
        self.num = 0
        # startedは再生しているかどうかを保持
        self.started = False

        # フレームの設定
        self.la = Label(self)
        self.la.pack()
        self.pack()
        self.bind_all('<1>', self.play)
        self.bind_all('<3>', self.reset)
        self._setting()

    def reset(self, event):
        self.num = 0
        if not self.started:
            self._setting()

    def play(self, event):
        # 右クリックでスタートとストップを切り替える
        if self.started:
            self.started = False
        else:
            self._setting()
            self.after(TIME, self.start)
            self.started = True

    def start(self):
        if self.started:
            self.num += 1
            if self.num < len(self.ls):
                self._setting()
                self.after(TIME, self.start)
            else:
                self.num = 0
                self.started = False

    def _setting(self):
        # 画像を読み込み、タイトルを変える
        self.image = PIL.ImageTk.PhotoImage(PIL.Image.open(PATH + self.ls[self.num]))
        self.la.configure(image=self.image)
        self.master.title(self.ls[self.num])

if __name__ == '__main__':
    app = App()
    app.mainloop()

試したこと

補足情報(言語/FW/ツール等のバージョンなど)

IDE:pycharm
OS:Windows

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

Windowを分けたいという要望なため、TopLevelWindowを使用して改造。

※10のコメントの箇所は改善するとしたらThreadPoolExecutorを使用するという形になると思うので、興味があるならぐぐってみてくださいな。
申し訳ないのですが、途中でモチベーションが下がってしまったので、
回答のソースコード解説の要求はスルー致します><

# -*- coding: UTF-8 -*
from tkinter import *
# ※1 上記行と同じ事をしているためコメントアウト
# import tkinter
import PIL.Image
import PIL.ImageTk
import os
import functools
from collections import Counter
# import pygame
# import re, sys
# フレーム速度 [ms]
TIME = 33
# ※2_settingの処理時間計測用
times = Counter()


def on_application_exit(param):
    # ※3 終了処理はexitを呼び出し!
    sys.exit(0)

# ※4 Frame→Toplevelに変更
class App(Toplevel):
    # startedは再生しているかどうかを保持
    started = False
    # ※5 viewerの参照を保持
    viewers = []

    def __init__(self, master, path=''):
        super().__init__()
        App.viewers.append(self)
        # ※6 ウィンドウを閉じたときの終了イベントを追加
        self.protocol('WM_DELETE_WINDOW', functools.partial(on_application_exit, param=1))
        # ディクレクトリ内のファイルのリストを取得する
        self.image_path = path

        self.ls = os.listdir(path)
        # ※7 listdirではファイルの取得順序は保証されていない!のでソート処理を追加
        self.ls = sorted(self.ls)
        # ディクレクトリ内のファイルリストで余分なファイルを削除
        # b = self.ls.remove(os.path.basename(__file__))

        # ※8 Progressクラスを新規追加し、進捗を管理
        class Progress(object):
            def __init__(self, maximum=0):
                self.minimum = 0
                self.maximum = maximum
                self.value = 0

            def perform_step(self):
                if self.maximum == self.value:
                    return
                self.value += 1

            def reset(self):
                self.value = self.minimum
        self.progress = Progress(len(self.ls))
        # ラベルの設定
        self.la = Label(self)
        self.la.pack()
        #※9 self.packをコメントアウト
        #self.pack()
        self.bind_all('<1>', App.viewer_click)
        self.bind_all('<3>', App.all_reset)
        self._setting()

    def reset(self, event):
        self.progress.reset()
        self._setting()

    def next_image(self):
        if not self.started:
            return
        self.progress.perform_step()
        if self.progress.value < self.progress.maximum:
            self._setting()
        else:
            self.reset(None)

    def _setting(self):
        # ※10 time.perf_counter()で計測すると_setting内の処理時間が30fpsを余裕でオーバーしている
        import time
        start = time.perf_counter()
        # 画像を読み込み、タイトルを変える
        filename = self.image_path + self.ls[self.progress.value]
        img = PIL.Image.open(filename)
        self.image = PIL.ImageTk.PhotoImage(img)
        elapsed_time = time.perf_counter() - start
        # ※2
        times[round(elapsed_time * 1000, 3)] += 1
        self.la.configure(image=self.image)
        self.title(os.path.basename(filename))

    @staticmethod
    def viewer_click(event):
        # マウスクリックでスタートとストップを切り替える
        App.started = not App.started
        if App.started:
            image_update()
            return

    @staticmethod
    def all_reset(event):
        for app in App.viewers:
            app.reset(event)


def image_update():
    for app in App.viewers:
        app.next_image()
    if App.started:
        root.after(TIME, decorated)


root = Tk()
decorated = functools.update_wrapper(functools.partial(image_update), image_update)


def main():
    # ※11 root windowを非表示
    root.withdraw()
    root.protocol('WM_DELETE_WINDOW', functools.partial(on_application_exit, param=0))
    viewer_params = [
        {'path': './src/img/0/', 'x': 50, 'y': 50},
        {'path': './src/img/1/', 'x': 50, 'y': 460},
        {'path': './src/img/2/', 'x': 460, 'y': 50},
        {'path': './src/img/3/', 'x': 460, 'y': 460}
    ]
    for para in viewer_params:
        app = App(root, para['path'])
        # ※12 他のwindowとの位置をgeometryで設定
        # geometry width*height+x+y
        app.geometry("{0}x{1}+{2}+{3}".format(400, 400, para['x'], para['y']))
    root.mainloop()


if __name__ == '__main__':
    main()

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/01/22 09:13

    詳しい解説ありがとうございました、参考にさせていただきます。
    ご回答いただきありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.54%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる