Compare commits
10 Commits
4120e19291
...
76d1feddf0
Author | SHA1 | Date |
---|---|---|
|
76d1feddf0 | |
|
b1b460d21a | |
|
122a0aa404 | |
|
81543f3583 | |
|
23c8b9f91b | |
|
cb9dfe3f00 | |
|
18424141e0 | |
|
741132933b | |
|
8545858f29 | |
|
0001f73329 |
|
@ -2,3 +2,5 @@ node_modules
|
||||||
tmp/*
|
tmp/*
|
||||||
temp/*
|
temp/*
|
||||||
.nexe
|
.nexe
|
||||||
|
dist
|
||||||
|
.DS_Store
|
|
@ -24,16 +24,21 @@ Options
|
||||||
|
|
||||||
-V, --version output the version number
|
-V, --version output the version number
|
||||||
-i, --input <path> Video source to print to film strip, anything that avconv can read
|
-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
|
-o, --output <path> Output directory, will render images on specified page size
|
||||||
-d, --dpi <dpi> DPI output pages
|
-d, --dpi <dpi> DPI output pages
|
||||||
-f, --film <gauge> Choose film gauge: 16mm, super16, 35mm
|
-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
|
-v, --verbose Run in verbose mode
|
||||||
|
-n, --negative Invert color channels to create negative
|
||||||
-h, --help output usage information
|
-h, --help output usage information
|
||||||
```
|
```
|
||||||
|
|
||||||
Releases
|
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.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)
|
* [1.0.0](https://github.com/sixteenmillimeter/v2f/releases/tag/1.0.0)
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -1,36 +1,50 @@
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt><a href="#convert">convert(path, dpi, length)</a></dt>
|
<dt><a href="#initialize">initialize(Commander)</a></dt>
|
||||||
<dd><p>Turn video into sheet of images</p>
|
<dd></dd>
|
||||||
|
<dt><a href="#convert">convert(input, dpi, length)</a></dt>
|
||||||
|
<dd><p>Create image sequence from source video, using</p>
|
||||||
</dd>
|
</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><p>Stitch rendered frames into strips</p>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
|
<a name="initialize"></a>
|
||||||
|
|
||||||
|
## initialize(Commander)
|
||||||
|
**Kind**: global function
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Commander | <code>Object</code> | object |
|
||||||
|
|
||||||
<a name="convert"></a>
|
<a name="convert"></a>
|
||||||
|
|
||||||
## convert(path, dpi, length)
|
## convert(input, dpi, length)
|
||||||
Turn video into sheet of images
|
Create image sequence from source video, using
|
||||||
|
|
||||||
**Kind**: global function
|
**Kind**: global function
|
||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| path | <code>String</code> | file path (absolute) |
|
| input | <code>String</code> | file path (absolute) |
|
||||||
| dpi | <code>Integer</code> | target printing dpi |
|
| dpi | <code>Integer</code> | target printing dpi |
|
||||||
| length | <code>Integer</code> | strip length in frames |
|
| length | <code>Integer</code> | strip length in frames |
|
||||||
|
|
||||||
<a name="stitch"></a>
|
<a name="stitch"></a>
|
||||||
|
|
||||||
## stitch(loc, dim)
|
## stitch(output, dim, next, pageW, pageL)
|
||||||
Stitch rendered frames into strips
|
Stitch rendered frames into strips
|
||||||
|
|
||||||
**Kind**: global function
|
**Kind**: global function
|
||||||
|
|
||||||
| Param | Type | Description |
|
| 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 |
|
| 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 |
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
|
@ -1,23 +1,32 @@
|
||||||
{
|
{
|
||||||
"name": "v2f",
|
"name": "v2f",
|
||||||
"version": "1.1.0",
|
"version": "1.2.1",
|
||||||
"description": "Turn a video into strips of precise 16mm-size stills",
|
"description": "Turn a video into strips of precise 16mm-size stills",
|
||||||
"main": "v2f.js",
|
"main": "v2f.js",
|
||||||
"scripts": {
|
"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",
|
"author": "mmcwilliams",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"nexe": {
|
|
||||||
"output": "../video_to_page_nexe/v2f",
|
|
||||||
"runtime": {
|
|
||||||
"ignoreFlags": true,
|
|
||||||
"framework": "nodejs",
|
|
||||||
"version": "8.7.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^2.5.0",
|
"async": "^2.6.2",
|
||||||
"commander": "^2.11.0"
|
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mkdir -p dist
|
||||||
|
./node_modules/.bin/nexe ./v2f.js --build --output ./dist/v2f
|
|
@ -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)
|
|
@ -1,51 +1,50 @@
|
||||||
/*jshint strict: true, esversion:6, node: true, asi: true*/
|
'use strict';
|
||||||
|
const cmd = require('commander');
|
||||||
'use strict'
|
const async = require('async');
|
||||||
const cmd = require('commander')
|
const exec = require('child_process').exec;
|
||||||
const async = require('async')
|
const fs = require('fs');
|
||||||
const exec = require('child_process').exec
|
const path = require('path');
|
||||||
const fs = require('fs')
|
const os = require('os');
|
||||||
const path = require('path')
|
const osTmp = os.tmpdir();
|
||||||
|
const TMP = path.join(osTmp, '/v2f/');
|
||||||
const TMP = '/tmp/v2f/'
|
const pkg = require('./package.json');
|
||||||
|
|
||||||
class Dimensions {
|
class Dimensions {
|
||||||
constructor(filmStr, dpi) {
|
constructor(filmStr, dpi) {
|
||||||
const IN = dpi / 25.4
|
const IN = dpi / 25.4;
|
||||||
const film = this._gauge(filmStr)
|
const film = this._gauge(filmStr);
|
||||||
|
this.h = Math.round(film.h * IN); //frame height
|
||||||
this.h = Math.round(film.h * IN) //frame height
|
this.w = Math.round(film.w * IN); //frame width
|
||||||
this.w = Math.round(film.w * IN) //frame width
|
this.o = Math.round(film.o * IN); //space between columns
|
||||||
this.o = Math.round(film.o * IN) //space between columns
|
this.dpi = dpi;
|
||||||
this.dpi = dpi
|
this.film = film;
|
||||||
this.film = film
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_gauge(film) {
|
_gauge(film) {
|
||||||
if (film === '16mm') {
|
if (film === '16mm') {
|
||||||
return {
|
return {
|
||||||
h: 7.62,
|
h: 7.62,
|
||||||
w: 10.5,
|
w: 10.5,
|
||||||
o: 16
|
o: 16
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else if (film === 'super16'){
|
else if (film === 'super16') {
|
||||||
return {
|
return {
|
||||||
h: 7.62,
|
h: 7.62,
|
||||||
w: 12.75,
|
w: 12.75,
|
||||||
o: 16
|
o: 16
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else if (film === '35mm') {
|
else if (film === '35mm') {
|
||||||
return {
|
return {
|
||||||
h: 19.05,
|
h: 19.05,
|
||||||
w: 22,
|
w: 22,
|
||||||
o: 35
|
o: 35
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
else {
|
||||||
error('Film type not found, see --help for more info')
|
error('Film type not found, see --help for more info');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
|
@ -53,27 +52,29 @@ class Dimensions{
|
||||||
* @param {Object} Commander object
|
* @param {Object} Commander object
|
||||||
*/
|
*/
|
||||||
function initialize(command) {
|
function initialize(command) {
|
||||||
const dpi = command.dpi || 300
|
const dpi = command.dpi || 300;
|
||||||
const film = command.film || '16mm'
|
const film = command.film || '16mm';
|
||||||
const input = command.input || command.args[0] || error('No input file, see --help for more info')
|
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 output = command.output || command.args[1] || error('No ouput directory, see --help for more info');
|
||||||
const dim = new Dimensions(film, dpi)
|
const dim = new Dimensions(film, dpi);
|
||||||
|
const pageW = command.width || 8.5;
|
||||||
if (!fs.existsSync(input)) error(`Video "${input}" cannot be found`)
|
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([
|
async.series([
|
||||||
next => {
|
(next) => {
|
||||||
convert(input, dim, next)
|
convert(exe, input, dim, negative, next);
|
||||||
},
|
},
|
||||||
next => {
|
(next) => {
|
||||||
stitch(output, dim, next)
|
stitch(output, dim, next, pageW, pageL);
|
||||||
},
|
},
|
||||||
cleanup
|
cleanup
|
||||||
], () => {
|
], () => {
|
||||||
console.log(`Finished creating pages`)
|
console.log(`Finished creating pages`);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** *
|
/** *
|
||||||
* Create image sequence from source video, using
|
* Create image sequence from source video, using
|
||||||
*
|
*
|
||||||
|
@ -83,24 +84,22 @@ function initialize (command) {
|
||||||
* @param {Integer} length strip length in frames
|
* @param {Integer} length strip length in frames
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function convert (input, dim, next) {
|
function convert(exe, input, dim, negative = false, next) {
|
||||||
const file = input.split('/').pop()
|
const file = input.split('/').pop();
|
||||||
const execStr = `avconv -i "${input}" -s ${dim.w}x${dim.h} -qscale 1 "${TMP}v2f_sequence_%04d.jpg"`
|
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(`Converting ${file}...`);
|
||||||
console.log(`Exporting all frames with aspect ratio: ${dim.w / dim.h}...`)
|
console.log(`Exporting all frames with aspect ratio: ${dim.w / dim.h}...`);
|
||||||
|
if (!fs.existsSync(TMP))
|
||||||
if (!fs.existsSync(TMP)) fs.mkdirSync(TMP)
|
fs.mkdirSync(TMP);
|
||||||
|
|
||||||
exec(execStr, (ste, std) => {
|
exec(execStr, (ste, std) => {
|
||||||
if (ste) {
|
if (ste) {
|
||||||
return error(ste)
|
return error(ste);
|
||||||
}
|
}
|
||||||
console.log('Frames exported successfully!')
|
console.log('Frames exported successfully!');
|
||||||
next()
|
next();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** *
|
/** *
|
||||||
* Stitch rendered frames into strips
|
* Stitch rendered frames into strips
|
||||||
|
|
||||||
|
@ -108,105 +107,103 @@ function convert (input, dim, next) {
|
||||||
* @param {String} output Path of folder containing frames
|
* @param {String} output Path of folder containing frames
|
||||||
* @param {Object} dim Dimensions object
|
* @param {Object} dim Dimensions object
|
||||||
* @param {Function} next Async lib callback function
|
* @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) {
|
function stitch(output, dim, next, pageW, pageL) {
|
||||||
const length = Math.floor((11 * dim.dpi) / dim.h) - 1
|
const length = Math.floor((pageL * dim.dpi) / dim.h) - 1;
|
||||||
const width = Math.floor((8.5 * dim.dpi / dim.o)) - 1
|
const width = Math.floor((pageW * dim.dpi / dim.o)) - 1;
|
||||||
const loc = TMP.substring(0, TMP.length - 1)
|
const loc = TMP.substring(0, TMP.length - 1);
|
||||||
const diff = Math.round((dim.o - dim.w) / 2)
|
const diff = Math.round((dim.o - dim.w) / 2);
|
||||||
let page = 0
|
let page = 0;
|
||||||
let pageCount = 0
|
let pageCount = 0;
|
||||||
let cmd = `find "${loc}" -type f -name "v2f_sequence_*.jpg"`
|
let cmd = `find "${loc}" -type f -name "v2f_sequence_*.jpg"`;
|
||||||
|
console.log('Stitching frames into sheets...');
|
||||||
console.log('Stitching frames into sheets...')
|
console.log(`Sheets will contain ${width}x${length} frames...`);
|
||||||
console.log(`Sheets will contain ${width}x${length} frames...`)
|
|
||||||
|
|
||||||
exec(cmd, (ste, std) => {
|
exec(cmd, (ste, std) => {
|
||||||
if (ste) {
|
if (ste) {
|
||||||
return error(ste)
|
return error(ste);
|
||||||
}
|
}
|
||||||
let jobs = []
|
let jobs = [];
|
||||||
let cmds = []
|
let cmds = [];
|
||||||
let frames = std.split('\n')
|
let frames = std.split('\n');
|
||||||
let execStr = 'montage '
|
let execStr = 'montage ';
|
||||||
let pagePath = ``
|
let pagePath = ``;
|
||||||
let i = 0
|
let i = 0;
|
||||||
|
frames = frames.filter((elem) => {
|
||||||
frames = frames.filter(elem => {
|
|
||||||
if (elem.indexOf('find: ') === -1) {
|
if (elem.indexOf('find: ') === -1) {
|
||||||
return elem
|
return elem;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
frames.sort()
|
frames.sort();
|
||||||
for (let frame of frames) {
|
for (let frame of frames) {
|
||||||
execStr += `${frame} `
|
execStr += `${frame} `;
|
||||||
if ((i + 1) % (width * length) === 0 || i === frames.length - 1) {
|
if ((i + 1) % (width * length) === 0 || i === frames.length - 1) {
|
||||||
pagePath = path.join(output, `./page_${pad(page)}.jpg`)
|
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}"`
|
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)
|
cmds.push(execStr);
|
||||||
execStr = 'montage '
|
execStr = 'montage ';
|
||||||
page++
|
page++;
|
||||||
}
|
}
|
||||||
i++
|
i++;
|
||||||
}
|
}
|
||||||
jobs = cmds.map(cmd => {
|
jobs = cmds.map((cmd) => {
|
||||||
return cb => {
|
return (cb) => {
|
||||||
exec(cmd, (err, std, ste) => {
|
exec(cmd, (err, std, ste) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return error(err)
|
return error(err);
|
||||||
}
|
}
|
||||||
console.log(`Created page of ${width}x${length} frames!`)
|
console.log(`Created page of ${width}x${length} frames!`);
|
||||||
cb()
|
cb();
|
||||||
})
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
async.series(jobs, next);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
|
||||||
async.series(jobs, next)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanup(next) {
|
function cleanup(next) {
|
||||||
console.log('Cleaning up...');
|
console.log('Cleaning up...');
|
||||||
exec(`rm -r "${TMP}"`, (err) => {
|
exec(`rm -r "${TMP}"`, (err) => {
|
||||||
if (err) console.error(err)
|
if (err)
|
||||||
if (next) next()
|
console.error(err);
|
||||||
})
|
if (next)
|
||||||
|
next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function pad(n) {
|
function pad(n) {
|
||||||
return ('00000' + n).slice(-5)
|
return ('00000' + n).slice(-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
var error = function (err) {
|
var error = function (err) {
|
||||||
if (process.argv.indexOf('-v') !== -1 || process.argv.indexOf('--verbose') !== -1) {
|
if (process.argv.indexOf('-v') !== -1 || process.argv.indexOf('--verbose') !== -1) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
} else {
|
|
||||||
console.error('Error running program. Run in verbose mode for more info (-v,--verbose)')
|
|
||||||
}
|
}
|
||||||
process.exit(1)
|
else {
|
||||||
|
console.error('Error running program. Run in verbose mode for more info (-v,--verbose)');
|
||||||
}
|
}
|
||||||
|
process.exit(1);
|
||||||
|
};
|
||||||
process.on('uncaughtException', err => {
|
process.on('uncaughtException', err => {
|
||||||
error(err)
|
error(err);
|
||||||
})
|
});
|
||||||
|
|
||||||
//convert(process.argv[2], process.argv[3])
|
//convert(process.argv[2], process.argv[3])
|
||||||
|
|
||||||
//fix for nexe
|
//fix for nexe
|
||||||
let args = [].concat(process.argv)
|
let args = [].concat(process.argv);
|
||||||
if (args[1].indexOf('v2f.js') === -1) {
|
if (args[1].indexOf('v2f.js') === -1) {
|
||||||
args.reverse()
|
args.reverse();
|
||||||
args.push('node')
|
args.push('node');
|
||||||
args.reverse()
|
args.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.arguments('<input> <output>')
|
cmd.arguments('<input> <output>')
|
||||||
.version('1.1.0')
|
.version(pkg.version)
|
||||||
.option('-i, --input <path>', 'Video source to print to film strip, anything that avconv can read')
|
.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('-o, --output <path>', 'Output directory, will render images on specified page size')
|
||||||
.option('-d, --dpi <dpi>', 'DPI output pages')
|
.option('-d, --dpi <dpi>', 'DPI output pages')
|
||||||
.option('-f, --film <gauge>', 'Choose film gauge: 16mm, super16, 35mm')
|
.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('-v, --verbose', 'Run in verbose mode')
|
||||||
.parse(args)
|
.option('-n, --negative', 'Invert color channels to create negative')
|
||||||
|
.parse(args);
|
||||||
initialize(cmd)
|
initialize(cmd);
|
||||||
|
|
Loading…
Reference in New Issue