Merge pull request #3 from sixteenmillimeter/kinolab

Kinolab updates
This commit is contained in:
Matt 2017-10-29 23:57:20 -04:00 committed by GitHub
commit ea69267ee4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 276 additions and 143 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
node_modules
tmp/*
temp/*
.nexe

View File

@ -3,7 +3,7 @@ v2f
Convert video to 16mm-sized strips of frames. For transferring to acetate and other experimental needs.
Look how easy it is to use:
Use the distributed binary
./v2f ./path_to_video.mov 300
@ -22,7 +22,7 @@ Releases
Dependencies
------------
- node.js (or use a compiled version)
- node.js (or use a [released version](https://github.com/sixteenmillimeter/v2f/releases/))
- libav
- ImageMagick
@ -37,11 +37,14 @@ Ubuntu
apt-get install libav imagemagick
Contribute
----------
- Issue Tracker: https://github.com/sixteenmillimeter/v2f/issues
- Source Code: https://github.com/sixteenmillimeter/v2f
- Home Page: https://sixteenmillimeter.com/projects/v2f
Support
-------

36
docs/Readme.md Normal file
View File

@ -0,0 +1,36 @@
## Functions
<dl>
<dt><a href="#convert">convert(path, dpi, length)</a></dt>
<dd><p>Turn video into sheet of images</p>
</dd>
<dt><a href="#stitch">stitch(loc, dim)</a></dt>
<dd><p>Stitch rendered frames into strips</p>
</dd>
</dl>
<a name="convert"></a>
## convert(path, dpi, length)
Turn video into sheet of images
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| path | <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 rendered frames into strips
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| loc | <code>String</code> | Path of folder containing frames |
| dim | <code>Object</code> | Dimensions object |

26
package-lock.json generated Normal file
View File

@ -0,0 +1,26 @@
{
"name": "v2f",
"version": "1.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
"integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==",
"requires": {
"lodash": "4.17.4"
}
},
"commander": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
},
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "v2f",
"version": "1.0.0",
"version": "1.1.0",
"description": "Turn a video into strips of precise 16mm-size stills",
"main": "v2f.js",
"scripts": {
@ -8,12 +8,16 @@
},
"author": "mmcwilliams",
"license": "MIT",
"nexe" : {
"output" : "../video_to_page_nexe/v2f",
"runtime": {
"ignoreFlags": true,
"framework" : "nodejs",
"version" : "5.0.0"
}
"nexe": {
"output": "../video_to_page_nexe/v2f",
"runtime": {
"ignoreFlags": true,
"framework": "nodejs",
"version": "8.7.0"
}
},
"dependencies": {
"async": "^2.5.0",
"commander": "^2.11.0"
}
}

328
v2f.js
View File

@ -1,152 +1,214 @@
var exec = require('child_process').exec,
fs = require('fs'),
_tmp = './temp';
/*jshint strict: true, esversion:6, node: true, asi: true*/
//var frame_height = 7.61;
//var frame_height = 7.49;
var frame_height = 7.62;
var frame_padding = 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')
var frame_dimensions = function (dpi) {
var h = Math.round(frame_height * (dpi / 25.4)),
w = Math.round(10.5 * (dpi / 25.4)),
o = Math.round(16 * (dpi / 25.4));
return {h: h, w: w, o: o, dpi: dpi};
};
const TMP = '/tmp/v2f/'
/*
convert() - Turn video into sheet of images
class Dimensions{
constructor (filmStr, dpi) {
const IN = dpi / 25.4
const film = this._gauge(filmStr)
@param: path - file path (absolute)
@param: dpi - target printing dpi
@param: length - strip length in frames
*/
var convert = function (path, dpi) {
'use strict';
var dim = frame_dimensions(dpi),
file = path.split('/').pop(),
loc = _tmp + '/',
execStr = 'avconv -i "' + path + '" -s ' + dim.w + 'x' + dim.h + ' -qscale 1 "' + loc + 'sequence_%04d.jpg"';
console.log('Converting ' + file + '...');
console.log('Exporting all frames with aspect ratio: ' + (dim.w / dim.h) + '...');
fs.mkdirSync(_tmp);
exec(execStr, function (ste, std) {
if (ste) {
return errorHandle(ste);
}
console.log('Frames exported successfully!');
stitch(loc.substring(0, loc.length - 1), dim);
});
};
var stitch = function (loc, dim) {
'use strict';
var length = Math.floor((11 * dim.dpi) / dim.h) - 1,
width = Math.floor((8.5 * dim.dpi / dim.o)) - 1,
page = 0,
pageCount = 0,
cmd = 'find "' + loc + '" -type f -name "sequence_*.jpg"',
find_cb = function (ste, std) {
if (ste) {
return errorHandle(ste);
}
var frames = std.split('\n'),
execStr = 'montage ',
montage_cb = function (stee, stdd) {
if (stee) {
return errorHandle(ste);
}
console.log('Created page_' + pageCount + '.jpg!');
pageCount++;
if (pageCount === page) {
console.log('Cleaning up...');
setTimeout(function () {
exec('find "' + loc + '" -type f -name "sequence_*.jpg" -delete', function () {
fs.rmdirSync(_tmp);
console.log('Done!');
});
}, 1000);
}
};
for (var i = 0; i < frames.length; i++) {
execStr += frames[i] + ' ';
if ((i + 1) % (width * length) === 0 || i === frames.length - 1) {
execStr += '\ -tile 1x' + length + ' -geometry ' + dim.w + 'x' + dim.h + '+' + Math.round((dim.o - dim.w) / 2) + '+0 miff:- |\ \nmontage - -geometry +0+0 -tile ' + width + 'x1 -density ' + dim.dpi + ' "./page_' + page + '.jpg"';
exec(execStr, montage_cb);
execStr = 'montage ';
page++;
}
}
};
loc = _tmp;
console.log('Stitching frames into sheets...');
console.log('Sheets will contain ' + width + 'x' + length + ' frames...');
exec(cmd, find_cb);
};
var errorHandle = function (err) {
'use strict';
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)');
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
}
process.exit(1);
};
process.on('uncaughtException', function (err) {
errorHandle(err);
});
if (typeof process.argv[2] === 'undefined') {
console.error('No path to video defined');
process.exit(1);
_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')
}
}
}
if (typeof process.argv[3] === 'undefined') {
process.argv[3] = 300;
console.log('Using default 300dpi');
/**
*
*
* @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`)
})
}
convert(process.argv[2], process.argv[3]);
/*
INSTALLATION AND RUNNING
/** *
* 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"`
//Install in this order to satisfy requirements: GCC required by MacPorts, etc.
console.log(`Converting ${file}...`)
console.log(`Exporting all frames with aspect ratio: ${dim.w / dim.h}...`)
//Install Node http://nodejs.org/dist/v0.10.22/node-v0.10.22.pkg
//Install GCC https://github.com/kennethreitz/osx-gcc-installer#option-1-downloading-pre-built-binaries
//Install MacPorts http://www.macports.org/install.php
//Install ImageMagick in terminal type "sudo port install ImageMagick"
//Install avconv -see below
if (!fs.existsSync(TMP)) fs.mkdirSync(TMP)
/*
Download and unzip http://libav.org/releases/libav-9.6.tar.gz
in terminal type "cd " and drag in unzipped folder and hit enter
in terminal copy "sudo port install yasm zlib bzip2 faac lame speex libogg libvorbis libtheora libvpx x264 XviD openjpeg15 opencore-amr freetype" (without quotes) hit enter
in terminal copy "./configure \ --enable-gpl --enable-libx264 --enable-libxvid \ --enable-version3 --enable-libopencore-amrnb --enable-libopencore-amrwb \ --enable-nonfree --enable-libfaac \ --enable-libmp3lame --enable-libspeex --enable-libvorbis --enable-libtheora --enable-libvpx \ --enable-libopenjpeg --enable-libfreetype --enable-doc --enable-gnutls --enable-shared" hit enter
(if that gives errors just use "./configure" and hit enter)
in terminal copy "make && sudo make install" hit enter
exec(execStr, (ste, std) => {
if (ste) {
return error(ste)
}
console.log('Frames exported successfully!')
next()
})
}
that will install it
/** *
* 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
*
*/
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"`
//run by going to terminal typing "node " dragging this script into the terminal, dragging the video into the terminal and adding a DPI value and hitting enter
//the command should look something like this:
//node /Users/stenzel/Desktop/video_to_page.js /Users/stenzel/Desktop/PaulRobeson/Paul\ Robeson\ discusses\ Othello.mp4 600
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
//Get the absolute path of any file by dragging it into terminal and copying the results
//Must be enclosed by ''
//Will generate pages and frames in same folder as source video
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}"`
console.log(execStr)
process.exit()
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 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)
}
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('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)