Уничтожение процесса Python из Node не убивает дочерний процесс Python (дочерним процессом является ffmpeg.exe)

Я разрабатываю приложение Electron. В этом приложении я создаю процесс Python с путем к файлу в качестве аргумента, а затем сам файл передается в ffmpeg (через модуль ffmpeg-python), а затем проходит через некоторые функции Tensorflow.

Я пытаюсь обработать случай, когда пользователь закрывает приложение Electron, пока идет весь фоновый процесс. Однако из моих тестов кажется, что процесс ffmpeg остается в рабочем состоянии, несмотря ни на что. Я работаю в Windows, смотрю на диспетчер задач и не понимаю, что происходит: при закрытии окна приложения Electron иногда ffmpeg.exe будет отдельным процессом, иногда он остается в Electron группа процессов.

Я заметил, что если я убью процесс Electron, закрыв окно, процесс python также закроется, как только ffmpeg выполнит свою работу, поэтому я думаю, что это работает наполовину. Проблема в том, что ffmpeg выполняет интенсивную работу, и если пользователю нужно закрыть окно, то процесс ffmpeg также НЕОБХОДИМО убить. Но никак не могу этого добиться.

Я пробовал пару вещей, поэтому я вставлю код:

main.js

// retrieve video data
ipcMain.handle('get-games', async (event, arg) => {
    const spawn = require('child_process').spawn;
    const pythonProcess = spawn('python', ["./backend/predict_games.py", arg]);

    // sets pythonProcess as a global variable to be accessed when quitting the app
    global.childProcess = pythonProcess;

    return new Promise((resolve, reject) => {
        let result = "";

        pythonProcess.stdout.on('data', async (data) => {
            data = String(data);

            if (data.startsWith("{"))
                result = JSON.parse(data);
        });

        pythonProcess.on('close', () => {
            resolve(result);
        })

        pythonProcess.on('error', (err) => {
            reject(err);
        });
    })
});

app.on('before-quit', function () {
    global.childProcess.kill('SIGINT');
});

predict_games.py (часть ffmpeg)

def convert_video_to_frames(fps, input_file):
    # a few useful directories
    local_dir = os.path.dirname(os.path.abspath(__file__))
    snapshots_dir = fr"{local_dir}/snapshots/{input_file.stem}"

    # creates snapshots folder if it doesn't exist
    Path(snapshots_dir).mkdir(parents=True, exist_ok=True)

print(f"Processing: {Path(fr'{input_file}')}")
try:
    (
        ffmpeg.input(Path(input_file))
        .filter("fps", fps=fps)
        .output(f"{snapshots_dir}/%d.jpg", s = "426x240", start_number=0)
        .run(capture_stdout=True, capture_stderr=True)
    )
except ffmpeg.Error as e:
    print("stdout:", e.stdout.decode("utf8"))
    print("stderr:", e.stderr.decode("utf8"))

Кто-нибудь знает?

Кажется, вам нужен механизм, чтобы убить процесс python от электрона. Процесс ffmpeg должен быть убит вместе с ним в процессе. Если нет, вам нужно получить экземпляр Fmpeg Popen и убить его, когда python получит запрос на завершение от js. Наконец, почему вы пишете файл jpg вместо того, чтобы получать данные напрямую через канал stdout?

kesh 02.04.2022 14:02

@kesh Спасибо за ваш ответ, я совсем не знаком с IPC, поэтому не уверен, что понимаю вас. Итак, вы говорите, что я должен каким-то образом получить экземпляр popen ffmpeg со стороны скрипта Python, а затем каким-то образом убить экземпляр, как только Python получит сигнал SIGINT/SIGTERM (не уверен, что есть разница), это правильно? У вас есть какие-либо ресурсы относительно того, как я могу это сделать? Я пишу jpg просто потому, что это расширение, которое мне нужно, чтобы ffmpeg дал мне, если я что-то не упустил.

Impasse 02.04.2022 22:20

