1

when the button clicked ,the following code begin to run (called from the main (GUI) thread ),then the user interface become freezed

def on_pushButton_clicked(self):
        subprocess.call([r'C:\Program Files\ffmpeg-20131122-git-fb7d70c-win32-static\bin\ffmpeg', '-f', 'concat', '-i','mylist.txt', '-c',  'copy', 'output.mp4'])

anyone can explain why ? subprocess() itself can not run in an asynchronous way ?

iMath
  • 2,326
  • 2
  • 43
  • 75
  • As @falsetu said use Popen you can see an example [here](http://stackoverflow.com/a/20451375/1982962) – Kobi K Dec 09 '13 at 13:19
  • @ Kobi K then what should I do if I want to know when the process is finished ? – iMath Dec 09 '13 at 13:31
  • `Popen.communicate()` wait's for the process to terminate, when it's done the next line will be preformed `stdOutValue, stdErrValue = communicateRes`, and by that you will have the stdout and error. – Kobi K Dec 09 '13 at 13:37
  • 1
    when I use Popen.communicate(),the user interface become freezed again . – iMath Dec 09 '13 at 13:46
  • Yes you are right, Look [here](http://stackoverflow.com/a/12058609/1982962) this is the solution using `poll()`, you are calling a subprocess and by using `pool()` you can check if child process has terminated. – Kobi K Dec 09 '13 at 14:33
  • here are 3 scripts that demonstrate how to show output from a subprocess continuously without blocking GUI: 1. [tkinter and threads](https://gist.github.com/zed/42324397516310c86288#file-kill-process-py) 2. [gtk and threads](https://gist.github.com/zed/8a255e81eb87431c0e63#file-show-subprocess-output-thread-py) 3. [gtk and event loop](https://gist.github.com/zed/8a255e81eb87431c0e63#file-show-subprocess-output-io-watch-py) You could adapt similar approach for qt – jfs Dec 09 '13 at 20:14

2 Answers2

1

subprocess.call waits until the subprocess terminate. Use subprocess.Popen instead.

def on_pushButton_clicked(self):
    subprocess.Popen([r'C:\Program Files\ffmpeg-20131122-git-fb7d70c-win32-static\bin\ffmpeg', '-f', 'concat', '-i','mylist.txt', '-c',  'copy', 'output.mp4'])
AsukaMinato
  • 1,017
  • 12
  • 21
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • what should I do if I want to know when the process is finished ? – iMath Dec 09 '13 at 13:30
  • @user1485853, `subprocess.Popen` returns a `Popen` object; save it somewhere. then use [`subprocess.Popen.poll`](http://docs.python.org/2/library/subprocess.html#subprocess.Popen.poll) method to check whether process is terminated. (It returns `None` if the process is still running. returns return status otherwise) – falsetru Dec 09 '13 at 13:33
  • @user1485853, Yes, check it periodically. Alternatively you can run `subprocess.call` inside a separated thread. – falsetru Dec 09 '13 at 13:50
  • @user1485853, More preferably you can use GUI toolkit provided subprocess functionality (which may provide process-terminated event.) For example, see http://pyqt.sourceforge.net/Docs/PyQt4/qprocess.html#finished – falsetru Dec 09 '13 at 13:52
0

As others have said, subprocess.call will wait until the command complete.

But given that you're using PyQt, it's might be better to use QProcess, since that will allow you to use signals and events to keep the GUI responsive.

There are a few issues to think about.

Firstly, the example ffmpeg command will hang if the output file already exists, because, by deafult, it will prompt the user for permission to overwrite. So it would be a good idea to add the -y or -n flag to deal with that.

Secondly, the command could also hang for some unexpected reason. So you should probably give the user a way to forcibly kill the process.

Finally, what should happen if the user attempts to close the application before the command completes? Probably you will need to handle the closeEvent of the main window to deal with that.

The demo script below shows some possible ways to handle the above issues:

from PyQt4 import QtCore, QtGui

class Window(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        layout = QtGui.QVBoxLayout(self)
        self.label = QtGui.QLabel('Elapsed: 0', self)
        layout.addWidget(self.label)
        self.buttonStart = QtGui.QPushButton('Start', self)
        self.buttonStart.clicked.connect(self.handleButtonStart)
        layout.addWidget(self.buttonStart)
        self.buttonStop = QtGui.QPushButton('Stop', self)
        self.buttonStop.setDisabled(True)
        self.buttonStop.clicked.connect(self.handleButtonStop)
        layout.addWidget(self.buttonStop)
        self._process = QtCore.QProcess(self)
        self._process.started.connect(self.handleStarted)
        self._process.finished.connect(self.handleFinished)
        self._process.error.connect(self.handleError)
        self._time = QtCore.QTime()
        self._timer = QtCore.QTimer(self)
        self._timer.timeout.connect(self.handleTimeout)

    def closeEvent(self, event):
        if self._timer.isActive():
            event.ignore()
        else:
            QtGui.QWidget.closeEvent(self, event)

    def handleButtonStart(self):
        self._running = True
        self._process.start('ffmpeg', [
            '-f', 'concat', '-i', 'input.txt',
            '-c',  'copy', '-y', 'output.mp4',
            ], QtCore.QIODevice.ReadOnly)

    def handleTimeout(self):
        self.label.setText(
            'Elapsed: %.*f' % (2, self._time.elapsed() / 1000.0))

    def handleButtonStop(self):
        if self._timer.isActive():
            self._process.close()

    def handleStarted(self):
        self.buttonStart.setDisabled(True)
        self.buttonStop.setDisabled(False)
        self._time.start()
        self._timer.start(50)

    def handleFinished(self):
        self._timer.stop()
        self.buttonStart.setDisabled(False)
        self.buttonStop.setDisabled(True)

    def handleError(self, error):
        if error == QtCore.QProcess.CrashExit:
            print('Process killed')
        else:
            print(self._process.errorString())

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 200, 100)
    window.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • why shoud we call qApp.processEvents() occasionally here ? – iMath Dec 10 '13 at 01:58
  • I tested that even I close the application ,but the ffmpeg command still run in background until it finished,can you explain why ?maybe we can depend on this feature so there is no need to handle the closeEvent . – iMath Dec 10 '13 at 02:50
  • @user1485853. You're right - the `qApp.processEvents()` line was from an earlier edit and should have been removed (it's gone now). – ekhumoro Dec 10 '13 at 03:52
  • @user1485853. If ffmpeg is still running, closing the window will just hide your application until the command finishes (you can see this if you look at a process viewer). But if ffmpeg hung for some reason, your application would be left running with no way to close it. To avoid this situation, many applications will show a message box in the closeEvent giving the user choices for what to do next (or, more crudely, just prevent the application from being hidden, as with my script). – ekhumoro Dec 10 '13 at 04:03
  • how do you prevent the application from being hidden in this script ? – iMath Dec 11 '13 at 02:18
  • I think you can use QProcess::atEnd() to test if the process is still running in the closeEvent,so there is no need to use self._timer here . – iMath Dec 11 '13 at 02:32
  • @user1485853. The `event.ignore()` should bypass the normal close routine, and for me it does exactly that (but only tested on Linux and WinXP, so maybe YMMV applies). – ekhumoro Dec 11 '13 at 03:10
  • @user1485853. It's just a _demo_ script, not production code. Feel free to adapt it in whatever way you like ;-) – ekhumoro Dec 11 '13 at 03:13