Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

15 changed files with 2402 additions and 5354 deletions

5
.gitignore vendored
View File

@ -1,6 +1,3 @@
node_modules
examples
dist
*.AppleDouble
*.Parent
*.DS_Store
dist

View File

@ -1,13 +1,9 @@
# frameloom
![Image illustrating two 5 frame sequences stitched together with frameloom](./img/frameloom.jpg)
Node script to generate flicker videos by interweaving frames from multiple videos
--------
## Git URL [git.sixteenmillimeter.com/16mm/frameloom](https://git.sixteenmillimeter.com/16mm/frameloom)
## Requirements
This script relies on `ffmpeg` to export and stitch video back together
@ -25,15 +21,13 @@ chmod +x frameloom
## Basic Usage
```bash
./frameloom -i /path/to/video1:/path/to/video2 -o /path/to/output
```
```./frameloom -i /path/to/video1:/path/to/video2 -o /path/to/output```
## Options
Run `./frameloom -h` to display help screen.
```bash
```
Usage: frameloom [options]
Options:
@ -45,19 +39,6 @@ Options:
-t, --tmp [dir] Specify tmp directory for exporting frames
-a, --avconv Specify avconv if preferred to ffmpeg
-R, --random Randomize frames. Ignores pattern if included
-s, --spin Randomly rotate frames before rendering
-e, --exec Command to execute on every frame. Specify {{i}} and {{o}} if the command requires
it, otherwise frame path will be appended to command
-q, --quiet Suppresses all log messages
-h, --help display help for command
-h, --help output usage information
```
## License
Copyright 2018-2021 M McWilliams
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -34,7 +34,7 @@ if (!fs.existsSync(`./dist/${platform}_${arch}`)) {
console.log(`Building frameloom and saving in dist/${platform}_${arch}...`)
console.time('frameloom')
exec([ 'frameloom', '--target', 'node10', '--output', `./dist/${platform}_${arch}/frameloom` ]).then(async (res) => {
exec([ 'frameloom', '--target', 'host', '--output', `./dist/${platform}_${arch}/frameloom` ]).then(async (res) => {
try {
await shell_out(`zip -r ./dist/frameloom_${platform}_${arch}_${packageJson.version}.zip ./dist/${platform}_${arch}/frameloom`)
console.log(`Compressed binary to dist/frameloom_${platform}_${arch}_${packageJson.version}.zip`)

View File

@ -8,10 +8,6 @@
<dt><a href="#delay">delay(ms)</a><code>Promise</code></dt>
<dd><p>Delays process for specified amount of time in milliseconds.</p>
</dd>
<dt><a href="#log">log()</a></dt>
<dd><p>Log function wrapper that can silences logs when
QUIET == true</p>
</dd>
<dt><a href="#zeroPad">zeroPad(i, max)</a><code>string</code></dt>
<dd><p>Pads a numerical value with preceding zeros to make strings same length.</p>
</dd>
@ -19,18 +15,15 @@ QUIET == true</p>
<dd><p>Shuffles an array into a random state.</p>
</dd>
<dt><a href="#clear">clear()</a></dt>
<dd><p>Clears the temporary directory of all files.
<dd><p>Clears the temporary directory of all files.
Establishes a directory if none exists.</p>
</dd>
<dt><a href="#frames">frames(video, order, avconv)</a><code>string</code></dt>
<dt><a href="#frames">frames(video, order)</a><code>string</code></dt>
<dd><p>Exports all frames from video. Appends number to the string
to keep frames in alternating order to be quickly stitched together
or re-sorted.</p>
</dd>
<dt><a href="#subExec">subExec(cmd)</a></dt>
<dd><p>Shells out to run a sub command on every frame to perform effects</p>
</dd>
<dt><a href="#weave">weave(pattern, realtime, random)</a></dt>
<dt><a href="#weave">weave(pattern, realtime)</a></dt>
<dd><p>Re-arranges the frames into the order specified in the pattern.
Calls <code>patternSort()</code> to perform the rename and unlink actions</p>
</dd>
@ -43,7 +36,7 @@ QUIET == true</p>
<dt><a href="#randomSort">randomSort(list, pattern, realtime)</a></dt>
<dd><p>Ramdomly sort frames for re-stitching.</p>
</dd>
<dt><a href="#render">render(output, avconv)</a></dt>
<dt><a href="#render">render(output)</a></dt>
<dd><p>Render the frames into a video using ffmpeg.</p>
</dd>
<dt><a href="#main">main(arg)</a></dt>
@ -77,13 +70,6 @@ Delays process for specified amount of time in milliseconds.
| --- | --- | --- |
| ms | <code>integer</code> | Milliseconds to delay for |
<a name="log"></a>
## log()
Log function wrapper that can silences logs when
QUIET == true
**Kind**: global function
<a name="zeroPad"></a>
## zeroPad(i, max) ⇒ <code>string</code>
@ -111,13 +97,13 @@ Shuffles an array into a random state.
<a name="clear"></a>
## clear()
Clears the temporary directory of all files.
Clears the temporary directory of all files.
Establishes a directory if none exists.
**Kind**: global function
<a name="frames"></a>
## frames(video, order, avconv) ⇒ <code>string</code>
## frames(video, order) ⇒ <code>string</code>
Exports all frames from video. Appends number to the string
to keep frames in alternating order to be quickly stitched together
or re-sorted.
@ -129,22 +115,10 @@ Exports all frames from video. Appends number to the string
| --- | --- | --- |
| video | <code>string</code> | String representing path to video |
| order | <code>integer</code> | Integer to be appended to pathname of file |
| avconv | <code>boolean</code> | Whether or not to use avconv instead of ffmpeg |
<a name="subExec"></a>
## subExec(cmd)
Shells out to run a sub command on every frame to perform effects
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| cmd | <code>string</code> | Command to execute on every frame |
<a name="weave"></a>
## weave(pattern, realtime, random)
## weave(pattern, realtime)
Re-arranges the frames into the order specified in the pattern.
Calls `patternSort()` to perform the rename and unlink actions
@ -154,7 +128,6 @@ Re-arranges the frames into the order specified in the pattern.
| --- | --- | --- |
| pattern | <code>array</code> | Pattern of the frames per input |
| realtime | <code>boolean</code> | Flag to turn on or off realtime behavior (drop frames / number of vids) |
| random | <code>boolean</code> | Whether or not to randomize frames |
<a name="altSort"></a>
@ -197,7 +170,7 @@ Ramdomly sort frames for re-stitching.
<a name="render"></a>
## render(output, avconv)
## render(output)
Render the frames into a video using ffmpeg.
**Kind**: global function
@ -205,7 +178,6 @@ Render the frames into a video using ffmpeg.
| Param | Type | Description |
| --- | --- | --- |
| output | <code>string</code> | Path to export the video to |
| avconv | <code>boolean</code> | Whether or not to use avconv in place of ffmpeg |
<a name="main"></a>

955
frameloom Normal file → Executable file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

5981
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,20 @@
{
"name": "frameloom",
"version": "1.0.3",
"version": "1.0.0",
"description": "Node script to generate flicker videos by interweaving frames from multiple videos",
"main": "frameloom",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"version": "npm --no-git-tag-version version patch",
"compile": "./node_modules/.bin/tsc src/frameloom.ts --outFile ./frameloom --noImplicitAny --lib ES2017 -t ES2017 --moduleResolution Node",
"build": "node build.js",
"docs": "sh ./scripts/docs.sh",
"examples": "sh ./scripts/examples.sh",
"examples:youtube": "sh ./scripts/examples_youtube.sh",
"examples:assemble": "sh ./scripts/examples_assemble.sh"
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "sixteenmillimeter",
"license": "MIT",
"dependencies": {
"commander": "^7.2.0",
"fs-extra": "^9.1.0"
"commander": "^2.19.0",
"fs-extra": "^7.0.1"
},
"devDependencies": {
"@types/node": "^14.14.36",
"jsdoc-to-markdown": "^7.0.1",
"pkg": "^4.5.1",
"qunit": "^2.14.1",
"typescript": "^4.2.3"
},
"pkg": {
"scripts": [
"./frameloom",
"./lib/**/*"
]
"jsdoc-to-markdown": "^4.0.1",
"pkg": "^4.3.5",
"qunit": "^2.8.0"
}
}

View File

@ -1,13 +0,0 @@
#!/bin/bash
TMP_CROP=$(mktemp)
TMP_GIF=$(mktemp)
TMP_PALETTE=$(mktemp)
echo "Generating square gif of ${1} as ${2}x${2}"
ffmpeg -y -i "$1" -c:v prores_ks -profile:v 3 -filter:v "scale=1920:1080:force_original_aspect_ratio=decrease,crop=1080:1080:420:0" "$TMP_CROP.mov"
ffmpeg -y -i "$TMP_CROP.mov" -c:v prores_ks -profile:v 3 -vf scale=$2:$2 "$TMP_GIF.mov"
ffmpeg -y -i "$TMP_GIF.mov" -vf palettegen "$TMP_PALETTE.png"
ffmpeg -y -i "$TMP_GIF.mov" -i "$TMP_PALETTE.png" -filter_complex paletteuse -f gif "square_${2}.gif"
echo "Generated square_${2}.gif"

View File

@ -1,700 +0,0 @@
#!/usr/bin/env node
'use strict'
const execRaw = require('child_process').exec
const { tmpdir } = require('os')
const { join, extname } = require('path')
const program = require('commander')
const { move, exists, unlink, readdir, mkdir } = require('fs-extra')
const { version } = require('./package.json')
const OUTPUT_RE : RegExp = new RegExp('{{o}}', 'g')
const INPUT_RE : RegExp = new RegExp('{{i}}', 'g')
let QUIET : boolean = false
let TMPDIR : string = tmpdir() || '/tmp'
let TMPPATH : string
/**
* 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 exec (cmd : string) : Promise<string> {
return new Promise((resolve : any, reject : any) => {
return execRaw(cmd, { maxBuffer : 500 * 1024 * 1024}, (err : any, stdio : string, stderr : string) => {
if (err) return reject(err)
return resolve(stdio)
})
})
}
/**
* Delays process for specified amount of time in milliseconds.
*
* @param {integer} ms Milliseconds to delay for
*
* @returns {Promise} Promise that resolves after set time
**/
async function delay (ms : number) : Promise<any> {
return new Promise((resolve : any, reject : any) =>{
return setTimeout(resolve, ms)
})
}
/**
* Log function wrapper that can silences logs when
* QUIET == true
*/
function log (msg : string, err : any = false) : boolean {
if (QUIET) return false
if (err) {
console.error(msg, err)
} else {
console.log(msg)
}
return true
}
/**
* Pads a numerical value with preceding zeros to make strings same length.
*
* @param {integer} i Number to pad
* @param {integer} max (optional) Maximum length of string to pad to
*
* @returns {string} Padded number as a string
**/
function zeroPad (i : number, max : number = 5) {
let str : string = i + ''
let len : number = str.length
for (let x : number = 0; x < max - len; x++) {
str = '0' + str
}
return str
}
/**
* Shuffles an array into a random state.
*
* @param {array} a Array to randomize
**/
function shuffle (array : any[]) {
let j : any
let temp : any
for (let i : number = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1))
temp = array[i]
array[i] = array[j]
array[j] = temp
}
}
function randomInt (min : number, max : number) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Clears the temporary directory of all files.
* Establishes a directory if none exists.
**/
async function clear () {
let cmd : string = `rm -r "${TMPPATH}"`
let dirExists : boolean
try {
dirExists = await exists(TMPPATH)
} catch (err) {
log('Error checking if file exists', err)
}
if (dirExists) {
log(`Clearing temp directory "${TMPPATH}"`)
try {
await exec(cmd)
} catch (err) {
//suppress error
console.dir(err)
}
}
try {
await mkdir(TMPPATH)
} catch (err) {
if (err.code !== 'EEXIST') {
log('Error making directory', err)
}
}
return true
}
/**
* Exports all frames from video. Appends number to the string
* to keep frames in alternating order to be quickly stitched together
* or re-sorted.
*
* @param {string} video String representing path to video
* @param {integer} order Integer to be appended to pathname of file
* @param {boolean} avconv Whether or not to use avconv instead of ffmpeg
*
* @returns {string} String with the export order, not sure why I did this
**/
async function frames (video : string, order : number, avconv : boolean) : Promise<string> {
let ext : string = 'tif'
let exe : string = avconv ? 'avconv' : 'ffmpeg'
let tmpoutput : string
let cmd : string
tmpoutput = join(TMPPATH, `export-%05d_${order}.${ext}`)
cmd = `${exe} -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"`
log(`Exporting ${video} as single frames...`)
try {
await exec(cmd)
} catch (err) {
log('Error exporting video', err)
return process.exit(3)
}
return join(TMPPATH, `export-%05d_${order}`)
}
/**
* Shells out to run a sub command on every frame to perform effects
*
* @param {string} cmd Command to execute on every frame
*
**/
async function subExec (cmd : string) {
let frames : string[]
let frameCmd : string
let framePath : string
try {
frames = await readdir(TMPPATH)
} catch (err) {
log('Error reading tmp directory', err)
}
frames = frames.filter (file =>{
if (file.indexOf('.tif') !== -1) return true
})
for (let frame of frames) {
framePath = join(TMPPATH, frame)
if (cmd.indexOf('{{i}}') !== -1 || cmd.indexOf('{{o}}')) {
frameCmd = cmd.replace(INPUT_RE, framePath)
.replace(OUTPUT_RE, framePath)
} else {
frameCmd = `${cmd} ${framePath}`
}
try {
await exec(frameCmd)
} catch (err) {
log('Error executing sub command on frame', err)
return process.exit(10)
}
}
}
/**
* Re-arranges the frames into the order specified in the pattern.
* Calls `patternSort()` to perform the rename and unlink actions
*
* @param {array} pattern Pattern of the frames per input
* @param {boolean} realtime Flag to turn on or off realtime behavior (drop frames / number of vids)
* @param {boolean} random Whether or not to randomize frames
**/
async function weave (pattern : number[], realtime : boolean, random : boolean) {
let frames : string[]
let seq : string[]
let alt : boolean = false
log('Weaving frames...')
try {
frames = await readdir(TMPPATH)
} catch (err) {
log('Error reading tmp directory', err)
}
//console.dir(frames)
frames = frames.filter (file =>{
if (file.indexOf('.tif') !== -1) return true
})
for (let el of pattern) {
if (el !== 1) alt = true
}
if (random){
log('Sorting frames randomly...')
try {
seq = await randomSort(frames, pattern, realtime)
} catch (err) {
log('Error sorting frames', err)
}
} else if (!alt) {
log('Sorting frames normally...')
try {
seq = await standardSort(frames, pattern, realtime)
} catch (err) {
log('Error sorting frames', err)
}
} else if (alt) {
//log('This feature is not ready, please check https://github.com/sixteenmillimeter/frameloom.git', {})
//process.exit(10)
log('Sorting frames with alternate pattern...')
try {
seq = await altSort(frames, pattern, realtime)
} catch (err) {
log('Error sorting frames', err)
}
}
//console.dir(seq)
}
/**
* Alternate frame sorting method.
*
* @param {array} list List of frames to group
* @param {array} pattern Array representing pattern
* @param {boolean} realtime Flag to group with "realtime" behavior
**/
async function altSort (list : string[], pattern : number[], realtime : boolean) {
let groups : any[] = []
let newList : string[] = []
let loops : number = 0
let patternIndexes : number[] = []
let frameCount : number = 0
let skipCount : number
let skip : boolean
let oldName : string
let oldPath : string
let newName : string
let newPath : string
let ext : string = extname(list[0])
let x : number
let i : number
for (x = 0; x < pattern.length; x++) {
groups.push([])
for (let i : number = 0; i < pattern[x]; i++) {
patternIndexes.push(x)
}
}
for (i = 0; i < list.length; i++) {
groups[i % pattern.length].push(list[i])
}
loops = Math.ceil(list.length / patternIndexes.length)
if (realtime) {
skip = false
skipCount = patternIndexes.length + 1
}
for (x = 0; x < loops; x++) {
for (i = 0; i < patternIndexes.length; i++) {
if (realtime) {
skipCount--;
if (skipCount === 0) {
skip = !skip;
skipCount = pattern.length
}
}
if (typeof groups[patternIndexes[i]][0] === 'undefined') {
continue
}
oldName = String(groups[patternIndexes[i]][0])
oldPath = join(TMPPATH, oldName)
groups[patternIndexes[i]].shift()
if (skip) {
log(`Skipping ${oldName}`)
try {
await unlink(oldPath)
} catch (err) {
log('Error deleting frame', err)
}
continue
}
newName = `./render_${zeroPad(frameCount)}${ext}`
newPath = join(TMPPATH, newName)
log(`Renaming ${oldName} -> ${newName}`)
try {
await move(oldPath, newPath)
newList.push(newName)
frameCount++
} catch (err) {
log('Error renaming frame', err)
return process.exit(10)
}
}
}
return newList
}
/**
* Standard frame sorting method.
*
* @param {array} list List of frames to group
* @param {array} pattern Array representing pattern
* @param {boolean} realtime Flag to group with "realtime" behavior
**/
async function standardSort (list : string[], pattern : number[], realtime : boolean) {
let frameCount : number = 0
let stepCount : number
let step : any
let skipCount : number
let skip : boolean
let ext : string = extname(list[0])
let oldPath : string
let newName : string
let newPath : string
let newList : string[] = []
if (realtime) {
skip = false
skipCount = pattern.length + 1
}
for (let i : number = 0; i < list.length; i++) {
if (realtime) {
skipCount--;
if (skipCount === 0) {
skip = !skip;
skipCount = pattern.length
}
}
oldPath = join(TMPPATH, list[i])
if (skip) {
log(`Skipping ${list[i]}`)
try {
await unlink(oldPath)
} catch (err) {
log('Error deleting frame', err)
}
continue
}
newName = `./render_${zeroPad(frameCount)}${ext}`
newPath = join(TMPPATH, newName)
log(`Renaming ${list[i]} -> ${newName}`)
try {
await move(oldPath, newPath)
newList.push(newName)
frameCount++
} catch (err) {
log('Error renaming frame', err)
return process.exit(10)
}
}
return newList
}
/**
* Ramdomly sort frames for re-stitching.
*
* @param {array} list List of frames to group
* @param {array} pattern Array representing pattern
* @param {boolean} realtime Flag to group with "realtime" behavior
**/
async function randomSort (list : string[], pattern : number[], realtime : boolean) {
let frameCount : number = 0
let ext : string = extname(list[0])
let oldPath : string
let newName : string
let newPath : string
let newList : string[] = []
let removeLen : number = 0
let remove : string[] = []
shuffle(list)
if (realtime) {
removeLen = Math.floor(list.length / pattern.length)
remove = list.slice(removeLen, list.length)
list = list.slice(0, removeLen)
log(`Skipping extra frames...`)
for (let i : number = 0; i < remove.length; i++) {
oldPath = join(TMPPATH, remove[i])
log(`Skipping ${list[i]}`)
try {
await unlink(oldPath)
} catch (err) {
log('Error deleting frame', err)
}
}
}
for (let i : number = 0; i < list.length; i++) {
oldPath = join(TMPPATH, list[i])
newName = `./render_${zeroPad(frameCount)}${ext}`
newPath = join(TMPPATH, newName)
log(`Renaming ${list[i]} -> ${newName}`)
try {
await move(oldPath, newPath)
newList.push(newName)
} catch (err) {
log('Error moving frame', err)
}
frameCount++
}
return newList
}
async function spinFrames () {
let frames : string[]
let framePath : string
let cmd : string
let flip : string
let flop : string
let rotate : string
console.log('Spinning frames...')
try {
frames = await readdir(TMPPATH)
} catch (err) {
console.error('Error reading tmp directory', err)
}
//console.dir(frames)
frames = frames.filter (file =>{
if (file.indexOf('.tif') !== -1) return true
})
for (let frame of frames) {
framePath = join(TMPPATH, frame)
rotate = ''
flip = ''
flop = ''
if (randomInt(0, 1) === 1) {
rotate = '-rotate 180 '
}
if (randomInt(0, 1) === 1) {
flip = '-flip '
}
if (randomInt(0, 1) === 1) {
flop = '-flop '
}
if (flip === '' && flop === '' && rotate === '') {
//skip unrotated, unflipped and unflopped frames
continue
}
cmd = `convert ${framePath} ${rotate}${flip}${flop} ${framePath}`
console.log(cmd)
try {
await exec(cmd)
} catch (err) {
console.error(err)
process.exit(10)
}
}
}
/**
* Render the frames into a video using ffmpeg.
*
* @param {string} output Path to export the video to
* @param {boolean} avconv Whether or not to use avconv in place of ffmpeg
**/
async function render (output : string, avconv : boolean) {
//process.exit()
let frames : string = join(TMPPATH, `render_%05d.tif`)
let exe : string = avconv ? 'avconv' : 'ffmpeg'
let resolution : string = '1920x1080' //TODO: make variable/argument
//TODO: make object configurable with shorthand names
let h264 : string = `-vcodec libx264 -g 1 -crf 25 -pix_fmt yuv420p`
let prores : string = `-c:v prores_ks -profile:v 3`
//
let format : string = (output.indexOf('.mov') !== -1) ? prores : h264
let framerate : string = `24`
const cmd : string = `${exe} -r ${framerate} -f image2 -s ${resolution} -i ${frames} ${format} -y ${output}`
log(`Exporting video ${output}`)
log(cmd)
try {
await exec(cmd)
} catch (err) {
log('Error rendering video with ffmpeg', err)
}
}
/**
* Parses the arguments and runs the process of exporting, sorting and then
* "weaving" the frames back into a video
*
* @param {object} arg Object containing all arguments
**/
async function main (program : any) {
const arg = program.opts();
let input : string[] = arg.input.split(':')
let output : string = arg.output
let pattern : any[] = []
let realtime : boolean = false
let avconv : boolean = false
let random : boolean = false
let e : any = false
let exe : string = arg.avconv ? 'avconv' : 'ffmpeg'
let fileExists : any
console.time('frameloom')
if (input.length < 2) {
log('Must provide more than 1 input', {})
return process.exit(1)
}
if (!output) {
log('Must provide video output path', {})
return process.exit(2)
}
if (arg.random) {
random = true
}
if (arg.avconv) {
avconv = true
}
if (arg.tmp) {
TMPDIR = arg.tmp
}
if (arg.exec) {
e = arg.exec
}
if (arg.quiet) {
QUIET = true
}
if (arg.pattern) {
pattern = arg.pattern.split(':')
pattern = pattern.map(el =>{
return parseInt(el);
})
} else {
for (let i = 0; i <input.length; i++) {
pattern.push(1);
}
}
try {
fileExists = await exec(`which ${exe}`)
} catch (err) {
log(`Error checking for ${exe}`)
process.exit(11)
}
if (!fileExists || fileExists === '' || fileExists.indexOf(exe) === -1) {
log(`${exe} is required and is not installed. Please install ${exe} to use frameloom.`)
process.exit(12)
}
if (pattern.length !== input.length) {
log(`Number of inputs (${input.length}) doesn't match the pattern length (${pattern.length})`)
process.exit(10)
}
if (arg.realtime) realtime = true;
TMPPATH = join(TMPDIR, 'frameloom');
try {
await clear()
} catch (err) {
log('Error clearing temp directory', err)
return process.exit(3)
}
log(`Processing video files ${input.join(', ')} into ${output} with pattern ${pattern.join(':')}`)
for (let i = 0; i < input.length; i++) {
try {
await frames(input[i], i, avconv)
} catch (err) {
log('Error exporting video fie to image sequence', err)
return process.exit(4)
}
}
try {
await weave(pattern, realtime, random)
} catch (err) {
log('Error weaving', err)
return process.exit(5)
}
if (arg.spin) {
try {
await spinFrames()
} catch (err) {
log('Error spinning', err)
return process.exit(13)
}
}
if (e) {
try {
await subExec(e)
} catch (err) {
log('Error performing subcommand', err)
return process.exit(7)
}
}
try {
await render(output, avconv)
} catch (err) {
log('Error rendering', err)
return process.exit(6)
}
try {
await clear()
} catch (err) {
log('Error clearing files', err)
return process.exit(7)
}
console.timeEnd('frameloom')
}
program
.version(version)
.option('-i, --input [files]', 'Specify input videos with paths seperated by colon')
.option('-o, --output [file]', 'Specify output path of video')
.option('-p, --pattern [pattern]', 'Specify a pattern for the flicker 1:1 is standard')
.option('-r, --realtime', 'Specify if videos should preserve realtime speed')
.option('-t, --tmp [dir]', 'Specify tmp directory for exporting frames')
.option('-a, --avconv', 'Specify avconv if preferred to ffmpeg')
.option('-R, --random', 'Randomize frames. Ignores pattern if included')
.option('-s, --spin', 'Randomly rotate frames before rendering')
.option('-e, --exec', 'Command to execute on every frame. Specify {{i}} and {{o}} if the command requires it, otherwise frame path will be appended to command')
.option('-q, --quiet', 'Suppresses all log messages')
.parse(process.argv)
main(program)