Compare commits

...

10 Commits

Author SHA1 Message Date
litter 76d1feddf0 Merge remote-tracking branch 'origin/master' 2019-10-29 18:08:59 -04:00
litter b1b460d21a Update README to reflect changes in API. Now app supports negatives and swapping ffmpeg for avconv (libav). 2019-10-29 18:08:31 -04:00
litter 122a0aa404 Switch from nexe to pkg build process (faster, works) 2019-10-29 18:07:38 -04:00
litter 81543f3583 Reference ./package.json for pkg build process. 2019-10-29 18:07:20 -04:00
mmcwilliams 23c8b9f91b Fixed nexe entry in package to match build script 2019-10-25 12:23:11 -04:00
mmcwilliams cb9dfe3f00 Add alternate executable flag. Add "negative" flag for inverting colors. 2019-10-25 12:12:35 -04:00
mmcwilliams 18424141e0 Update Readme 2019-04-17 16:10:08 -04:00
mmcwilliams 741132933b Build process using nexe. 2019-04-17 15:39:32 -04:00
mmcwilliams 8545858f29 Add a new docs script. Build docs as npm run docs 2019-04-17 14:48:02 -04:00
mmcwilliams 0001f73329 Converted codebase to typescript.
Added new feature, custom page sizes.
Use arguments -w,--width and -l,--length to specify the size in inches.
Still defaults to 8.5x11in
2019-04-17 14:45:32 -04:00
9 changed files with 3328 additions and 215 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
node_modules
tmp/*
temp/*
.nexe
.nexe
dist
.DS_Store

View File

@ -22,18 +22,23 @@ Options
Options:
-V, --version output the version number
-i, --input <path> Video source to print to film strip, anything that avconv can read
-o, --output <path> Output directory, will print images on A4 standard paper file
-d, --dpi <dpi> DPI output pages
-f, --film <gauge> Choose film gauge: 16mm, super16, 35mm
-v, --verbose Run in verbose mode
-h, --help output usage information
-V, --version output the version number
-i, --input <path> Video source to print to film strip, anything that avconv can read
-o, --output <path> Output directory, will render images on specified page size
-d, --dpi <dpi> DPI output pages
-f, --film <gauge> Choose film gauge: 16mm, super16, 35mm
-w, --width <inches> Output page width, in inches. Default 8.5
-l, --length <inches> Output page length, in inches. Default 11
-e, --executable <binary> Alternate binary to use in place of avconv
-v, --verbose Run in verbose mode
-n, --negative Invert color channels to create negative
-h, --help output usage information
```
Releases
--------
* [1.2.1](https://github.com/sixteenmillimeter/v2f/releases/tag/1.2.1)
* [1.1.1](https://github.com/sixteenmillimeter/v2f/releases/tag/1.1.1)
* [1.0.0](https://github.com/sixteenmillimeter/v2f/releases/tag/1.0.0)

51
build.js Normal file
View File

@ -0,0 +1,51 @@
'use strict'
const { exec } = require('pkg')
const execRaw = require('child_process').exec
const os = require('os')
const fs = require('fs-extra')
const packageJson = require('./package.json')
const platform = os.platform()
const arch = os.arch()
/**
* Shells out to execute a command with async/await.
* Async wrapper to exec module.
*
* @param {string} cmd Command to execute
*
* @returns {Promise} Promise containing the complete stdio
**/
async function shell_out (cmd) {
return new Promise((resolve, reject) => {
return execRaw(cmd, (err, stdio, stderr) => {
if (err) return reject(err)
return resolve(stdio)
})
})
}
//exec(args) takes an array of command line arguments and returns a promise. For example:
if (!fs.existsSync(`./dist/${platform}_${arch}`)) {
fs.mkdirSync(`./dist/${platform}_${arch}`)
}
console.log(`Building v2f and saving in dist/${platform}_${arch}...`)
console.time('v2f')
exec([ 'v2f.js', '--target', 'node10', '--output', `./dist/${platform}_${arch}/v2f` ]).then(async (res) => {
try {
await shell_out(`zip -r ./dist/v2f_${platform}_${arch}_${packageJson.version}.zip ./dist/${platform}_${arch}/v2f`)
console.log(`Compressed binary to dist/v2f_${platform}_${arch}_${packageJson.version}.zip`)
} catch (err) {
console.error(err)
process.exit(err)
}
console.timeEnd('v2f')
console.log('built')
}).catch(err => {
console.error(err)
})
// do something with app.exe, run, test, upload, deploy, etc

