1
2

ffmpegの処理をtkinterでGUI化(その3: キャンセルボタン)

Last updated at Posted at 2023-02-09

その1: プログレスバーとリアルタイム出力表示
その2: コンソール風出力表示
の続きです.
この記事の続きはこちら

やりたいこととGUIの内容

  • 処理を中止するキャンセルボタンをつける.
  • 完成したもの↓
    Animation2.gif

環境

Windows 10 Pro, Python 3.7, miniconda(conda 4.11.0)

コード

import os, subprocess
import tkinter as tk
from tkinter import ttk, messagebox
import threading
import signal

command = "ffmpeg hogehoge" # ffmpegの処理コマンド

def start():
    progress = tk.Toplevel()
    progress.geometry("750x350")
    progress.title("processing...")
    progress.grab_set()
    progress.focus_set()
    bar = ttk.Progressbar(progress,mode='indeterminate')
    bar.pack(side = tk.TOP, fill = tk.X)

    info = tk.Text(progress)
    info.bind("<Key>", lambda e: ctrlEvent(e))
    scrollbar = tk.Scrollbar(progress, orient=tk.VERTICAL, command=info.yview)
    scrollbar.pack(side = tk.RIGHT, fill = tk.Y)
    cancel_button = tk.Button(progress, text = "cancel")
    cancel_button.pack(side = tk.TOP,anchor = tk.W)
    info["yscrollcommand"] = scrollbar.set
    info.pack(side = tk.TOP, anchor = tk.W, fill = tk.BOTH)

    bar.start()

    def ctrlEvent(event):
        if(event.state & 2**2 == 4 and event.keysym=='c' ):
            return
        else:
            return "break"

    def process():
        p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,shell=True,universal_newlines=True)
        
        def cancel_animation():
            os.kill(p.pid, signal.CTRL_C_EVENT)
            for line in p.stdout:
                try:
                    info.insert(tk.END, line)
                    info.see("end")
                except KeyboardInterrupt:
                    pass
        cancel_button.configure(command = cancel_animation)

        for line in p.stdout:
            info.insert(tk.END, line)
            info.see("end")

        try:
            outs, errs = p.communicate()
        except subprocess.TimeoutExpired:
            pass
        else:
            p.terminate()
            bar.stop()
            cancel_button.configure(state = "disabled")

    th1 = threading.Thread(target=process)
    th1.start()


root = tk.Tk()
button = tk.Button(root, text = "start", command = start)
button.pack()

root.mainloop()

ポイント

キャンセル処理

  • cancel_animation()関数内にキャンセルボタン押下時の動作を書いている.
def cancel_animation():
    os.kill(p.pid, signal.CTRL_C_EVENT)
    bar.stop()
    for line in p.stdout:
        try:
            info.insert(tk.END, line)
            info.see("end")
        except KeyboardInterrupt:
            pass
  • ffmpegでの処理はsubprocessで行われているが,kill()terminate()では処理を中止できなかったので,os.killを使った.
os.kill(p.pid, signal.CTRL_C_EVENT)

参考: Stackoverflow - How to kill subprocess python in windows

キャンセル処理後のエラー対応

  • os.killでプロセスを終了するだけだと,keyboardInterruptののちに,cancel_animation()内じゃない方のinfo.insert(tk.END, line)の行でRuntimeError: main thread is not in main loopのエラーが出力される.以下がその例(スクリプトの行番号などは上記掲載のものと異なります)
KeyboardInterrupt
Exception in thread Thread-1:
Traceback (most recent call last):
    self.run()
  File "C:\hogehoge\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "C:\hogehoge\animation.py", line 182, in process
    info.insert(tk.END, line)
  File "C:\hogehoge\__init__.py", line 3272, in insert
    self.tk.call((self._w, 'insert', index, chars) + args)
RuntimeError: main thread is not in main loop
  • RuntimeError: main thread is not in main loopが出ている状態でプログレスバーウインドウを閉じて,もう一度メインウインドウから実行ボタンを押した場合は,標準出力がテキストウィジェットに全く表示されない.
  • エラーの根本的な原因は,tkinterを別スレッドで起動していることにあるらしい.
    参考: Qiita - tkinterを別スレッドで起動したときは、そのスレッド自身で停止処理して変数を後始末してからスレッド終了すること
    Teratail - Tkinterのエラーの対処方法がわかりません
  • とりあえず,cancel_animation()内で処理中止後も標準出力を表示し続けるようにしたらRuntimeError: main thread is not in main loopは出なくなった.ついでに,KeyboardInterruptを除外するようにした.
  • プログレスバーウインドウを閉じたあとにもう一度実行し直しても問題なく動作する.
  • ちなみに,p.stdoutを利用している通り,処理中止後にffmpegから出力されるExiting normally, received signal 2.といった文言は標準エラー出力ではなく標準出力.

エトセトラ

  • ffmpegでの処理が完了した後はキャンセルボタンを押せなくする.
(省略)
else:
    p.terminate()
    bar.stop()
    cancel_button.configure(state = "disabled")

最後までお読みいただきありがとうございました.

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2