Первое, что я бы попробовал, это вызвать pythonProcess.kill(), когда пользователь закрывает окно (может быть, событие close окна браузера? Я давно не использовал электрон). Это также должно убить подпроцесс FFmpeg. Если это не сработает (pythonProcess мертв, но процесс FFmpeg жив), только тогда подумайте о том, чтобы вернуть Popen из ffmpeg-python (вам нужно посмотреть его документацию о том, как вернуть запущенный процесс)

kesh 02.04.2022 22:57

ре: jpg. ну, да, я имел в виду, почему вы не получаете данные кадра напрямую через канал stdout, конвертируете их в массив numpy и передаете их Tensorflow. Это должно быть намного быстрее, чем запись кадров на диск. (это, очевидно, не связано с вашим вопросом, мне просто было любопытно.)

kesh 02.04.2022 23:00

о, мой плохой, я слепой, как обычно. Только сейчас я увидел ваш обратный звонок before-quit. Как только вы поймете, как получить объект Popen, см. эта почта, чтобы убить процесс FFmpeg при выходе из Python.

kesh 02.04.2022 23:25

Я пробовал подход pythonProcess.kill(), но, к сожалению, он не работает. Я попробовал пару вещей и понял, что процесс, который я убиваю, имеет другой PID, чем ffmpeg (сравнивая атрибут .pid с pid в диспетчере задач). Когда я закрываю приложение Electron, я просто убиваю процесс Python, но процесс ffmpeg просто остается в живых. Также я не могу понять закономерность, но либо группа процессов Electron остается открытой (только с ffmpeg.exe внутри нее), либо ffmpeg.exe появляется как отдельный процесс (с тем же PID, что и раньше). Я честно не понимаю.

Impasse 02.04.2022 23:32

@kesh Не беспокойся, если честно, большое спасибо за то, что помог мне с этим. Я посмотрю вашу ссылку, когда проснусь завтра, еще раз спасибо и хорошего дня / ночи!

Impasse 02.04.2022 23:33

@kesh Наконец-то я нашел решение и поставил его в качестве ответа! Кроме того, чтобы ответить на вопрос: jpg. Честно говоря, я не думал об этом, я тоже новичок в TF, поэтому я думаю, что это то, что я считал лучшим решением в то время, ха-ха. В ближайшее время изменю его в соответствии с вашим предложением, так что большое спасибо за подсказку!

Impasse 03.04.2022 10:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
8
42
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо, наконец-то я смог это исправить! Поскольку ffmpeg-python — это просто набор привязок для старого доброго ffmpeg, сам исполняемый файл по-прежнему лежит в основе модуля. Это также означает, что при запуске ffmpeg экран выглядит примерно так:

... 

    Metadata:
      handler_name    : VideoHandler
      vendor_id       : [0][0][0][0]
  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 159 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
      vendor_id       : [0][0][0][0]
Stream mapping:
  Stream #0:0 (h264) -> fps:default
  fps:default -> Stream #0:0 (mjpeg)

...

Press [q] to stop, [?] for help

это то, что происходит за кулисами. Как только я понял это, все, что мне нужно было сделать, это найти способ отправить «q» на стандартный ввод ffmpeg. И я сделал это, добавив этот фрагмент в событие window-all-closed:

app.on('window-all-closed', () => {
    // writes a 'q' in ffmpeg's terminal to quit ffmpeg's process
    // reminder: python gets closed when the Electron app is closed
    global.childProcess.stdin.write("q\n");

    if (process.platform !== 'darwin') app.quit()
})

Сам скрипт Python остался нетронутым по сравнению с фрагментом в вопросе, и это единственное, что я в итоге изменил. Теперь каждый раз, когда я выхожу из своего приложения Electron, ffmpeg получает «q». Процесс Python не нужно останавливать вручную, потому что Electron уже делает это за вас.

Итак, проблема решена. :)

Другие вопросы по теме