【问题标题】:What is the best way to generate 'Ctrl+c' event in python?在 python 中生成“Ctrl+c”事件的最佳方法是什么?
【发布时间】:2022-04-22 13:44:01
【问题描述】:

我正在开发一个 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()

【问题讨论】:

  • raise KeyboardInterrupt
  • @SuperShoot 你能解释一下吗?我想使用按钮生成'ctrl+c'。根本不会使用键盘。
  • 因此您想重新创建 ctrl+c 所做的事情(即发送 SIGINT)到录制过程。这个答案是否这样做:stackoverflow.com/questions/27356837/…?我建议在子进程中从 Python 运行录制进程,然后您可以根据需要终止。
  • @M.Denis,因为你不能使用子进程(因为记录是在其他地方启动的)或试图终止记录进程,它在 Python 中作为子进程启动(使用 os.kill(pid, signal.SIGINT)如链接的答案)不起作用?
  • @M.Denis,我看到您正在使用包soundfilesounddevice,在您的问题顶部值得一提,因为我认为可能需要终止这些包。你能不能在这里使用sd.stop() 或其他内置方法。否则,您似乎可以在 Windows 中使用CTRL_C_EVENT 获取 PID 并杀死:stackoverflow.com/questions/6645713/…

标签: python keyboard-events


【解决方案1】:

如果命令

 exit()

make app 没有响应我们可以模拟 ctrl 事件

signal.CTRL_C_EVENT

在窗口中

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,所以我没有尝试 signal.CTRL_C_EVENT 所以无论如何都尝试一下

希望它会起作用!

【讨论】:

  • 在 end_button_func() 中使用 exit() 会使应用无响应。
  • 您能相应地编辑我的代码部分吗?我刚刚将 end_button_func 中的 pass 替换为 exit()。你也可以这样做,然后尝试跑步。
  • 目前我正在使用 SO iPhone 应用程序,因为我在大学...如果你不先解决我会在我回家的时候尝试
  • 那太好了,谢谢。
  • 很遗憾,我还没有解决这个问题。如果您可以尝试用您的答案更改代码,那就太好了。
【解决方案2】:

查看sounddevice的source code,看起来KeyboardInterrupt事件在他们的例子中调用了argparser的exit方法来记录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)

这个例子对你有用吗?我认为 exit 无论如何都会简单地调用系统退出。如果这有效,那么从那里开始并确保您的 GUI 中的停止命令正在做同样的事情(即提高 KeyboardInterrupt 或退出)。

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

如果此出口在 Windows 中不起作用,则调用 sd.stop() 可能是一个跨平台解决方案(尽管我怀疑离开 with 块无论如何都会这样做)。

基于讨论的更新:

由于示例运行良好,但 end_button_func 冻结了 GUI,记录过程似乎需要 separate thread,因此您的 GUI 会响应,直到传递信号以停止它。我认为最好的方法是传递一个参数,在按下停止按钮时触发KeyboardInterrupt 异常in the thread。要与线程通信,您需要发送一个信号,如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。

【讨论】:

  • 是的,录制功能完全可以正常工作,它通过按“ctrl+c”停止录制。但我无法将开始和结束功能集成到按钮中。
  • @M.Denis 好的,所以你想重新创建 ctrl+c 的操作,这会创建一个 KeyboardInterrupt 异常。如果将end_button_func 中的pass 替换为raise KeyboardInterrupt,会发生什么情况?
  • 我猜你需要为你的记录过程创建一个单独的线程,这样你的 GUI 是响应式的,你可以传递一个参数,当按下停止按钮时触发线程中的 KeyboardInterrupt 异常.有关信息,请参阅此链接 pymadethis.com/article/…。请注意,self.pushButton_1.clciked.connect 中有错字
  • 非常好,注意每个线程都有自己的上下文,将参数传递给子线程并根据该参数的值在子线程中引发KeyboardInterrupt 异常将是我建议的方法。
  • @M.Denis,你解决了吗?我在回答中添加了更多细节以解决您的最后评论。问题并不简单,你最好去掉一个在子进程中引发异常的最小示例,如果你不能解决它,请提出一个新问题。
【解决方案3】:

你可以像这个图那样做。 安装 OpenCV 的库 使用cv2.waitKey()通过键盘控制程序。

【讨论】:

  • 请将代码和数据添加为文本 (using code formatting),而不是图像。图片:A)不允许我们复制粘贴代码/错误/数据进行测试; B) 不允许根据代码/错误/数据内容进行搜索;和many more reasons。除了代码格式的文本之外,只有在图像添加了一些重要的东西,而不仅仅是文本代码/错误/数据传达的内容时,才应该使用图像。
  • 请在引号中使用纯文本,这样我们可以复制粘贴到我们自己的环境中进行调试。
猜你喜欢
  • 2013-06-13
  • 1970-01-01
  • 1970-01-01
  • 2012-03-17
  • 1970-01-01
  • 2013-01-20
  • 1970-01-01
  • 2012-01-31
  • 2010-09-30
相关资源
最近更新 更多