在 Windows 上的 Python Popen 子进程中暂停 FFmpeg 编码

Posted

技术标签:

【中文标题】在 Windows 上的 Python Popen 子进程中暂停 FFmpeg 编码【英文标题】:Pause a FFmpeg encoding in a Python Popen subprocess on Windows 【发布时间】:2021-03-17 01:31:38 【问题描述】:

我正在尝试暂停 FFmpeg 在 非 shell 子进程中的编码(这对于它如何在更大的程序中发挥作用很重要)。这可以通过单独按键盘上的“暂停/中断”键来完成,我正在尝试将其发送到 Popen。

命令本身必须是跨平台兼容的,所以我不能以任何方式包装它,但我可以根据需要发送信号或运行特定于平台的函数。

我查看了how to send a "Ctrl+Break" to a subprocess via pid or handler,它建议发送一个信号,但这引发了“ValueError: Unsupported signal: 21”

from subprocess import Popen, PIPE
import signal


if __name__ == '__main__':
    command = "ffmpeg.exe -y -i example_video.mkv -map 0:v -c:v libx265 -preset slow -crf 18 output.mkv"
    proc = Popen(command, stdin=PIPE, shell=False)

    try:
        proc.send_signal(signal.SIGBREAK)
    finally:
        proc.wait()

然后尝试使用 GenerateConsoleCtrlEvent 创建 Ctrl+Break 事件,如此处所述https://docs.microsoft.com/en-us/windows/console/generateconsolectrlevent

from subprocess import Popen, PIPE
import ctypes


if __name__ == '__main__':
    command = "ffmpeg.exe -y -i example_video.mkv -map 0:v -c:v libx265 -preset slow -crf 18 output.mkv"
    proc = Popen(command, stdin=PIPE, shell=False)

    try:
        ctypes.windll.kernel32.GenerateConsoleCtrlEvent(1, proc.pid)
    finally:
        proc.wait()

我尝试过psutil 暂停功能,但即使在“暂停”时,它也会使 CPU 负载保持在很高的水平。

即使它不能与整个程序一起工作,我至少尝试过设置creationflags=CREATE_NEW_PROCESS_GROUP,这使得 SIGBREAK 不会出错,但也不会暂停它。因为 Ctrl-Break 事件将完全停止编码而不是暂停它。

【问题讨论】:

有什么解决办法吗?我一直在努力解决这个问题。 我认为发送c 到stdin 会导致显示命令菜单,效果是一样的。也许proc.stdin.write(b'c');proc.stdin.flush() 然后只是发送一个输入到标准输入简历?我认为这可能行得通? @sytech Pressing c 确实会暂停它以等待过滤器输入,并且可以跨平台工作,但存在导致一个 CPU 保持峰值的相同问题。而在 powershell 中使用 pause 按钮或在 linux 上使用 kill -STOP 可以让它们全部释放。然而,这可能是最好的答案,我只是在寻找不可能的答案。 @CasualDemon 在我的机器中,psutil 工作正常,并且在进程暂停时没有使用 CPU。在 Windows 中,PsSuspend 也可以使用。我测试的方式是在暂停和恢复之间添加sleep(60) 【参考方案1】:

Linux/Unix 解决方案:

import subprocess, os, signal
# ...
# Start the task:
proc = subprocess.Popen(..., start_new_session=True)
# ...
def send_signal_to_task(pid, signal):
        #gpid = os.getpgid(pid)  # WARNING This does not work
        gpid = pid  # But this does!
        print(f"Sending signal to process group gpid...")
        os.killpg(gpid, signal)
# ...
# Pause and resume:
send_signal_to_task(proc.pid, signal.SIGSTOP)
send_signal_to_task(proc.pid, signal.SIGCONT)

注意start_new_session=TruePopen 调用中并在send_signal_to_task 函数中使用os.killpg 而不是os.kill。其原因与您报告的即使处于暂停状态也有大量 CPU 使用率的原因相同。 ffmpeg 产生许多子进程。 start_new_session=True 将创建一个新的进程组,os.killpg 向组中的所有进程发送信号。

跨平台解决方案应该由psutil 模块提供,但您可能应该研究它是否支持进程组作为上述变体:

>>> import psutil
>>> psutil.pids()
[1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, 268, 1215,
 1216, 1220, 1221, 1243, 1244, 1301, 1601, 2237, 2355, 2637, 2774, 3932,
 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, 4263, 4282, 4306, 4311,
 4312, 4313, 4314, 4337, 4339, 4357, 4358, 4363, 4383, 4395, 4408, 4433,
 4443, 4445, 4446, 5167, 5234, 5235, 5252, 5318, 5424, 5644, 6987, 7054,
 7055, 7071]
>>> p = psutil.Process(7055)
>>> p.suspend()
>>> p.resume()

参考:https://pypi.org/project/psutil/

关于在 Windows 中模拟进程组的一些提示:Popen waiting for child process even when the immediate child has terminated

【讨论】:

以上是关于在 Windows 上的 Python Popen 子进程中暂停 FFmpeg 编码的主要内容,如果未能解决你的问题,请参考以下文章

为啥带有 shell=True 的 subprocess.Popen() 在 Linux 和 Windows 上的工作方式不同?

使用 subprocess.Popen 启动 pyuic5

python, windows : 用 shlex 解析命令行

如何在 Windows 上使用带有内置命令的 subprocess.Popen

python 使用``subprocess.Popen``修复python 2.7 windows unicode问题。

Python Popen communicate 和wait使用上的区别