Skip to content

Instantly share code, notes, and snippets.

@erhhung
Created October 7, 2016 02:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save erhhung/d29c9a8c099e39d4c1cb0e80cf508c99 to your computer and use it in GitHub Desktop.
Save erhhung/d29c9a8c099e39d4c1cb0e80cf508c99 to your computer and use it in GitHub Desktop.
Node.js class to encode a sequence of image files into VP9 WEBM video using FFmpeg.
/*
* encode image sequence into WEBM video
*/
import {exec} from 'child-process-promise';
import format from 'string-template';
import fsAsync from 'file-async';
import shell from 'shelljs';
import path from 'path';
import os from 'os';
/*
* ffmpeg must be installed with libvpx support (specify
* --with-libvpx when using Homebrew and --enable-libvpx
* when building from source), and be on the system path
*/
const FFMPEG_BIN = shell.which('ffmpeg');
if (! FFMPEG_BIN) {
throw new Error(`FFmpeg not found`);
}
const DEV_NULL = /Windows/.test(os.type()) ? '\\\\.\\NUL' : '/dev/null'
, FFMPEG_TEMP = path.join(os.tmpdir(), 'ffmpeg2pass')
, FFMPEG_LOG = FFMPEG_TEMP + '-0.log';
// ffmpeg VP9 encoding guide:
// http://wiki.webmproject.org/ffmpeg/vp9-encoding-guide
const FFMPEG_BASE = FFMPEG_BIN + `\
-loglevel error -y -i {input} -r {rate}\
-c:v libvpx-vp9 -pass {pass} -passlogfile ${FFMPEG_TEMP}\
-b:v 0 -crf 55 -pix_fmt yuv420p -threads 8 -speed {speed}\
-tile-columns 6 -frame-parallel 1`
, FFMPEG_PASS1 = FFMPEG_BASE + `\
-f webm {output}`
, FFMPEG_PASS2 = FFMPEG_BASE + `\
-auto-alt-ref 1 -lag-in-frames 25 {output}`
, PASS1_SPEED = 4
, PASS2_SPEED = 1;
export default class Images2Video {
/**
* @param filePattern - file path plus name pattern
* containing "%d", "%02d" etc
* @param frameRate - playback rate (default 30)
*/
constructor(filePattern, frameRate = 30) {
if (!/%0?\d?d/.test(filePattern)) {
throw new Error(`invalid file name pattern`);
}
this.args = {
input: filePattern
, rate: +frameRate
};
}
/**
* encode image files into video
*
* @param destFile - output .webm file
* @return {Promise}
*/
async encode(destFile = "video.webm") {
if (!destFile.endsWith('.webm')) {
throw new Error(`output file must be .webm`);
}
await this._pass1();
await this._pass2(destFile);
}
async _pass1() {
console.log(`FFmpeg pass 1:`, FFMPEG_LOG);
let args = Object.assign({
pass: 1
, speed: PASS1_SPEED
, output: DEV_NULL
}, this.args);
let command = format(FFMPEG_PASS1, args)
, result = await exec(command);
if (result.stderr ||
!await fsAsync.exists(FFMPEG_LOG)) {
throw new Error(`FFmpeg pass 1 failed:
${result.stderr}`);
}
}
async _pass2(destFile) {
console.log(`FFmpeg pass 2:`, destFile);
let args = Object.assign({
pass: 2
, speed: PASS2_SPEED
, output: destFile
}, this.args);
let command = format(FFMPEG_PASS2, args)
, result = await exec(command);
await fsAsync.remove(FFMPEG_LOG);
if (result.stderr ||
!await fsAsync.exists(destFile)) {
throw new Error(`FFmpeg pass 2 failed:
${result.stderr}`);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment