1cb93a386Sopenharmony_ci/** 2cb93a386Sopenharmony_ci * Command line application to build a 5x5 filmstrip from a Lottie file in the 3cb93a386Sopenharmony_ci * browser and then exporting that filmstrip in a 1000x1000 PNG. 4cb93a386Sopenharmony_ci * 5cb93a386Sopenharmony_ci */ 6cb93a386Sopenharmony_ciconst puppeteer = require('puppeteer'); 7cb93a386Sopenharmony_ciconst express = require('express'); 8cb93a386Sopenharmony_ciconst fs = require('fs'); 9cb93a386Sopenharmony_ciconst commandLineArgs = require('command-line-args'); 10cb93a386Sopenharmony_ciconst commandLineUsage= require('command-line-usage'); 11cb93a386Sopenharmony_ciconst fetch = require('node-fetch'); 12cb93a386Sopenharmony_ci 13cb93a386Sopenharmony_ci// Valid values for the --renderer flag. 14cb93a386Sopenharmony_ciconst RENDERERS = ['svg', 'canvas']; 15cb93a386Sopenharmony_ci 16cb93a386Sopenharmony_ciconst opts = [ 17cb93a386Sopenharmony_ci { 18cb93a386Sopenharmony_ci name: 'input', 19cb93a386Sopenharmony_ci typeLabel: '{underline file}', 20cb93a386Sopenharmony_ci description: 'The Lottie JSON file to process.' 21cb93a386Sopenharmony_ci }, 22cb93a386Sopenharmony_ci { 23cb93a386Sopenharmony_ci name: 'output', 24cb93a386Sopenharmony_ci typeLabel: '{underline file}', 25cb93a386Sopenharmony_ci description: 'The captured filmstrip PNG file to write. Defaults to filmstrip.png', 26cb93a386Sopenharmony_ci }, 27cb93a386Sopenharmony_ci { 28cb93a386Sopenharmony_ci name: 'renderer', 29cb93a386Sopenharmony_ci typeLabel: '{underline mode}', 30cb93a386Sopenharmony_ci description: 'Which renderer to use, "svg" or "canvas". Defaults to "svg".', 31cb93a386Sopenharmony_ci }, 32cb93a386Sopenharmony_ci { 33cb93a386Sopenharmony_ci name: 'port', 34cb93a386Sopenharmony_ci description: 'The port number to use, defaults to 8081.', 35cb93a386Sopenharmony_ci type: Number, 36cb93a386Sopenharmony_ci }, 37cb93a386Sopenharmony_ci { 38cb93a386Sopenharmony_ci name: 'lottie_player', 39cb93a386Sopenharmony_ci description: 'The path to lottie.min.js, defaults to a local npm install location.', 40cb93a386Sopenharmony_ci type: String, 41cb93a386Sopenharmony_ci }, 42cb93a386Sopenharmony_ci { 43cb93a386Sopenharmony_ci name: 'post_to', 44cb93a386Sopenharmony_ci description: 'If set, the url to post results to for Gold Ingestion.', 45cb93a386Sopenharmony_ci type: String, 46cb93a386Sopenharmony_ci }, 47cb93a386Sopenharmony_ci { 48cb93a386Sopenharmony_ci name: 'in_docker', 49cb93a386Sopenharmony_ci description: 'Is this being run in docker, defaults to false', 50cb93a386Sopenharmony_ci type: Boolean, 51cb93a386Sopenharmony_ci }, 52cb93a386Sopenharmony_ci { 53cb93a386Sopenharmony_ci name: 'skip_automation', 54cb93a386Sopenharmony_ci description: 'If the automation of the screenshot taking should be skipped ' + 55cb93a386Sopenharmony_ci '(e.g. debugging). Defaults to false.', 56cb93a386Sopenharmony_ci type: Boolean, 57cb93a386Sopenharmony_ci }, 58cb93a386Sopenharmony_ci { 59cb93a386Sopenharmony_ci name: 'help', 60cb93a386Sopenharmony_ci alias: 'h', 61cb93a386Sopenharmony_ci type: Boolean, 62cb93a386Sopenharmony_ci description: 'Print this usage guide.' 63cb93a386Sopenharmony_ci }, 64cb93a386Sopenharmony_ci]; 65cb93a386Sopenharmony_ci 66cb93a386Sopenharmony_ciconst usage = [ 67cb93a386Sopenharmony_ci { 68cb93a386Sopenharmony_ci header: 'Lottie Filmstrip Capture', 69cb93a386Sopenharmony_ci content: `Command line application to build a 5x5 filmstrip 70cb93a386Sopenharmony_cifrom a Lottie file in the browser and then export 71cb93a386Sopenharmony_cithat filmstrip in a 1000x1000 PNG.` 72cb93a386Sopenharmony_ci }, 73cb93a386Sopenharmony_ci { 74cb93a386Sopenharmony_ci header: 'Options', 75cb93a386Sopenharmony_ci optionList: opts, 76cb93a386Sopenharmony_ci }, 77cb93a386Sopenharmony_ci]; 78cb93a386Sopenharmony_ci 79cb93a386Sopenharmony_ci// Parse and validate flags. 80cb93a386Sopenharmony_ciconst options = commandLineArgs(opts); 81cb93a386Sopenharmony_ci 82cb93a386Sopenharmony_ciif (!options.output) { 83cb93a386Sopenharmony_ci options.output = 'filmstrip.png'; 84cb93a386Sopenharmony_ci} 85cb93a386Sopenharmony_ciif (!options.port) { 86cb93a386Sopenharmony_ci options.port = 8081; 87cb93a386Sopenharmony_ci} 88cb93a386Sopenharmony_ciif (!options.lottie_player) { 89cb93a386Sopenharmony_ci options.lottie_player = 'node_modules/lottie-web/build/player/lottie.min.js'; 90cb93a386Sopenharmony_ci} 91cb93a386Sopenharmony_ci 92cb93a386Sopenharmony_ciif (options.help) { 93cb93a386Sopenharmony_ci console.log(commandLineUsage(usage)); 94cb93a386Sopenharmony_ci process.exit(0); 95cb93a386Sopenharmony_ci} 96cb93a386Sopenharmony_ci 97cb93a386Sopenharmony_ciif (!options.input) { 98cb93a386Sopenharmony_ci console.error('You must supply a Lottie JSON filename.'); 99cb93a386Sopenharmony_ci console.log(commandLineUsage(usage)); 100cb93a386Sopenharmony_ci process.exit(1); 101cb93a386Sopenharmony_ci} 102cb93a386Sopenharmony_ci 103cb93a386Sopenharmony_ciif (!options.renderer) { 104cb93a386Sopenharmony_ci options.renderer = 'svg'; 105cb93a386Sopenharmony_ci} 106cb93a386Sopenharmony_ci 107cb93a386Sopenharmony_ciif (!RENDERERS.includes(options.renderer)) { 108cb93a386Sopenharmony_ci console.error('The --renderer flag must have as a value one of: ', RENDERERS); 109cb93a386Sopenharmony_ci console.log(commandLineUsage(usage)); 110cb93a386Sopenharmony_ci process.exit(1); 111cb93a386Sopenharmony_ci} 112cb93a386Sopenharmony_ci 113cb93a386Sopenharmony_ci// Start up a web server to serve the three files we need. 114cb93a386Sopenharmony_cilet lottieJS = fs.readFileSync(options.lottie_player, 'utf8'); 115cb93a386Sopenharmony_cilet driverHTML = fs.readFileSync('driver.html', 'utf8'); 116cb93a386Sopenharmony_cilet lottieJSON = fs.readFileSync(options.input, 'utf8'); 117cb93a386Sopenharmony_ci 118cb93a386Sopenharmony_ciconst app = express(); 119cb93a386Sopenharmony_ciapp.get('/', (req, res) => res.send(driverHTML)); 120cb93a386Sopenharmony_ciapp.get('/lottie.js', (req, res) => res.send(lottieJS)); 121cb93a386Sopenharmony_ciapp.get('/lottie.json', (req, res) => res.send(lottieJSON)); 122cb93a386Sopenharmony_ciapp.listen(options.port, () => console.log('- Local web server started.')) 123cb93a386Sopenharmony_ci 124cb93a386Sopenharmony_ci// Utiltity function. 125cb93a386Sopenharmony_ciasync function wait(ms) { 126cb93a386Sopenharmony_ci await new Promise(resolve => setTimeout(() => resolve(), ms)); 127cb93a386Sopenharmony_ci return ms; 128cb93a386Sopenharmony_ci} 129cb93a386Sopenharmony_ci 130cb93a386Sopenharmony_ciconst targetURL = `http://localhost:${options.port}/#${options.renderer}`; 131cb93a386Sopenharmony_ci 132cb93a386Sopenharmony_ci// Drive chrome to load the web page from the server we have running. 133cb93a386Sopenharmony_ciasync function driveBrowser() { 134cb93a386Sopenharmony_ci console.log('- Launching chrome in headless mode.'); 135cb93a386Sopenharmony_ci let browser = null; 136cb93a386Sopenharmony_ci if (options.in_docker) { 137cb93a386Sopenharmony_ci browser = await puppeteer.launch({ 138cb93a386Sopenharmony_ci 'executablePath': '/usr/bin/google-chrome-stable', 139cb93a386Sopenharmony_ci 'args': ['--no-sandbox'], 140cb93a386Sopenharmony_ci }); 141cb93a386Sopenharmony_ci } else { 142cb93a386Sopenharmony_ci browser = await puppeteer.launch(); 143cb93a386Sopenharmony_ci } 144cb93a386Sopenharmony_ci 145cb93a386Sopenharmony_ci const page = await browser.newPage(); 146cb93a386Sopenharmony_ci console.log(`- Loading our Lottie exercising page for ${options.input}.`); 147cb93a386Sopenharmony_ci try { 148cb93a386Sopenharmony_ci // 20 seconds is plenty of time to wait for the json to be loaded once 149cb93a386Sopenharmony_ci // This usually times out for super large json. 150cb93a386Sopenharmony_ci await page.goto(targetURL, { 151cb93a386Sopenharmony_ci timeout: 20000, 152cb93a386Sopenharmony_ci waitUntil: 'networkidle0' 153cb93a386Sopenharmony_ci }); 154cb93a386Sopenharmony_ci // 20 seconds is plenty of time to wait for the frames to be drawn. 155cb93a386Sopenharmony_ci // This usually times out for json that causes errors in the player. 156cb93a386Sopenharmony_ci console.log('- Waiting 15s for all the tiles to be drawn.'); 157cb93a386Sopenharmony_ci await page.waitForFunction('window._tileCount === 25', { 158cb93a386Sopenharmony_ci timeout: 20000, 159cb93a386Sopenharmony_ci }); 160cb93a386Sopenharmony_ci } catch(e) { 161cb93a386Sopenharmony_ci console.log('Timed out while loading or drawing. Either the JSON file was ' + 162cb93a386Sopenharmony_ci 'too big or hit a bug in the player.', e); 163cb93a386Sopenharmony_ci await browser.close(); 164cb93a386Sopenharmony_ci process.exit(0); 165cb93a386Sopenharmony_ci } 166cb93a386Sopenharmony_ci 167cb93a386Sopenharmony_ci console.log('- Taking screenshot.'); 168cb93a386Sopenharmony_ci let encoding = 'binary'; 169cb93a386Sopenharmony_ci if (options.post_to) { 170cb93a386Sopenharmony_ci encoding = 'base64'; 171cb93a386Sopenharmony_ci // prevent writing the image to disk 172cb93a386Sopenharmony_ci options.output = ''; 173cb93a386Sopenharmony_ci } 174cb93a386Sopenharmony_ci 175cb93a386Sopenharmony_ci // See https://github.com/GoogleChrome/puppeteer/blob/v1.6.0/docs/api.md#pagescreenshotoptions 176cb93a386Sopenharmony_ci let result = await page.screenshot({ 177cb93a386Sopenharmony_ci path: options.output, 178cb93a386Sopenharmony_ci type: 'png', 179cb93a386Sopenharmony_ci clip: { 180cb93a386Sopenharmony_ci x: 0, 181cb93a386Sopenharmony_ci y: 0, 182cb93a386Sopenharmony_ci width: 1000, 183cb93a386Sopenharmony_ci height: 1000, 184cb93a386Sopenharmony_ci }, 185cb93a386Sopenharmony_ci encoding: encoding, 186cb93a386Sopenharmony_ci }); 187cb93a386Sopenharmony_ci 188cb93a386Sopenharmony_ci if (options.post_to) { 189cb93a386Sopenharmony_ci console.log(`- Reporting ${options.input} to Gold server ${options.post_to}`); 190cb93a386Sopenharmony_ci let shortenedName = options.input; 191cb93a386Sopenharmony_ci let lastSlash = shortenedName.lastIndexOf('/'); 192cb93a386Sopenharmony_ci if (lastSlash !== -1) { 193cb93a386Sopenharmony_ci shortenedName = shortenedName.slice(lastSlash+1); 194cb93a386Sopenharmony_ci } 195cb93a386Sopenharmony_ci await fetch(options.post_to, { 196cb93a386Sopenharmony_ci method: 'POST', 197cb93a386Sopenharmony_ci mode: 'no-cors', 198cb93a386Sopenharmony_ci headers: { 199cb93a386Sopenharmony_ci 'Content-Type': 'application/json', 200cb93a386Sopenharmony_ci }, 201cb93a386Sopenharmony_ci body: JSON.stringify({ 202cb93a386Sopenharmony_ci 'data': result, 203cb93a386Sopenharmony_ci 'test_name': shortenedName, 204cb93a386Sopenharmony_ci }) 205cb93a386Sopenharmony_ci }); 206cb93a386Sopenharmony_ci } 207cb93a386Sopenharmony_ci 208cb93a386Sopenharmony_ci await browser.close(); 209cb93a386Sopenharmony_ci // Need to call exit() because the web server is still running. 210cb93a386Sopenharmony_ci process.exit(0); 211cb93a386Sopenharmony_ci} 212cb93a386Sopenharmony_ci 213cb93a386Sopenharmony_ciif (!options.skip_automation) { 214cb93a386Sopenharmony_ci driveBrowser(); 215cb93a386Sopenharmony_ci} else { 216cb93a386Sopenharmony_ci console.log(`open ${targetURL} to see the animation.`) 217cb93a386Sopenharmony_ci} 218cb93a386Sopenharmony_ci 219