macOS警报关闭后,子进程不会被终止

时间:2016-12-28 16:28:10

标签: python bash macos applescript subprocess

我正在尝试编写一个显示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

1 个答案:

答案 0 :(得分: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