Compare commits
No commits in common. "76d1feddf0c4d0d72a3b28f3d2b53ce926cbdb77" and "4120e19291a0b55af74a8c04cc8fc2c63eefbe7f" have entirely different histories.
76d1feddf0
...
4120e19291
|
@ -1,6 +1,4 @@
|
||||||
node_modules
|
node_modules
|
||||||
tmp/*
|
tmp/*
|
||||||
temp/*
|
temp/*
|
||||||
.nexe
|
.nexe
|
||||||
dist
|
|
||||||
.DS_Store
|
|
19
Readme.md
19
Readme.md
|
@ -22,23 +22,18 @@ Options
|
||||||
|
|
||||||
Options:
|
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 render images on specified page size
|
-o, --output <path> Output directory, will print images on A4 standard paper file
|
||||||
-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
|
-v, --verbose Run in verbose mode
|
||||||
-l, --length <inches> Output page length, in inches. Default 11
|
-h, --help output usage information
|
||||||
-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
|
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)
|
||||||
|
|
||||||
|
|
51
build.js
51
build.js
|
@ -1,51 +0,0 @@
|
||||||
'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,50 +1,36 @@
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt><a href="#initialize">initialize(Commander)</a></dt>
|
<dt><a href="#convert">convert(path, dpi, length)</a></dt>
|
||||||
<dd></dd>
|
<dd><p>Turn video into sheet of images</p>
|
||||||
<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(output, dim, next, pageW, pageL)</a></dt>
|
<dt><a href="#stitch">stitch(loc, dim)</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(input, dpi, length)
|
## convert(path, dpi, length)
|
||||||
Create image sequence from source video, using
|
Turn video into sheet of images
|
||||||
|
|
||||||
**Kind**: global function
|
**Kind**: global function
|
||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| input | <code>String</code> | file path (absolute) |
|
| path | <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(output, dim, next, pageW, pageL)
|
## stitch(loc, dim)
|
||||||
Stitch rendered frames into strips
|
Stitch rendered frames into strips
|
||||||
|
|
||||||
**Kind**: global function
|
**Kind**: global function
|
||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| output | <code>String</code> | Path of folder containing frames |
|
| loc | <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,32 +1,23 @@
|
||||||
{
|
{
|
||||||
"name": "v2f",
|
"name": "v2f",
|
||||||
"version": "1.2.1",
|
"version": "1.1.0",
|
||||||
"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.6.2",
|
"async": "^2.5.0",
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
mkdir -p dist
|
|
||||||
./node_modules/.bin/nexe ./v2f.js --build --output ./dist/v2f
|
|
231
src/v2f.ts
231
src/v2f.ts
|
@ -1,231 +0,0 @@
|
||||||
'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,105 +1,106 @@
|
||||||
'use strict';
|
/*jshint strict: true, esversion:6, node: true, asi: true*/
|
||||||
const cmd = require('commander');
|
|
||||||
const async = require('async');
|
'use strict'
|
||||||
const exec = require('child_process').exec;
|
const cmd = require('commander')
|
||||||
const fs = require('fs');
|
const async = require('async')
|
||||||
const path = require('path');
|
const exec = require('child_process').exec
|
||||||
const os = require('os');
|
const fs = require('fs')
|
||||||
const osTmp = os.tmpdir();
|
const path = require('path')
|
||||||
const TMP = path.join(osTmp, '/v2f/');
|
|
||||||
const pkg = require('./package.json');
|
const TMP = '/tmp/v2f/'
|
||||||
class Dimensions {
|
|
||||||
constructor(filmStr, dpi) {
|
class Dimensions{
|
||||||
const IN = dpi / 25.4;
|
constructor (filmStr, dpi) {
|
||||||
const film = this._gauge(filmStr);
|
const IN = dpi / 25.4
|
||||||
this.h = Math.round(film.h * IN); //frame height
|
const film = this._gauge(filmStr)
|
||||||
this.w = Math.round(film.w * IN); //frame width
|
|
||||||
this.o = Math.round(film.o * IN); //space between columns
|
this.h = Math.round(film.h * IN) //frame height
|
||||||
this.dpi = dpi;
|
this.w = Math.round(film.w * IN) //frame width
|
||||||
this.film = film;
|
this.o = Math.round(film.o * IN) //space between columns
|
||||||
}
|
this.dpi = dpi
|
||||||
_gauge(film) {
|
this.film = film
|
||||||
if (film === '16mm') {
|
}
|
||||||
return {
|
|
||||||
h: 7.62,
|
_gauge (film) {
|
||||||
w: 10.5,
|
if (film === '16mm') {
|
||||||
o: 16
|
return {
|
||||||
};
|
h: 7.62,
|
||||||
}
|
w : 10.5,
|
||||||
else if (film === 'super16') {
|
o : 16
|
||||||
return {
|
}
|
||||||
h: 7.62,
|
} else if (film === 'super16'){
|
||||||
w: 12.75,
|
return {
|
||||||
o: 16
|
h: 7.62,
|
||||||
};
|
w : 12.75,
|
||||||
}
|
o : 16
|
||||||
else if (film === '35mm') {
|
}
|
||||||
return {
|
} else if (film === '35mm') {
|
||||||
h: 19.05,
|
return {
|
||||||
w: 22,
|
h : 19.05,
|
||||||
o: 35
|
w : 22,
|
||||||
};
|
o : 35
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
error('Film type not found, see --help for more info');
|
error('Film type not found, see --help for more info')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @function
|
* @function
|
||||||
* @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;
|
|
||||||
const pageL = command.length || 11;
|
if (!fs.existsSync(input)) error(`Video "${input}" cannot be found`)
|
||||||
const exe = command.executable || 'avconv';
|
|
||||||
const negative = typeof command.negative !== 'undefined' ? true : false;
|
async.series([
|
||||||
if (!fs.existsSync(input))
|
next => {
|
||||||
error(`Video "${input}" cannot be found`);
|
convert(input, dim, next)
|
||||||
async.series([
|
},
|
||||||
(next) => {
|
next => {
|
||||||
convert(exe, input, dim, negative, next);
|
stitch(output, dim, next)
|
||||||
},
|
},
|
||||||
(next) => {
|
cleanup
|
||||||
stitch(output, dim, next, pageW, pageL);
|
], () => {
|
||||||
},
|
console.log(`Finished creating pages`)
|
||||||
cleanup
|
})
|
||||||
], () => {
|
|
||||||
console.log(`Finished creating pages`);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** *
|
/** *
|
||||||
* Create image sequence from source video, using
|
* Create image sequence from source video, using
|
||||||
*
|
*
|
||||||
* @function
|
* @function
|
||||||
* @param {String} input file path (absolute)
|
* @param {String} input file path (absolute)
|
||||||
* @param {Integer} dpi target printing dpi
|
* @param {Integer} dpi target printing dpi
|
||||||
* @param {Integer} length strip length in frames
|
* @param {Integer} length strip length in frames
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function convert(exe, input, dim, negative = false, next) {
|
function convert (input, dim, next) {
|
||||||
const file = input.split('/').pop();
|
const file = input.split('/').pop()
|
||||||
const negStr = negative ? `-vf lutrgb="r=negval:g=negval:b=negval"` : '';
|
const execStr = `avconv -i "${input}" -s ${dim.w}x${dim.h} -qscale 1 "${TMP}v2f_sequence_%04d.jpg"`
|
||||||
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))
|
|
||||||
fs.mkdirSync(TMP);
|
if (!fs.existsSync(TMP)) fs.mkdirSync(TMP)
|
||||||
exec(execStr, (ste, std) => {
|
|
||||||
if (ste) {
|
exec(execStr, (ste, std) => {
|
||||||
return error(ste);
|
if (ste) {
|
||||||
}
|
return error(ste)
|
||||||
console.log('Frames exported successfully!');
|
}
|
||||||
next();
|
console.log('Frames exported successfully!')
|
||||||
});
|
next()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** *
|
/** *
|
||||||
* Stitch rendered frames into strips
|
* Stitch rendered frames into strips
|
||||||
|
|
||||||
|
@ -107,103 +108,105 @@ function convert(exe, input, dim, negative = false, 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, pageW, pageL) {
|
function stitch (output, dim, next) {
|
||||||
const length = Math.floor((pageL * dim.dpi) / dim.h) - 1;
|
const length = Math.floor((11 * dim.dpi) / dim.h) - 1
|
||||||
const width = Math.floor((pageW * dim.dpi / dim.o)) - 1;
|
const width = Math.floor((8.5 * 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(`Sheets will contain ${width}x${length} frames...`);
|
console.log('Stitching frames into sheets...')
|
||||||
exec(cmd, (ste, std) => {
|
console.log(`Sheets will contain ${width}x${length} frames...`)
|
||||||
if (ste) {
|
|
||||||
return error(ste);
|
exec(cmd, (ste, std) => {
|
||||||
}
|
if (ste) {
|
||||||
let jobs = [];
|
return error(ste)
|
||||||
let cmds = [];
|
}
|
||||||
let frames = std.split('\n');
|
let jobs = []
|
||||||
let execStr = 'montage ';
|
let cmds = []
|
||||||
let pagePath = ``;
|
let frames = std.split('\n')
|
||||||
let i = 0;
|
let execStr = 'montage '
|
||||||
frames = frames.filter((elem) => {
|
let pagePath = ``
|
||||||
if (elem.indexOf('find: ') === -1) {
|
let i = 0
|
||||||
return elem;
|
|
||||||
}
|
frames = frames.filter(elem => {
|
||||||
});
|
if (elem.indexOf('find: ') === -1) {
|
||||||
frames.sort();
|
return elem
|
||||||
for (let frame of frames) {
|
}
|
||||||
execStr += `${frame} `;
|
})
|
||||||
if ((i + 1) % (width * length) === 0 || i === frames.length - 1) {
|
frames.sort()
|
||||||
pagePath = path.join(output, `./page_${pad(page)}.jpg`);
|
for (let frame of frames) {
|
||||||
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 += `${frame} `
|
||||||
cmds.push(execStr);
|
if ((i + 1) % (width * length) === 0 || i === frames.length - 1) {
|
||||||
execStr = 'montage ';
|
pagePath = path.join(output, `./page_${pad(page)}.jpg`)
|
||||||
page++;
|
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)
|
||||||
i++;
|
execStr = 'montage '
|
||||||
}
|
page++
|
||||||
jobs = cmds.map((cmd) => {
|
}
|
||||||
return (cb) => {
|
i++
|
||||||
exec(cmd, (err, std, ste) => {
|
}
|
||||||
if (err) {
|
jobs = cmds.map(cmd => {
|
||||||
return error(err);
|
return cb => {
|
||||||
}
|
exec(cmd, (err, std, ste) => {
|
||||||
console.log(`Created page of ${width}x${length} frames!`);
|
if (err) {
|
||||||
cb();
|
return error(err)
|
||||||
});
|
}
|
||||||
};
|
console.log(`Created page of ${width}x${length} frames!`)
|
||||||
});
|
cb()
|
||||||
async.series(jobs, next);
|
})
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
async.series(jobs, next)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
function cleanup(next) {
|
|
||||||
console.log('Cleaning up...');
|
function cleanup (next) {
|
||||||
exec(`rm -r "${TMP}"`, (err) => {
|
console.log('Cleaning up...');
|
||||||
if (err)
|
exec(`rm -r "${TMP}"`, (err) => {
|
||||||
console.error(err);
|
if (err) console.error(err)
|
||||||
if (next)
|
if (next) next()
|
||||||
next();
|
})
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function pad(n) {
|
|
||||||
return ('00000' + n).slice(-5);
|
function pad (n) {
|
||||||
|
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 {
|
||||||
else {
|
console.error('Error running program. Run in verbose mode for more info (-v,--verbose)')
|
||||||
console.error('Error running program. Run in verbose mode for more info (-v,--verbose)');
|
}
|
||||||
}
|
process.exit(1)
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>')
|
cmd.arguments('<input> <output>')
|
||||||
.version(pkg.version)
|
.version('1.1.0')
|
||||||
.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 render images on specified page size')
|
.option('-o, --output <path>', 'Output directory, will print images on A4 standard paper file')
|
||||||
.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('-v, --verbose', 'Run in verbose mode')
|
||||||
.option('-l, --length <inches>', 'Output page length, in inches. Default 11')
|
.parse(args)
|
||||||
.option('-e, --executable <binary>', 'Alternate binary to use in place of avconv, ie ffmpeg')
|
|
||||||
.option('-v, --verbose', 'Run in verbose mode')
|
initialize(cmd)
|
||||||
.option('-n, --negative', 'Invert color channels to create negative')
|
|
||||||
.parse(args);
|
|
||||||
initialize(cmd);
|
|
||||||
|
|
Loading…
Reference in New Issue