View File

@ -1,36 +1,50 @@
## Functions
<dl>
<dt><a href="#convert">convert(path, dpi, length)</a></dt>
<dd><p>Turn video into sheet of images</p>
<dt><a href="#initialize">initialize(Commander)</a></dt>
<dd></dd>
<dt><a href="#convert">convert(input, dpi, length)</a></dt>
<dd><p>Create image sequence from source video, using</p>
</dd>
<dt><a href="#stitch">stitch(loc, dim)</a></dt>
<dt><a href="#stitch">stitch(output, dim, next, pageW, pageL)</a></dt>
<dd><p>Stitch rendered frames into strips</p>
</dd>
</dl>
<a name="initialize"></a>
## initialize(Commander)
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| Commander | <code>Object</code> | object |
<a name="convert"></a>
## convert(path, dpi, length)
Turn video into sheet of images
## convert(input, dpi, length)
Create image sequence from source video, using
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| path | <code>String</code> | file path (absolute) |
| input | <code>String</code> | file path (absolute) |
| dpi | <code>Integer</code> | target printing dpi |
| length | <code>Integer</code> | strip length in frames |
<a name="stitch"></a>
## stitch(loc, dim)
## stitch(output, dim, next, pageW, pageL)
Stitch rendered frames into strips
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| loc | <code>String</code> | Path of folder containing frames |
| output | <code>String</code> | Path of folder containing frames |
| dim | <code>Object</code> | Dimensions object |
| next | <code>function</code> | Async lib callback function |
| pageW | <code>Integer</code> | Page width in inches |
| pageL | <code>Integer</code> | Page length in inches |

2818
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,32 @@
{
"name": "v2f",
"version": "1.1.0",
"version": "1.2.1",
"description": "Turn a video into strips of precise 16mm-size stills",
"main": "v2f.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"build": "node build.js",
"compile": "./node_modules/.bin/tsc ./src/v2f.ts --outFile ./v2f.js --noImplicitAny --lib ES2017 --lib ES2016 -t ES2016",
"docs": "./node_modules/.bin/jsdoc2md ./v2f.js > ./docs/Readme.md"
},
"author": "mmcwilliams",
"license": "MIT",
"nexe": {
"output": "../video_to_page_nexe/v2f",
"runtime": {
"ignoreFlags": true,
"framework": "nodejs",
"version": "8.7.0"
}
},
"dependencies": {
"async": "^2.5.0",
"async": "^2.6.2",
"commander": "^2.11.0"
},
"devDependencies": {
"@types/node": "^11.13.4",
"fs-extra": "^8.1.0",
"jsdoc-to-markdown": "^4.0.1",
"pkg": "^4.4.0",
"typescript": "^3.4.3"
},
"pkg": {
"scripts": [
"./v2f.js",
"./package.json"
]
}
}

4
scripts/build.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
mkdir -p dist
./node_modules/.bin/nexe ./v2f.js --build --output ./dist/v2f

231
src/v2f.ts Normal file
View File

@ -0,0 +1,231 @@
'use strict'
const cmd = require('commander')
const async = require('async')
const exec = require('child_process').exec
const fs = require('fs')
const path = require('path')
const os = require('os')
const osTmp : string = os.tmpdir()
const TMP : string = path.join(osTmp, '/v2f/')
const pkg : any = require('./package.json')
class Dimensions{
h : number;
w : number;
o : number;
dpi : number;
film : any;
constructor (filmStr : string, dpi : number) {
const IN : number = dpi / 25.4
const film : any = this._gauge(filmStr)
this.h = Math.round(film.h * IN) //frame height
this.w = Math.round(film.w * IN) //frame width
this.o = Math.round(film.o * IN) //space between columns
this.dpi = dpi
this.film = film
}
_gauge (film : string) {
if (film === '16mm') {
return {
h: 7.62,
w : 10.5,
o : 16
}
} else if (film === 'super16'){
return {
h: 7.62,
w : 12.75,
o : 16
}
} else if (film === '35mm') {
return {
h : 19.05,
w : 22,
o : 35
}
} else {
error('Film type not found, see --help for more info')
}
}
}
/**
*
*
* @function
* @param {Object} Commander object
*/
function initialize (command : any) {
const dpi : number = command.dpi || 300
const film : string = command.film || '16mm'
const input : string = command.input || command.args[0] || error('No input file, see --help for more info')
const output : string = command.output || command.args[1] || error('No ouput directory, see --help for more info')
const dim : any = new Dimensions(film, dpi)
const pageW : number = command.width || 8.5
const pageL : number = command.length || 11
const exe : string = command.executable || 'avconv'
const negative : boolean = typeof command.negative !== 'undefined' ? true : false
if (!fs.existsSync(input)) error(`Video "${input}" cannot be found`)
async.series([
(next : any)=> {
convert(exe, input, dim, negative, next)
},
(next : any) => {
stitch(output, dim, next, pageW, pageL)
},
cleanup
], () => {
console.log(`Finished creating pages`)
})
}
/** *
* Create image sequence from source video, using
*
* @function
* @param {String} input file path (absolute)
* @param {Integer} dpi target printing dpi
* @param {Integer} length strip length in frames
*
*/
function convert (exe : string, input : string, dim : any, negative : boolean = false, next : any) {
const file : string = input.split('/').pop()
const negStr : string = negative ? `-vf lutrgb="r=negval:g=negval:b=negval"` : '';
const execStr : string = `${exe} -i "${input}" -s ${dim.w}x${dim.h} -qscale 1 ${negStr} "${TMP}v2f_sequence_%04d.jpg"`
console.log(`Converting ${file}...`)
console.log(`Exporting all frames with aspect ratio: ${dim.w / dim.h}...`)
if (!fs.existsSync(TMP)) fs.mkdirSync(TMP)
exec(execStr, (ste : any, std : string) => {
if (ste) {
return error(ste)
}
console.log('Frames exported successfully!')
next()
})
}
/** *
* Stitch rendered frames into strips
* @function
* @param {String} output Path of folder containing frames
* @param {Object} dim Dimensions object
* @param {Function} next Async lib callback function
* @param {Integer} pageW Page width in inches
* @param {Integer} pageL Page length in inches
*
*/
function stitch (output : string, dim : any, next : any, pageW : number, pageL : number) {
const length : number = Math.floor((pageL * dim.dpi) / dim.h) - 1
const width : number = Math.floor((pageW * dim.dpi / dim.o)) - 1
const loc : string = TMP.substring(0, TMP.length - 1)
const diff : number = Math.round((dim.o - dim.w) / 2)
let page : number = 0
let pageCount : number = 0
let cmd : string = `find "${loc}" -type f -name "v2f_sequence_*.jpg"`
console.log('Stitching frames into sheets...')
console.log(`Sheets will contain ${width}x${length} frames...`)
exec(cmd, (ste : any, std : string) => {
if (ste) {
return error(ste)
}
let jobs : any[] = []
let cmds : string[] = []
let frames : string[] = std.split('\n')
let execStr : string = 'montage '
let pagePath : string = ``
let i : number = 0
frames = frames.filter((elem : string) => {
if (elem.indexOf('find: ') === -1) {
return elem
}
})
frames.sort()
for (let frame of frames) {
execStr += `${frame} `
if ((i + 1) % (width * length) === 0 || i === frames.length - 1) {
pagePath = path.join(output, `./page_${pad(page)}.jpg`)
execStr += `\ -tile 1x${length} -geometry ${dim.w}x${dim.h}+${diff}+0 miff:- |\ \nmontage - -geometry +0+0 -tile ${width}x1 -density ${dim.dpi} "${pagePath}"`
cmds.push(execStr)
execStr = 'montage '
page++
}
i++
}
jobs = cmds.map((cmd : string) => {
return (cb : any) => {
exec(cmd, (err : any, std : string, ste : string) => {
if (err) {
return error(err)
}
console.log(`Created page of ${width}x${length} frames!`)
cb()
})
}
})
async.series(jobs, next)
})
}
function cleanup (next : any) {
console.log('Cleaning up...');
exec(`rm -r "${TMP}"`, (err : any) => {
if (err) console.error(err)
if (next) next()
})
}
function pad (n : number) {
return ('00000' + n).slice(-5)
}
var error = function (err : any) {
if (process.argv.indexOf('-v') !== -1 || process.argv.indexOf('--verbose') !== -1){
console.error(err)
} else {
console.error('Error running program. Run in verbose mode for more info (-v,--verbose)')
}
process.exit(1)
}
process.on('uncaughtException', err => {
error(err)
})
//convert(process.argv[2], process.argv[3])
//fix for nexe
let args = [].concat(process.argv)
if (args[1].indexOf('v2f.js') === -1) {
args.reverse()
args.push('node')
args.reverse()
}
cmd.arguments('<input> <output>')
.version(pkg.version)
.option('-i, --input <path>', 'Video source to print to film strip, anything that avconv can read')
.option('-o, --output <path>', 'Output directory, will render images on specified page size')
.option('-d, --dpi <dpi>', 'DPI output pages')
.option('-f, --film <gauge>', 'Choose film gauge: 16mm, super16, 35mm')
.option('-w, --width <inches>', 'Output page width, in inches. Default 8.5')
.option('-l, --length <inches>', 'Output page length, in inches. Default 11')
.option('-e, --executable <binary>', 'Alternate binary to use in place of avconv, ie ffmpeg')
.option('-v, --verbose', 'Run in verbose mode')
.option('-n, --negative', 'Invert color channels to create negative')
.parse(args)
initialize(cmd)

