Страницы

воскресенье, 7 февраля 2016 г.

Node.js FFmpeg Stream

Как определить наличие обложки в аудио файле и вытащить ее из потока с помощью FFmpeg на Node.js. Может пригодиться для загрузки музыки на веб-сайт, создания каталога музыкальных произведений из уже существующих файлов и т.п. Для приготовления понадобится аудио файл в формате mp3, без обложки, плюс пара картинок в формате JPG и PNG.



Для начала сбилдим пару файлов с обложками с помощью какого-нибудь редактора тэгов, я использую FFmpeg - по-моему не лучший вариант, но для теста пойдет. 

Код определения наличия обложки на Node.js может выглядеть примерно так - ffp.js:
'use strict';
 
const spawn = require('child_process').spawn;
const path = require('path');
const fs = require('fs');
 
const proc = spawn(
  'ffprobe', ['-i', 'async:pipe:0', '-show_entries', 'stream=codec_name,width,height', '-print_format', 'json', '-v', 'quiet'],
  { stdio: ['pipe', 'pipe', process.stderr] }
  );
const input = fs.createReadStream(path.join(__dirname, process.argv[2]));
const buffer = [];
const onError = (err) => { throw err };
 
input.on('error', onError);
proc.on('error', onError);
proc.stdin.on('error', onError);
proc.stdout.on('error', onError);
 
proc.stdout.on('readable', () => {
  let chunk;
  while ((chunk = proc.stdout.read()) !== null) {
    buffer.push(chunk);
  }
});
proc.stdout.on('end', () => console.log(JSON.parse(Buffer.concat(buffer).toString())));
 
input.pipe(proc.stdin);


Вытаскиваем обложки.

Код извлечения обложки на Node.js может выглядеть следующим образом - ffm.js:
'use strict';
 
const path = require('path');
const spawn = require('child_process').spawn;
const fs = require('fs');
 
const proc = spawn(
  'ffmpeg', ['-i', 'pipe:0', '-f', 'image2', '-c:v', 'copy', 'pipe:1', '-v', 'quiet'],
  { stdio: ['pipe', 'pipe', process.stderr] }
  );
const input = fs.createReadStream(path.join(__dirname, process.argv[2]));
const output = fs.createWriteStream(path.join(__dirname, process.argv[3]));
const onError = (err) => { throw err };
 
input.on('error', onError);
output.on('error', onError);
proc.on('error', onError);
proc.stdin.on('error', onError);
proc.stdout.on('error', onError);
 
input.pipe(proc.stdin);
proc.stdout.pipe(output);


Не смотря на то, что я намеренно перепутал формат обложки каждого файла, на результат это никак не повлияло, на скриншоте можно определить "реальный" формат по размеру файла: v1 - JPG, v2 - PNG.

На десерт сбилдим файл с обложками обоих форматов.

И снова по размеру обложек извлеченных из файла можно догадаться что в результате мы получили JPG.

Если один из видео кодеков окажется битым, может помочь определение индекса в команде извлечения видео кодека - ffm.js:
'use strict';
 
const path = require('path');
const spawn = require('child_process').spawn;
const fs = require('fs');
 
const proc = spawn(
  'ffmpeg', ['-i', 'pipe:0', '-f', 'image2', '-c:v:2', 'copy', 'pipe:1', '-v', 'quiet'],
  { stdio: ['pipe', 'pipe', process.stderr] }
  );
const input = fs.createReadStream(path.join(__dirname, process.argv[2]));
const output = fs.createWriteStream(path.join(__dirname, process.argv[3]));
const onError = (err) => { throw err };
 
input.on('error', onError);
output.on('error', onError);
proc.on('error', onError);
proc.stdin.on('error', onError);
proc.stdout.on('error', onError);
 
input.pipe(proc.stdin);
proc.stdout.pipe(output);

That's all folks!