Страницы

четверг, 1 января 2015 г.

Node.js && (ImageMagick || FFmpeg) Stream

Поискал я готовые модули в npm для работы с ImageMagick и FFmpeg, и то что нашел мне не понравилось. Хотелось бы один поток на входе растасовывать на несколько потоков на выходе, и для этого написал я свой велосипед. В качестве модуля этот код я не вижу, а для примера сгодится. Смотрим внимательно...

Начнем с ImageMagick. Есть картинка 600х600:

Задача: сделать из нее несколько картинок размером поменьше.

Решение - im-resize.js:
var spawn = require('child_process').spawn;
var PassThrough = require('stream').PassThrough;
var fs = require('fs');
var filename = process.argv[2];
var sizes = process.argv.slice(3);
var i = sizes.length;
var time = timer();
var pass = new PassThrough();
var readable = fs.createReadStream(process.argv[2]).pipe(pass);
sizes.forEach(function(size) {
  var proc = spawn('convert', ['-resize', size, ':-', ':-']);
  proc.on('error', errHandler(size + ' process err:'));
  proc.stdin.on('error', errHandler(size + ' process stdinc err:'));
  proc.stdout.on('error', errHandler(size + ' process stdout err:'));
  proc.stderr.on('error', errHandler(size + ' process stderr err:'));
  proc.stderr.on('data', errHandler(size + ' process stderr data:'));
  proc.stdout.pipe(fs.createWriteStream((function() { // здесь собираем имя нового файла
    var arr = filename.split('.'), i = arr.length - 2;
    arr[i] = arr[i] + '-' + size;
    return arr.join('.');
  })()));
  proc.stdout.on('end', function() {
    console.log(size + ' is ready');
    if (!--i) console.log('Time: %s ms', time());
  });
  pass.pipe(proc.stdin);
});

function errHandler(s) {
  return function(err) {
    console.error(s, err.toString());
  }
}

function timer(s) {
  var time = Date.now();
  return function() {
    return Date.now() - time;
  }
}
Легким движением руки брюки превращаются...

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

Переходим к FFmpeg. Есть видео 640x360 в формате mp4:

Задача: сделать из него несколько видео размером поменьше в формате flv.

Код решения почти не отличается от предыдущего - ff-resize.js:
var spawn = require('child_process').spawn;
var PassThrough = require('stream').PassThrough;
var fs = require('fs');
var filename = process.argv[2];
var sizes = process.argv.slice(3);
var i = sizes.length;
var time = timer();
var pass = new PassThrough();
var readable = fs.createReadStream(process.argv[2]).pipe(pass);
sizes.forEach(function(size) {
  // если не выставить loglevel то получим много букафф в stderr data
  var proc = spawn('ffmpeg', ['-i', 'pipe:0', '-s', size, '-f', 'flv', 'pipe:1', '-loglevel', 'panic']);
  proc.on('error', errHandler(size + ' process err:'));
  proc.stdin.on('error', errHandler(size + ' process stdinc err:'));
  proc.stdout.on('error', errHandler(size + ' process stdout err:'));
  proc.stderr.on('error', errHandler(size + ' process stderr err:'));
  proc.stderr.on('data', errHandler(size + ' process stderr data:'));
  proc.stdout.pipe(fs.createWriteStream((function() { // здесь собираем имя нового файла
    var arr = filename.split('.'), i = arr.length - 2;
    arr[i] = arr[i] + '-' + size;
    arr[i + 1] = 'flv';
    return arr.join('.');
  })()));
  proc.stdout.on('end', function() {
    console.log(size + ' is ready');
    if (!--i) console.log('Time: %s ms', time());
  });
  pass.pipe(proc.stdin);
});

function errHandler(s) {
  return function(err) {
    console.error(s, err.toString());
  }
}

function timer(s) {
  var time = Date.now();
  return function() {
    return Date.now() - time;
  }
}
Выполняем, на этот раз все не так легко...

Вот как-то так. Ничего подобного в сети мне обнаружить не удалось. Может плохо искал, может потому что сегодня 1-е января нового 2015-го года... С новым годом!