355
v2f.js Executable file → Normal file
View File

@ -1,106 +1,105 @@
/*jshint strict: true, esversion:6, node: true, asi: true*/
'use strict'
const cmd = require('commander')
const async = require('async')
const exec = require('child_process').exec
const fs = require('fs')
const path = require('path')
const TMP = '/tmp/v2f/'
class Dimensions{
constructor (filmStr, dpi) {
const IN = dpi / 25.4
const film = this._gauge(filmStr)
this.h = Math.round(film.h * IN) //frame height
this.w = Math.round(film.w * IN) //frame width
this.o = Math.round(film.o * IN) //space between columns
this.dpi = dpi
this.film = film
}
_gauge (film) {
if (film === '16mm') {
return {
h: 7.62,
w : 10.5,
o : 16
}
} else if (film === 'super16'){
return {
h: 7.62,
w : 12.75,
o : 16
}
} else if (film === '35mm') {
return {
h : 19.05,
w : 22,
o : 35
}
} else {
error('Film type not found, see --help for more info')
}
}
'use strict';
const cmd = require('commander');
const async = require('async');
const exec = require('child_process').exec;
const fs = require('fs');
const path = require('path');
const os = require('os');
const osTmp = os.tmpdir();
const TMP = path.join(osTmp, '/v2f/');
const pkg = require('./package.json');
class Dimensions {
constructor(filmStr, dpi) {
const IN = dpi / 25.4;
const film = this._gauge(filmStr);
this.h = Math.round(film.h * IN); //frame height
this.w = Math.round(film.w * IN); //frame width
this.o = Math.round(film.o * IN); //space between columns
this.dpi = dpi;
this.film = film;
}
_gauge(film) {
if (film === '16mm') {
return {
h: 7.62,
w: 10.5,
o: 16
};
}
else if (film === 'super16') {
return {
h: 7.62,
w: 12.75,
o: 16
};
}
else if (film === '35mm') {
return {
h: 19.05,
w: 22,
o: 35
};
}
else {
error('Film type not found, see --help for more info');
}
}
}
/**
*
*
*
* @function
* @param {Object} Commander object
*/
function initialize (command) {
const dpi = command.dpi || 300
const film = command.film || '16mm'
const input = command.input || command.args[0] || error('No input file, see --help for more info')
const output = command.output || command.args[1] || error('No ouput directory, see --help for more info')
const dim = new Dimensions(film, dpi)
if (!fs.existsSync(input)) error(`Video "${input}" cannot be found`)
async.series([
next => {
convert(input, dim, next)
},
next => {
stitch(output, dim, next)
},
cleanup
], () => {
console.log(`Finished creating pages`)
})
function initialize(command) {
const dpi = command.dpi || 300;
const film = command.film || '16mm';
const input = command.input || command.args[0] || error('No input file, see --help for more info');
const output = command.output || command.args[1] || error('No ouput directory, see --help for more info');
const dim = new Dimensions(film, dpi);
const pageW = command.width || 8.5;
const pageL = command.length || 11;
const exe = command.executable || 'avconv';
const negative = typeof command.negative !== 'undefined' ? true : false;
if (!fs.existsSync(input))
error(`Video "${input}" cannot be found`);
async.series([
(next) => {
convert(exe, input, dim, negative, next);
},
(next) => {
stitch(output, dim, next, pageW, pageL);
},
cleanup
], () => {
console.log(`Finished creating pages`);
});
}
/** *
* Create image sequence from source video, using
*
*
* @function
* @param {String} input file path (absolute)
* @param {Integer} dpi target printing dpi
* @param {Integer} length strip length in frames
*
*
*/
function convert (input, dim, next) {
const file = input.split('/').pop()
const execStr = `avconv -i "${input}" -s ${dim.w}x${dim.h} -qscale 1 "${TMP}v2f_sequence_%04d.jpg"`
console.log(`Converting ${file}...`)
console.log(`Exporting all frames with aspect ratio: ${dim.w / dim.h}...`)
if (!fs.existsSync(TMP)) fs.mkdirSync(TMP)
exec(execStr, (ste, std) => {
if (ste) {
return error(ste)
}
console.log('Frames exported successfully!')
next()
})
function convert(exe, input, dim, negative = false, next) {
const file = input.split('/').pop();
const negStr = negative ? `-vf lutrgb="r=negval:g=negval:b=negval"` : '';
const execStr = `${exe} -i "${input}" -s ${dim.w}x${dim.h} -qscale 1 ${negStr} "${TMP}v2f_sequence_%04d.jpg"`;
console.log(`Converting ${file}...`);
console.log(`Exporting all frames with aspect ratio: ${dim.w / dim.h}...`);
if (!fs.existsSync(TMP))
fs.mkdirSync(TMP);
exec(execStr, (ste, std) => {
if (ste) {
return error(ste);
}
console.log('Frames exported successfully!');
next();
});
}
/** *
* Stitch rendered frames into strips
@ -108,105 +107,103 @@ function convert (input, dim, next) {
* @param {String} output Path of folder containing frames
* @param {Object} dim Dimensions object
* @param {Function} next Async lib callback function
*
* @param {Integer} pageW Page width in inches
* @param {Integer} pageL Page length in inches
*
*/
function stitch (output, dim, next) {
const length = Math.floor((11 * dim.dpi) / dim.h) - 1
const width = Math.floor((8.5 * dim.dpi / dim.o)) - 1
const loc = TMP.substring(0, TMP.length - 1)
const diff = Math.round((dim.o - dim.w) / 2)
let page = 0
let pageCount = 0
let cmd = `find "${loc}" -type f -name "v2f_sequence_*.jpg"`
console.log('Stitching frames into sheets...')
console.log(`Sheets will contain ${width}x${length} frames...`)
exec(cmd, (ste, std) => {
if (ste) {
return error(ste)
}
let jobs = []
let cmds = []
let frames = std.split('\n')
let execStr = 'montage '
let pagePath = ``
let i = 0
frames = frames.filter(elem => {
if (elem.indexOf('find: ') === -1) {
return elem
}
})
frames.sort()
for (let frame of frames) {
execStr += `${frame} `
if ((i + 1) % (width * length) === 0 || i === frames.length - 1) {
pagePath = path.join(output, `./page_${pad(page)}.jpg`)
execStr += `\ -tile 1x${length} -geometry ${dim.w}x${dim.h}+${diff}+0 miff:- |\ \nmontage - -geometry +0+0 -tile ${width}x1 -density ${dim.dpi} "${pagePath}"`
cmds.push(execStr)
execStr = 'montage '
page++
}
i++
}
jobs = cmds.map(cmd => {
return cb => {
exec(cmd, (err, std, ste) => {
if (err) {
return error(err)
}
console.log(`Created page of ${width}x${length} frames!`)
cb()
})
}
})
async.series(jobs, next)
})
function stitch(output, dim, next, pageW, pageL) {
const length = Math.floor((pageL * dim.dpi) / dim.h) - 1;
const width = Math.floor((pageW * dim.dpi / dim.o)) - 1;
const loc = TMP.substring(0, TMP.length - 1);
const diff = Math.round((dim.o - dim.w) / 2);
let page = 0;
let pageCount = 0;
let cmd = `find "${loc}" -type f -name "v2f_sequence_*.jpg"`;
console.log('Stitching frames into sheets...');
console.log(`Sheets will contain ${width}x${length} frames...`);
exec(cmd, (ste, std) => {
if (ste) {
return error(ste);
}
let jobs = [];
let cmds = [];
let frames = std.split('\n');
let execStr = 'montage ';
let pagePath = ``;
let i = 0;
frames = frames.filter((elem) => {
if (elem.indexOf('find: ') === -1) {
return elem;
}
});
frames.sort();
for (let frame of frames) {
execStr += `${frame} `;
if ((i + 1) % (width * length) === 0 || i === frames.length - 1) {
pagePath = path.join(output, `./page_${pad(page)}.jpg`);
execStr += `\ -tile 1x${length} -geometry ${dim.w}x${dim.h}+${diff}+0 miff:- |\ \nmontage - -geometry +0+0 -tile ${width}x1 -density ${dim.dpi} "${pagePath}"`;
cmds.push(execStr);
execStr = 'montage ';
page++;
}
i++;
}
jobs = cmds.map((cmd) => {
return (cb) => {
exec(cmd, (err, std, ste) => {
if (err) {
return error(err);
}
console.log(`Created page of ${width}x${length} frames!`);
cb();
});
};
});
async.series(jobs, next);
});
}
function cleanup (next) {
console.log('Cleaning up...');
exec(`rm -r "${TMP}"`, (err) => {
if (err) console.error(err)
if (next) next()
})
function cleanup(next) {
console.log('Cleaning up...');
exec(`rm -r "${TMP}"`, (err) => {
if (err)
console.error(err);
if (next)
next();
});
}
function pad (n) {
return ('00000' + n).slice(-5)
function pad(n) {
return ('00000' + n).slice(-5);
}
var error = function (err) {
if (process.argv.indexOf('-v') !== -1 || process.argv.indexOf('--verbose') !== -1){
console.error(err)
} else {
console.error('Error running program. Run in verbose mode for more info (-v,--verbose)')
}
process.exit(1)
}
if (process.argv.indexOf('-v') !== -1 || process.argv.indexOf('--verbose') !== -1) {
console.error(err);
}
else {
console.error('Error running program. Run in verbose mode for more info (-v,--verbose)');
}
process.exit(1);
};
process.on('uncaughtException', err => {
error(err)
})
error(err);
});
//convert(process.argv[2], process.argv[3])
//fix for nexe
let args = [].concat(process.argv)
let args = [].concat(process.argv);
if (args[1].indexOf('v2f.js') === -1) {
args.reverse()
args.push('node')
args.reverse()
args.reverse();
args.push('node');
args.reverse();
}
cmd.arguments('<input> <output>')
.version('1.1.0')
.option('-i, --input <path>', 'Video source to print to film strip, anything that avconv can read')
.option('-o, --output <path>', 'Output directory, will print images on A4 standard paper file')
.option('-d, --dpi <dpi>', 'DPI output pages')
.option('-f, --film <gauge>', 'Choose film gauge: 16mm, super16, 35mm')
.option('-v, --verbose', 'Run in verbose mode')
.parse(args)
initialize(cmd)
.version(pkg.version)
.option('-i, --input <path>', 'Video source to print to film strip, anything that avconv can read')
.option('-o, --output <path>', 'Output directory, will render images on specified page size')
.option('-d, --dpi <dpi>', 'DPI output pages')
.option('-f, --film <gauge>', 'Choose film gauge: 16mm, super16, 35mm')
.option('-w, --width <inches>', 'Output page width, in inches. Default 8.5')
.option('-l, --length <inches>', 'Output page length, in inches. Default 11')
.option('-e, --executable <binary>', 'Alternate binary to use in place of avconv, ie ffmpeg')
.option('-v, --verbose', 'Run in verbose mode')
.option('-n, --negative', 'Invert color channels to create negative')
.parse(args);
initialize(cmd);