在python中生成“ Ctrl + c”事件的最佳方法是什么?

时间:2018-11-27 10:41:32

标签: python keyboard-events

我正在开发一个GUI,它可以使用sounddevice和soundfile库在任意时间内记录音频。按下“ ctrl + c”组合键可停止录制过程。

我正在尝试使用“开始”和“结束”按钮来实现GUI。 “开始”按钮应调用记录功能,“结束”按钮应模拟“ ctrl + c”事件。我不知道如何将此事件实现为python中的函数。 实施想法很受赞赏。

如果使用Windows命令提示符python record.py

运行该代码,则该代码可以正常运行

record.py 如下:

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 如下:

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()

2 个答案:

答案 0 :(得分:1)

如果命令

 exit()

make应用程序没有响应,我们可以使用以下方式模拟ctrl事件

signal.CTRL_C_EVENT

在Windows中

signal.SIGINT

在Linux中

记住要导入信号 因此功能变为...

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

我有一台Mac,所以我没有尝试发送信号。CTRL_C_EVENT,无论如何都尝试

希望它能工作!

答案 1 :(得分:0)

查看声音设备的source code,看起来KeyboardInterrupt事件在其示例中记录了rec_unlimited.py时调用了argparser的exit方法,

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)

此示例对您有用吗?我认为在此退出无论如何只会调用系统退出。如果可行,请从那里开始,并确保您的GUI中的stop命令正在执行相同的操作(即,提高KeyboardInterrupt或退出)。

如果sounddevice正在创建线程(queue.get建议这样做),则使用线程也可能会出现问题,因此exit调用不会终止所有线程。

如果该退出在Windows中不起作用,则也许调用sd.stop()可能是跨平台的解决方案(尽管我怀疑离开with块仍然可以这样做)。

基于讨论的更新:

由于示例运行良好,但是end_button_func冻结了GUI,看来记录过程需要separate thread,因此您的GUI会响应,直到传递了停止它的信号为止。我认为最好的方法是传递一个参数,当按下停止按钮时,该参数会触发线程中的KeyboardInterrupt异常 。要与线程通信,您需要像在answerthis中那样发送信号,并在此基础上提高线程中的KeyboardInterrupt

这个示例(PyQt: How to send a stop signal into a thread where an object is running a conditioned while loop?)似乎是最接近的,您可以在其中调整work函数以引发异常,如下所示,

@pyqtSlot()
def work(self):
    while self.running():
        time.sleep(0.1)
        print 'doing work...'
    self.sgnFinished.emit()
    raise KeyboardInterrupt

我这里没有PyQt5或Windows,因此无法进行进一步测试。请注意,您似乎需要使主类成为QObject才能使用pyqtSignal。