'What is the best way to generate 'Ctrl+c' event in python?
I am developing a GUI which can record audio for an arbitrary duration using sounddevice and soundfile libraries. The recording process is stopped by pressing 'ctrl+c' button combination.
I am trying to implement a GUI with a 'start' and 'end' button. The 'start' button should invoke the record function and the 'end' button should simulate 'ctrl+c' event. I don't know how this event can be implemented as a function in python. An idea to implement is much appreciated.
The code runs properly if you run it using windows command prompt python record.py
record.py is as follows:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
import queue
from PyQt5 import QtCore, QtGui, QtWidgets
import soundfile as sf
import sounddevice as sd
import mythreading
class Ui_MainWindow(object):
def __init__(self):
self.threadpool = QThreadPool()
print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(640, 480)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(280, 190, 75, 23))
self.pushButton.setObjectName("pushButton")
self.pushButton.clicked.connect(self.start_button_func)
self.pushButton_1 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_1.setGeometry(QtCore.QRect(380, 190, 75, 23))
self.pushButton_1.setObjectName("pushButton")
self.pushButton_1.clicked.connect(self.end_button_func)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 640, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "Start"))
self.pushButton_1.setText(_translate("MainWindow", "End"))
def record(self):
self.q = queue.Queue()
self.s = sd.InputStream(samplerate=48000, channels=2, callback=self.callback)
try:
# Make sure the file is open before recording begins
with sf.SoundFile('check.wav', mode='x', samplerate=48000, channels=2, subtype="PCM_16") as file:
with self.s:
# 1 second silence before the recording begins
time.sleep(1)
print('START')
print('#' * 80)
print('press Ctrl+C to stop the recording')
while True:
file.write(self.q.get())
except OSError:
print('The file to be recorded already exists.')
sys.exit(1)
except KeyboardInterrupt:
print('The utterance is recorded.')
def callback(self, indata, frames, time, status):
"""
This function is called for each audio block from the record function.
"""
if status:
print(status, file=sys.stderr)
self.q.put(indata.copy())
def start_button_func(self):
self.worker = mythreading.Worker(self.record)
self.threadpool.start(self.worker)
def end_button_func(self):
print('how to stop?')
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
mythreading.py is as follows:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
@pyqtSlot()
def run(self):
self.fn()
Solution 1:[1]
If the command
exit()
make app doesn’t respond we can simulate ctrl event with
signal.CTRL_C_EVENT
in windows
And
signal.SIGINT
In Linux
REMEMBER TO IMPORT SIGNAL So the function become...
import signal
...
...
...
...
def end_button_func(self):
signal.SIGINT # if you are using ubuntu or mac
signal.CTRL_C_EVENT # if you are using windows
I have a Mac so I didn't try signal.CTRL_C_EVENT so try both anyway
Hope it will work!
Solution 2:[2]
Looking at the source code of sounddevice, it looks like the KeyboardInterrupt
event calls the exit method of argparser in their example for recording rec_unlimited.py,
parser = argparse.ArgumentParser(description=__doc__)
...
try:
# Make sure the file is opened before recording anything:
with sf.SoundFile(args.filename, mode='x', samplerate=args.samplerate,
channels=args.channels, subtype=args.subtype) as file:
with sd.InputStream(samplerate=args.samplerate, device=args.device,
channels=args.channels, callback=callback):
print('#' * 80)
print('press Ctrl+C to stop the recording')
print('#' * 80)
while True:
file.write(q.get())
except KeyboardInterrupt:
print('\nRecording finished: ' + repr(args.filename))
parser.exit(0)
Does this example work for you? I think exit here will simply call system exit anyway. If this works, then start from there and make sure your stop command in your GUI is doing the same thing (i.e. raising KeyboardInterrupt
or exit).
It may also be an issue with using threading if sounddevice is creating threads (queue.get suggests this) so the exit call does not terminate all threads.
If this exit doesn't work in Windows, maybe calling sd.stop()
may be a cross platform solution (although I suspect leaving the with
block does this anyway).
UPDATE BASED ON DISCUSSION:
As the example works fine, but the end_button_func
freezes the GUI, it seems a separate thread is needed for the record process so your GUI is responsive until a signal is passed to stop it. I think the best way is to pass an argument which triggers the KeyboardInterrupt
exception in the thread when the stop button is pressed. To communicate with the thread, you need to send a signal as in this answer or this and based on that, raise the KeyboardInterrupt
in the thread.
This example (PyQt: How to send a stop signal into a thread where an object is running a conditioned while loop?) seems the closest, where you'd adjust the work
function to raise the exception as follows,
@pyqtSlot()
def work(self):
while self.running():
time.sleep(0.1)
print 'doing work...'
self.sgnFinished.emit()
raise KeyboardInterrupt
I don't have PyQt5 or windows here so cannot test further. Note it seems you need to make the main class a QObject
to use pyqtSignal.
Solution 3:[3]
You can do it like this fig.
install the lib of OpenCV
To use the cv2.waitKey()
to control the program by keyboard.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | Suraj Rao |