【问题标题】:subprocess isn't killed after macOS alert is closedmacOS 警报关闭后子进程不会被杀死
【发布时间】:2017-05-12 23:19:36
【问题描述】:

我正在尝试编写一个显示 macOS 警报并同时启动警报的 python 脚本。

警报关闭后应该停止警报声,但事实并非如此。

def show_alert(message="Flashlight alarm"):
    """Display a macOS dialog."""
    message = json.dumps(str(message))
    exit_status = os.system("osascript dialog.scpt {0}".format(message))
    return exist_status

def play_alarm(file_name = "beep.wav", repeat=3):
    """Repeat the sound specified to mimic an alarm."""
    process = subprocess.Popen(['sh', '-c', 'while :; do afplay "$1"; done', '_', file_name], shell=False)
    return process

def alert_after_timeout(timeout, message, sound = True):
    """After timeout seconds, show an alert and play the alarm sound."""
    time.sleep(timeout)
    process = None
    if sound:
        process = play_alarm()
    exit_status = show_alert(message)
    if process is not None:
        os.killpg(os.getpgid(process.pid), signal.SIGINT)
        process.kill()
    # also, this below line doesn't seem to open an alert.
    show_alert(exit_status)

alert_after_timeout(1, "1s alarm")

上面的代码应该在开始循环警报声后显示一个 macOS 警报(在文件 beep.wav 中)。警报关闭后,警报声应立即停止。

AppleScript 文件dialog.scpt 触发警报,它只有几行:

on run argv
  tell application "System Events" to display dialog (item 1 of argv) with icon file (path of container of (path to me) & "Icon.png")
end run

【问题讨论】:

  • if process is not None: => if process:。不要和is比较。
  • 我宁愿创建一个线程来播放声音,而不是使用 subprocess 在 shell 中循环在后台运行......对我来说听起来很脆弱。
  • @Jean-FrançoisFabre 我刚刚实施了该建议 (if process:),但脚本仍然以同样的方式失败。
  • @Jean-FrançoisFabre 如果您告诉我如何在使用多线程后完成我的工作,我将不胜感激!我很想能够那样做,但不觉得我目前拥有自己做这件事的专业知识。我昨天花了大约 6 个小时试图找出如何从纯 python 中播放声音。
  • 我会尝试 os.kill(process.pid,signal.SIGTERM) 并删除 os.killpg 电话。我无法测试,我正在使用 windows

标签: python bash macos applescript subprocess


【解决方案1】:

我承认我不知道为什么你不能杀死你在 shell 中运行的进程,使用子进程来模拟作为后台运行......,并且在此之后没有其他命令运行的事实意味着某处可能存在死锁。所以让我们放弃那个解决方案。

让我提出一个更 Pythonic 的解决方案。音频播放部分改编自 how to play wav file in python?,但现在循环播放,也适用于 python 3。

这个想法是启动一个线程,仅使用 python 模块循环播放声音。线程知道全局变量。如果设置了stop_audio 变量,则线程知道它必须退出无限循环并停止播放。

您从其他程序控制标志。单击消息后,设置标志,音频立即停止播放。

import pyaudio
import wave
import threading

# global variable used to gently tell the thread to stop playing
stop_audio = False

def show_alert(message="Flashlight alarm"):
    """Display a macOS dialog."""
    message = json.dumps(str(message))
    exit_status = os.system("osascript dialog.scpt {0}".format(message))
    return exit_status

# initialize audio

def play_alarm(file_name = "beep.wav"):
    #define stream chunk
    chunk = 1024

    #open a wav format music
    f = wave.open(file_name,"rb")

    #instantiate PyAudio
    p = pyaudio.PyAudio()
    #open stream
    stream = p.open(format = p.get_format_from_width(f.getsampwidth()),
                    channels = f.getnchannels(),
                    rate = f.getframerate(),
                    output = True)

    while not stop_audio:
        f.rewind()
        #read data
        data = f.readframes(chunk)

        #play stream
        while data and not stop_audio:
            stream.write(data)
            data = f.readframes(chunk)

    #stop stream
    stream.stop_stream()
    stream.close()

    #close PyAudio
    p.terminate()


def alert_after_timeout(timeout, message, sound = True):
    """After timeout seconds, show an alert and play the alarm sound."""
    time.sleep(timeout)
    process = None
    if sound:
       t = threading.Thread(target=play_alarm,args=("beep.wav",))
       t.start()
    exit_status = show_alert(message)

    global stop_sound
    if sound:
        stop_sound = True  # tell the thread to exit
        t.join()

    show_alert(exit_status)

alert_after_timeout(1, "1s alarm")

请注意,我已经删除了 repeat=3 参数,因为它没有被使用并且我没有理解它。

不使用pyaudio 的替代方法是循环调用外部播放器,将上面的play_alarm 替换为:

def play_alarm(file_name = "beep.wav"):
    global stop_sound
    while not stop_sound:
        subprocess.call(["afplay",file_name])

stop_soundTrue 时,声音会一直播放到最后,但不会继续播放。所以效果不是瞬间的,而是简单的。

还有另一种以更具反应性的方式削减声音的方法:

def play_alarm(file_name = "beep.wav"):
    global stop_sound
    while not stop_sound:
        process = subprocess.Popen(["afplay",file_name])
        while not stop_sound:
           if process.poll() is not None:
               break  # process has ended
           time.sleep(0.1)  # wait 0.1s before testing process & stop_sound flag
        if stop_sound:
           process.kill()  # kill if exit by stop_sound

【讨论】:

  • 我不能再分配process了,对吧?因为函数什么都不返回?
  • 我没有名为pyaudio的模块,我不想全局安装它
  • 另外,您没有在答案中使用threading 模块:/
  • 哦,我忘记了一半的代码!你说得对。编辑,并根据您的命令行添加了更简单的声音播放。
  • 感谢您添加更简单的替代方案,但我真的在寻找声音瞬间中断的解决方案。 pyaudio 有替代品吗?
猜你喜欢
  • 1970-01-01
  • 2012-05-02
  • 1970-01-01
  • 2013-08-19
  • 2019-08-10
  • 2010-12-08
  • 1970-01-01
  • 2010-12-02
  • 1970-01-01
相关资源
最近更新 更多