Skip to content

Работа с дочерними процессами в Node: управляем ffmpeg

06/05/2010

Модуль child-process, входящий в Node.js, позволяет работать с дочерними процессами: порождать их, передавать и получать информацию в асинхронном режиме, управлять работой потока. В качестве примера я сделал простую обёртку для ffmpeg — программы для работы с видео.

Вначале нам надо вызвать сам ffmpeg и передать ему нужные параметры — файл, нужный формат, соотношение сторон (например, для перекодирования видео под формат экрана КПК):

var child_process = require('child_process');
var encoder = child_process.spawn('ffmpeg',['-i', "video.mkv", '-s', '300x200', '-f', 'avi', '-vcodec', 'mpeg4', 'video.avi']);

Все параметры передаются в виде массива строк. У ffmpeg очень много параметров настройки, но меня сейчас интересует в основном перекодирование видео в другой формат с изменением размера и битрейта — т.е., перекодирование для мобильных устройств.

Метод spawn() вернёт нам EventEmitter с событиями «data» (получение информации от потока) и «end» (закрытие потока). Просто так запустить перекодирование слишком просто, так что мы сделаем так чтобы скрипт выдавал на сколько процентов завершено перекодирование. По моему изначально ffmpeg не выдаёт проценты, зато при запуске он передаст нам длительность видеофайла и по мере перекодирования будет вместе с другими параметрами передавать длительность уже перекодированной части в секундах. Всё что нам нужно: найти в данных длину видео, перевести её в секунды и мы сможем легко вычислять процент выполнения на каждом шаге.

Для этого нам, во первых, надо буферизировать входящие данные (потому что от процесса они могут прийти например в двух частях) и искать в накопленных данных строку типа «Duration: 01:38:12.04«. Как только эта строка найдена — у нас есть длительность видеофайла:

var total_time = 0,
    total_data = '';

encoder.stderr.addListener('data', function(data) {
  if (data) {
    total_data += data.toString();
    if (total_data.toString().match(/Duration:\s\d\d:\d\d:\d\d\.\d\d/)) {
        var time = total_data.toString().match(/Duration:\s(\d\d:\d\d:\d\d\.\d\d)/).toString().substring(10,21);
        sys.puts('DATA:' + total_data.toString());
        sys.puts('Time:' + time);
        var seconds = parseInt(time.substr(0,2))*3600 + parseInt(time.substr(3,2))*60 + parseInt(time.substr(6,2));
        total_data = '';
        total_time = seconds;
        sys.puts('sec: ' + seconds);
    }

    // ... //

  }
});

Обратите внимание, мы слушаем поток stderr, не stdout. Именно на stderr ffmpeg выдаёт информацию о статусе перекодирования. Теперь, когда мы знаем продолжительность видеофайла в секундах, нам надо знать сколько секунд перекодировано к данному моменту. Ffmpeg отдаёт эти данные в довольно удобной форме:

frame=   84 fps= 18 q=10.0 size=       5kB time=1.68 bitrate=  26.1kbits/s

Нас интересует именно колонка time. Периодически encoder будет запускать обработчик события «data» с такой строкой в качестве аргумента. Нам надо выделить из строки длительность регулярным выражением, и мы сможем в реальном времени наблюдать прогресс перекодирования:

encoder.stderr.addListener('data', function(data) {
  if (data) {

    // .... //

    if (data.toString().substr(0,5) == 'frame') {
        var time = parseInt(data.toString().match(/time=\s*(\d*)\.\d*/)[0].toString().substr(5));
        sys.puts('Percent done: ' + ((time / total_time) * 100) + '% (' + time + ' of ' + total_time);
    }


  }
});

Мы проверяем, начинается ли строка на «frame» и просто извлекаем время регулярным выражением. Кстати, можно было бы изначально пойти другим путём: вычислить число кадров видео через длительность и битрейт, а потом считать прогресс по счётчику кадров.

При отладке этого скрипта я столкнулся ещё с одной задачей. Для тестирования я использовал один и тот же файл, и иногда ffmpeg говорил мне что целевой файл уже существует и предлагал ввести Y или n чтобы перезаписать его или оставить нетронутым. Я изменил скрипт чтобы он сам всегда перезаписывал файл:

encoder.stderr.addListener('data', function(data) {
  if (data) {

    // .... //

    if (data.toString().substr(data.toString().length - 6,5) == '[y/N]') {
        sys.puts('File already exist, overwriting');
        encoder.stdin.write('Y', 'ascii');
    }

  }
});

Здесь я вообще не делаю различий между разными вопросами ffmpeg. Если пришедшая строка заканчивается на [y/N], мы отправляем процессу ffmpeg символ Y, чтобы продолжить перекодирование.

И последнее: чтобы узнать что перекодирование закончилось (и с каким результатом) надо добавить обработчик на событие «exit», примерно так:

encoder.stderr.addListener('exit', function(data) {
    sys.puts('Encoding done: ' + data);
});

Теперь всё это можно обернуть в модуль для node.js, предоставляющий функцию encode, возвращающую EventEmitter, и например показывать на сайте прогресс перекодирования закачанного видео.

Ссылки по теме

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: