Last active
September 1, 2016 12:54
-
-
Save kadamwhite/effcb6f3de78924be6a98f93f72d80da to your computer and use it in GitHub Desktop.
Take a directory of images output by https://github.com/jcjohnson/neural-style and convert them to a mp4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
RESOURCES | |
ffmpeg and libvpx with Homebrew https://gist.github.com/clayton/6196167 | |
ffmpeg docs http://ffmpeg.org/ffmpeg.html | |
http://robotics.usc.edu/~ampereir/wordpress/?p=702 | |
Compat https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats | |
https://en.support.wordpress.com/accepted-filetypes/ | |
http://easywpguide.com/wordpress-manual/adding-images-other-media/inserting-video-audio-or-other-file-type/ | |
"Supported A/V formats include M4a, MP4, OGG, WebM, FLV, WMV, MP3, WAV and WMA files." | |
*/ | |
'use strict'; | |
var spawn = require( 'child_process' ).spawn; | |
var exec = require( 'child_process' ).exec; | |
var fs = require( 'fs' ); | |
/** | |
* Get the list of files in a directory, either as a list of file and subdir | |
* names or a list of absolute file system paths | |
* | |
* @private | |
* @param {string} inputDir The file system path to the directory to read | |
* @returns {Promise} A promise to the string array of file names | |
*/ | |
const ls = ( inputDir, absolute ) => { | |
return new Promise( ( resolve, reject ) => { | |
fs.readdir( inputDir, ( err, list ) => { | |
if ( err ) { | |
return reject( err ); | |
} | |
resolve( list ); | |
}); | |
}); | |
}; | |
/** | |
* Execute a shell command and return a promise that will resolve or exit | |
* when that command completes | |
* | |
* @param {string} command A shell command string e.g. "mv file1 file2" | |
* @param {boolean} quiet Whether to suppress outputting the command to be run | |
* @returns {Promise} A promise that completes when the command finishes | |
*/ | |
const execCommand = ( command, quiet ) => { | |
return new Promise( ( resolve, reject ) => { | |
!quiet && console.log( command ); | |
exec( command, ( error, stdout, stderr ) => { | |
if ( error ) { | |
return reject( error ); | |
} | |
resolve(); | |
}); | |
}); | |
} | |
const execRegardless = command => { | |
return execCommand( command ).catch( err => console.log( err ) ); | |
} | |
/** | |
* Helper function that takes in an array of functions that return promises, | |
* then executes those functions sequentially to execute each action | |
* | |
* @param {function[]} arrOfFnsReturningPromises An array of functions | |
* @returns {Promise} A promise that will resolve once all the promises | |
* returned by that function successfully complete | |
*/ | |
const runInSequence = arrOfFnsReturningPromises => { | |
return arrOfFnsReturningPromises.reduce( | |
( lastStep, startNextStep ) => lastStep.then( startNextStep ), | |
Promise.resolve() | |
); | |
}; | |
const pad = ( num ) => { | |
let numStr = `${num}`; | |
while (numStr.length < 4) { | |
numStr = `0${numStr}`; | |
} | |
return numStr; | |
} | |
// How long should this jazz be? | |
// 1 repetition is a full forward-back mirrored sequence | |
const repetitions = 2; | |
const filename = process.argv[ 2 ] || 'output'; | |
// const delay = process.argv[ 3 ] || 10; | |
// const framerate = Math.ceil( delay / 60 ); | |
const framerate = process.argv[ 3 ] || 10; | |
// const imagickArgs = `-quiet -delay ${delay} -compress None -quality 100`; | |
// const convertFramesToMP4 = `convert ${imagickArgs} ./frames/*.png ${filename}.mp4`; | |
const convertFramesToMP4 = `ffmpeg -framerate 10 -i ./frames/frame%04d.png -c:v libx264 -r 30 -pix_fmt yuv420p ${filename}.mp4 -y`; | |
const convertMP4toWebM = `ffmpeg -i ${filename}.mp4 -c:v libvpx -b:v 1M -c:a libvorbis ${filename}.webm -y`; | |
// Forget the past | |
execRegardless( 'rm -rf ./frames' ) | |
// imagine the future | |
.then( () => execRegardless( 'mkdir ./frames' ) ) | |
// Get directory listing | |
.then( () => ls( __dirname ) ) | |
// Skip non-png's | |
.then( files => files.filter( file => /png/.test( file ) ) ) | |
// Assemble the list of images into a repeating sequence | |
.then( files => { | |
const first = files.find( file => /100/.test( file ) ); | |
const last = files.find( file => /^[^\d]+$/.test( file ) ); | |
const seq = files.filter( file => file !== first && file !== last ); | |
// First frame (x2 for pseudo-easing) | |
const frames = [ first, first ] | |
// Forward tween pass | |
.concat( seq ) | |
// Last frame (x2 for pseudo-easing) | |
.concat( [ last, last ] ) | |
// Backwards tween pass | |
.concat( [].concat( seq ).reverse() ) | |
return frames; | |
}) | |
.then( files => { | |
console.log( 'Copying frames to temporary folder...' ); | |
const copyCommands = files | |
.map( ( file, idx ) => { | |
return `cp ${file} ./frames/frame${pad(idx)}.png` | |
}) | |
// Convert the command strings to command invoker functions | |
.map( command => () => execCommand( command, true ) ); | |
// return copyCommands; | |
return runInSequence( copyCommands ) | |
.then( () => files ) | |
}) | |
// Convert frames to mp4 | |
.then( () => execCommand( convertFramesToMP4 ) ) | |
// And WebM | |
.then( () => execCommand( convertMP4toWebM ) ) | |
// Clean up temp files | |
.then( () => execRegardless( 'rm -rf ./frames' ) ) | |
// Done! | |
.then( () => console.log( 'Done!' ) ) | |
// Error handling | |
.catch( err => console.error( err ) ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment