├── .gitignore ├── LICENSE ├── bin ├── puWorker.js └── scriptimate.js ├── examples ├── 1_helloworld.smte ├── 2_rotate_scale_opacity.smte ├── 3_parallel_animations.gif ├── 3_parallel_animations.smte ├── 4_constants_and_expressions.smte ├── 5_easing.smte ├── 6_groups.gif ├── 6_groups.smte ├── 7_dashoffset.smte ├── groups │ └── main.smte ├── groups_adv │ └── main.smte ├── groups_adv2 │ └── main.smte └── src │ ├── boomerang.svg │ ├── boomerang1.svg │ ├── boomerang2.svg │ ├── boomerang3.svg │ ├── boomerang4.svg │ └── stroke.svg ├── index.html ├── package-lock.json ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.mp4 2 | *.webm 3 | *.gif 4 | res 5 | node_modules 6 | frames 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Devforth.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/puWorker.js: -------------------------------------------------------------------------------- 1 | 2 | const util = require('util'); 3 | const puppeteer = require('puppeteer'); 4 | const fs = require("fs"); 5 | 6 | const MAX_FILENAME_DIGS = 7; 7 | 8 | 9 | const input = { 10 | pageW: +process.argv[2], 11 | pageH: +process.argv[3], 12 | index: +process.argv[4], 13 | totalFramesCount: +process.argv[5], 14 | framesDir: process.argv[6], 15 | format: process.argv[7], 16 | quality: process.argv[8], 17 | skipFrames: +process.argv[9], 18 | } 19 | 20 | fs.writeFileSync(`/tmp/scriptimate_debug_test${input.index}.txt`, JSON.stringify(input, null, 2), () => {}); 21 | 22 | const fileHtml = fs.readFileSync(`${input.framesDir}/_index${(''+input.index).padStart(MAX_FILENAME_DIGS, '0')}.html`, 'utf8') 23 | const jpegFileName = `${input.framesDir}/${(''+(input.index - input.skipFrames)).padStart(MAX_FILENAME_DIGS, '0')}.${input.format}`; 24 | 25 | const genScreenshots = async () => { 26 | const browser = await puppeteer.launch({args: [ 27 | `--window-size=${input.pageW},${input.pageH}`, 28 | '--no-sandbox', 29 | '--disk-cache-dir=/tmp/pup', 30 | ], headless: true,}); 31 | 32 | const page = await browser.newPage(); 33 | await page.setViewport({width: input.pageW, height: input.pageH, deviceScaleFactor: 1}); 34 | await page._client.send('Emulation.clearDeviceMetricsOverride'); 35 | await page.setContent(fileHtml); 36 | await page.screenshot({path: jpegFileName, type: input.format, omitBackground: true}); 37 | 38 | process.exit(); 39 | } 40 | genScreenshots() 41 | -------------------------------------------------------------------------------- /bin/scriptimate.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {promises: fs, constants: fsConstants} = require('fs'); 4 | const { spawn, execFile } = require('child_process'); 5 | const { ArgumentParser } = require('argparse'); 6 | const { version } = require('../package.json'); 7 | const svgDim = require('svg-dimensions'); 8 | const YAML = require('yaml'); 9 | const crypto = require('crypto'); 10 | const os = require('os'); 11 | const jsdom = require("jsdom"); 12 | const { JSDOM } = jsdom; 13 | 14 | let uniq; 15 | 16 | function initUnique() { 17 | uniq = 0; 18 | } 19 | 20 | function generateUniqueId() { 21 | return 1237841 + uniq++; 22 | } 23 | 24 | const path = require('path'); 25 | 26 | const log = console.log; 27 | const MAX_FILENAME_DIGS = 7; 28 | let FRAMES_DIR = 'frames'; 29 | 30 | const parser = new ArgumentParser({ 31 | description: `Scriptimate v${version}` 32 | }); 33 | 34 | parser.add_argument('-v', '--version', { action: 'version', version }); 35 | parser.add_argument('-f', '--format', { help: 'output file format, or multiple via comma: "webm,mp4". Available formats: mov, mp4, gif, webm, default is mp4', default: 'mp4' }); 36 | parser.add_argument('-i', '--input', { help: 'Input .smte script file', default: null }); 37 | parser.add_argument('-fn', '--filename', { help: 'output filename', default: null }); 38 | parser.add_argument('-t', '--threads', { help: 'Threads count used during compiling, defaults to 4', default: 4 }); 39 | parser.add_argument('-fs', '--fromsecond', { help: 'Start from defined second (could be used to debug animation faster, also you can use "exis" keyword in smte script)', default: 0 }); 40 | parser.add_argument('-d', '--debughtml', { help: 'Create HTML files near image to debug', default: false }); 41 | parser.add_argument('-bd', '--basedir', { help: 'Input directory (folder where src subfolder and .smte file is located)', default: './' }); 42 | parser.add_argument('-fps', '--fps', { help: 'FPS', default: 25 }); 43 | parser.add_argument('-if', '--intermediateFormat', { help: 'Screenshots format used to compile video png|jpeg, defaults to png', default: 'png' }); 44 | parser.add_argument('-ijq', '--intermediateJpegQuality', { help: 'JPEG quality 0.0 - 1.0, defaults to 1', default: 1 }); 45 | parser.add_argument('-nc', '--nocache', { help: "Don't use screenshots cache (but still generate it), for scriptimate develeopmnt", default: false }); 46 | 47 | 48 | const proc_args = parser.parse_args(); 49 | 50 | const FPS = +proc_args.fps; 51 | 52 | const FORMAT = proc_args.intermediateFormat; 53 | const QUALITY = +proc_args.intermediateJpegQuality; 54 | 55 | 56 | FRAMES_DIR = proc_args.basedir + '/' + FRAMES_DIR; 57 | 58 | let translationsDict = {}; 59 | let parts; 60 | let timers; 61 | let boxholes; 62 | 63 | let groups; // name => Array of lines 64 | let freezer; 65 | 66 | let pageScale, pageW, pageH; 67 | let groupToAddNext; 68 | let skipFrames; 69 | let globalFramesCounter; 70 | let globalLastFrame; 71 | let cntr; 72 | let totalFrames; 73 | let totalFramesCount; 74 | 75 | 76 | function initVariables() { 77 | parts = {}; 78 | timers = {}; 79 | boxholes = {}; 80 | groups = {}; // name => Array of lines 81 | freezer = {}; 82 | pageScale = 1; 83 | pageW = 0; 84 | pageH = 0; 85 | groupToAddNext = null; 86 | skipFrames = 0; 87 | globalFramesCounter = 0; 88 | globalLastFrame = null; 89 | cntr = 0; 90 | totalFrames = 0; 91 | totalFramesCount = 0; 92 | frameAbsIndexByHTMLHash = {}; 93 | reuseAbsFrameIndexForAbsFrameIndex = {}; 94 | frameHashByAbsIndex = {}; 95 | } 96 | 97 | 98 | const arrayChunks = (arr, size) => arr.reduce((acc, e, i) => (i % size ? acc[acc.length - 1].push(e) : acc.push([e]), acc), []); 99 | 100 | // want more? copy from here: https://hinty.io/ivictbor/animation-formulas/ 101 | const animationHandlersByMode = { 102 | linear: (t, b, c, d) => { 103 | return c*t/d + b; 104 | }, 105 | easein: (t, b, c, d) => { // Quadratic easing in 106 | t /= d; 107 | return c*t*t + b; 108 | }, 109 | easeout: (t, b, c, d) => { // Quadratic easing out 110 | t /= d; 111 | return -c * t*(t-2) + b; 112 | }, 113 | easeinout: (t, b, c, d) => { // Quadratic easing out 114 | t /= d/2; 115 | if (t < 1) return c/2*t*t + b; 116 | t--; 117 | return -c/2 * (t*(t-2) - 1) + b; 118 | }, 119 | } 120 | 121 | const ACTION_HANDLERS = { 122 | move: (i, ags_arr, first_frame_in_animate, frames, mode) => { 123 | const svg = ags_arr[0]; 124 | if (!parts[svg]) { 125 | return; 126 | } 127 | const dstLeft = ags_arr[1] === '-' ? parts[svg].left : eval(ags_arr[1]); 128 | const dstTop = ags_arr[2] === '-' ? parts[svg].top : eval(ags_arr[2]); 129 | if (first_frame_in_animate) { 130 | freezer[svg] = {...freezer[svg], top: parts[svg].top, left: parts[svg].left}; 131 | } 132 | 133 | parts[svg].top = animationHandlersByMode[mode](i, freezer[svg].top, dstTop - freezer[svg].top, frames); 134 | parts[svg].left = animationHandlersByMode[mode](i, freezer[svg].left, dstLeft - freezer[svg].left, frames); 135 | global[`\$${svg}__X`] = parts[svg].left; //todo legacy 136 | global[`\$${svg}__Y`] = parts[svg].top; //todo legacy 137 | global[`\$${svg}__LEFT`] = parts[svg].left; 138 | global[`\$${svg}__TOP`] = parts[svg].top; 139 | }, 140 | scale: (i, ags_arr, first_frame_in_animate, frames, mode, cmd) => { 141 | const svg = ags_arr[0]; 142 | if (!parts[svg]) { 143 | log(`WARN: scale not applied, part not found: ${svg}, line: \n${cmd}\n`); 144 | return; 145 | } 146 | const dstScale = +eval(ags_arr[1]); 147 | let transformOrigin = null; 148 | if (ags_arr[2]) { 149 | transformOrigin = `${ags_arr[2]}`; 150 | if (ags_arr[3]) { 151 | transformOrigin = `${transformOrigin} ${ags_arr[3]}`; 152 | } 153 | } 154 | if (first_frame_in_animate) { 155 | freezer[svg] = {...freezer[svg], scale: parts[svg].scale}; 156 | } 157 | parts[svg].transformOrigin = transformOrigin; 158 | parts[svg].scale = animationHandlersByMode[mode](i, freezer[svg].scale, dstScale - freezer[svg].scale, frames); 159 | }, 160 | rotate: (i, ags_arr, first_frame_in_animate, frames, mode, cmd) => { 161 | const svg = ags_arr[0]; 162 | if (!parts[svg]) { 163 | log(`WARN: rotate not applied, part not found: ${svg}, line: \n${cmd}\n`); 164 | return; 165 | } 166 | const dstRotate = +eval(ags_arr[1]); 167 | let transformOrigin = null; 168 | if (ags_arr[2]) { 169 | transformOrigin = `${ags_arr[2]}`; 170 | if (ags_arr[3]) { 171 | transformOrigin = `${transformOrigin} ${ags_arr[3]}`; 172 | } 173 | } 174 | parts[svg].transformOrigin = transformOrigin; 175 | if (first_frame_in_animate) { 176 | freezer[svg] = {...freezer[svg], rotate: parts[svg].rotate}; 177 | } 178 | parts[svg].rotate = animationHandlersByMode[mode](i, freezer[svg].rotate, dstRotate - freezer[svg].rotate, frames); 179 | }, 180 | opacity: (i, ags_arr, first_frame_in_animate, frames, mode, cmd) => { 181 | const svg = ags_arr[0]; 182 | if (!parts[svg]) { 183 | log(`WARN: opacity not applied, part not found: ${svg}, line: \n${cmd}\n`); 184 | return; 185 | } 186 | const dstOpacity = +eval(ags_arr[1]); 187 | if (first_frame_in_animate) { 188 | freezer[svg] = {...freezer[svg], opacity: parts[svg].opacity}; 189 | } 190 | parts[svg].opacity = animationHandlersByMode[mode](i, freezer[svg].opacity, dstOpacity - freezer[svg].opacity, frames); 191 | }, 192 | dashoffset: (i, ags_arr, first_frame_in_animate, frames, mode, cmd) => { 193 | const svg = ags_arr[0]; 194 | if (!parts[svg]) { 195 | log(`WARN: dashoffset not applied, part not found: ${svg}, line: \n${cmd}\n`); 196 | return; 197 | } 198 | const dstOffset = +eval(ags_arr[1]); 199 | if (first_frame_in_animate) { 200 | freezer[svg] = {...freezer[svg], dashoffset: parts[svg].dashoffset}; 201 | } 202 | parts[svg].dashoffset = animationHandlersByMode[mode](i, freezer[svg].dashoffset, dstOffset - freezer[svg].dashoffset, frames); 203 | }, 204 | resize_div: (i, ags_arr, first_frame_in_animate, frames, mode, cmd) => { 205 | const svg = ags_arr[0]; 206 | if (!parts[svg]) { 207 | log(`WARN: resize_div not applied, part not found: ${svg}, line: \n${cmd}\n`); 208 | return; 209 | } 210 | if (parts[svg].type !== 'block') { 211 | log(`WARN: resize_div could be applied only to type block: not ${parts[svg].type}, part: ${svg}, line: \n${cmd}\n`); 212 | return; 213 | } 214 | const dstW = ags_arr[1] === '-' ? parts[svg].w : eval(ags_arr[1]); 215 | const dstH = ags_arr[2] === '-' ? parts[svg].h : eval(ags_arr[2]); 216 | if (first_frame_in_animate) { 217 | freezer[svg] = {...freezer[svg], w: parts[svg].w, h: parts[svg].h}; 218 | } 219 | 220 | parts[svg].w = animationHandlersByMode[mode](i, freezer[svg].w, dstW - freezer[svg].w, frames); 221 | parts[svg].h = animationHandlersByMode[mode](i, freezer[svg].h, dstH - freezer[svg].h, frames); 222 | }, 223 | pause: () =>{ 224 | 225 | } 226 | } 227 | 228 | const genHtml = (allParts) => { 229 | const inner = Object.values(allParts).sort((a,b) => { 230 | return a.index - b.index 231 | }).reduce((acc, p) => { 232 | if (p.type === 'part') { 233 | const bh = boxholes[p.toBoxHole] || {left: 0, top:0}; 234 | 235 | const partHTML = `
${p.content}
`; 236 | if (p.toBoxHole) { 237 | return `${acc}
238 | ${partHTML}
`; 239 | } else { 240 | return `${acc}${partHTML}`; 241 | } 242 | } else if (p.type === 'block') { 243 | return `${acc}
${p.content}
` 244 | } 245 | }, ''); 246 | return ` 247 | 248 | 259 | 260 | ${inner} 261 | `; 262 | } 263 | String.prototype.replaceAll = function(target, replacement) { 264 | return this.split(target).join(replacement); 265 | }; 266 | 267 | function firstDefined(...vals) { 268 | for (let i = 0; i < vals.length; i += 1) { 269 | if (vals[i] !== undefined) { 270 | return vals[i]; 271 | } 272 | } 273 | return undefined; 274 | } 275 | 276 | const moveToTop = async ( filename) => { 277 | if (!parts[filename]) { 278 | console.error(`error: can't moveToTop ${filename}, part should be first added, e.g. using "place"`); 279 | return; 280 | } 281 | parts[filename].index = Object.values(parts).reduce((a, p) => Math.max(a, p.index), 0) + 1; 282 | } 283 | 284 | const addPart = async (lang, filename, left, top, opacity, scale, toBoxHole, dashoffset) => { 285 | let f; 286 | 287 | const readFname = async (fn) => { 288 | const filePath = `${proc_args.basedir}/src/${fn}.svg`; 289 | const fileBuffer = await fs.readFile(filePath, { encoding: 'utf-8' }); 290 | return fileBuffer.toString(); 291 | }; 292 | let fname = `${filename}_${lang}`; 293 | try { 294 | f = await readFname(fname); 295 | } catch (e) { 296 | fname = filename; 297 | f = await readFname(fname) 298 | } 299 | await new Promise((resolve) => { 300 | svgDim.get(`${proc_args.basedir}/src/${fname}.svg`, function(err, dimensions) { 301 | if (err) { 302 | console.log(`INFO: can't read ${filename} dimensions`, err); 303 | } else { 304 | global[`\$${filename}__WIDTH`] = dimensions.width; 305 | global[`\$${filename}__HEIGHT`] = dimensions.height; 306 | } 307 | resolve(); 308 | }); 309 | }); 310 | const partIds = {}; 311 | let withUniquifiedIDs = f.replace(/id="(.*?)"/g, (_, v) => { 312 | if (!partIds[v]) { 313 | partIds[v] = `id_${generateUniqueId()}`; 314 | } 315 | return `id="${partIds[v]}"`; 316 | }); 317 | 318 | // for languages where texts might have unstable width, we need remove box limiting 319 | withUniquifiedIDs = withUniquifiedIDs.replace(//g, (_, v1, mid, v2) => { 320 | return ``; 321 | }); 322 | 323 | Object.keys(partIds).forEach((u) => { 324 | withUniquifiedIDs = withUniquifiedIDs.replaceAll(`#${u}`, `#${partIds[u]}`); 325 | }); 326 | if (lang !== 'default') { 327 | const strings = translationsDict[lang]; 328 | const dom = new JSDOM(withUniquifiedIDs); 329 | for ( let e of dom.window.document.querySelectorAll("tspan") ) { 330 | for (let tr of Object.keys(strings)) { 331 | e.innerHTML = e.innerHTML.replaceAll(tr, strings[tr]); 332 | }; 333 | } 334 | withUniquifiedIDs = dom.window.document.querySelector('html').innerHTML; 335 | } 336 | parts[filename] = { 337 | type: 'part', 338 | filename, 339 | content: withUniquifiedIDs, 340 | top: +firstDefined(eval(top), 0), 341 | left: +firstDefined(eval(left), 0), 342 | opacity: +firstDefined(eval(opacity), 1), 343 | index: Object.values(parts).reduce((a, p) => Math.max(a, p.index), 0) + 1, 344 | scale: +firstDefined(eval(scale), 1.0), 345 | rotate: +firstDefined(0, 0), 346 | dashoffset: +firstDefined(eval(dashoffset), 0), 347 | extrastyle: '', 348 | toBoxHole, 349 | }; 350 | freezer[filename] = {}; 351 | global[`\$${filename}__X`] = parts[filename].left; 352 | global[`\$${filename}__Y`] = parts[filename].top; 353 | global[`\$${filename}__LEFT`] = parts[filename].left; 354 | global[`\$${filename}__TOP`] = parts[filename].top; 355 | }; 356 | 357 | const addDiv = (name, left, top, w, h, opacity, c, toBoxHole) => { 358 | //+rest.join(' ').replaceAll('"', '').replaceAll("'", '') 359 | const content = eval(c); 360 | parts[name] = { 361 | type: 'block', 362 | name, 363 | top: +firstDefined(eval(top), 0), 364 | left: +firstDefined(eval(left), 0), 365 | opacity: +firstDefined(opacity, 1), 366 | w: +firstDefined(eval(w), 0), 367 | h: +firstDefined(eval(h), 0), 368 | index: Object.values(parts).reduce((a, p) => Math.max(a, p.index), 0) + 1, 369 | rotate: +firstDefined(0, 0), 370 | dashoffset: +firstDefined(0, 0), 371 | content: content, 372 | toBoxHole, 373 | } 374 | global[`\$${name}__X`] = parts[name].left; 375 | global[`\$${name}__Y`] = parts[name].top; 376 | global[`\$${name}__LEFT`] = parts[name].left; 377 | global[`\$${name}__TOP`] = parts[name].top; 378 | } 379 | 380 | const addBoxHole = (name, left, top, w, h) => { 381 | boxholes[name] = { 382 | name, 383 | top: +firstDefined(eval(top), 0), 384 | left: +firstDefined(eval(left), 0), 385 | w: +firstDefined(eval(w), 0), 386 | h: +firstDefined(eval(h), 0), 387 | } 388 | } 389 | 390 | const setPseudoInterval = (callback, ms) => { 391 | const o = { 392 | t: 0, 393 | tick(passed_ms) { 394 | this.t += passed_ms; 395 | while (this.t >= ms) { 396 | callback(); 397 | this.t -= ms; 398 | } 399 | } 400 | } 401 | return o; 402 | } 403 | 404 | const addStyle = (part, style) => { 405 | if (!parts[part]) { 406 | log(`WARN: style not applied, part not found: ${part}`) 407 | return 408 | } 409 | parts[part].extrastyle = style; 410 | } 411 | 412 | const schedule_eval = (name, ms, ...rest) => { 413 | const code = rest.join(' '); 414 | // todo check that timer already scheduled and drop warn 415 | 416 | timers[name] = setPseudoInterval( 417 | () => { 418 | const incr = (part, delta) => { 419 | parts[part].content = +parts[part].content + (delta || 1); 420 | global[part+'_value'] = parts[part].content 421 | } 422 | const get = (part) => { 423 | return parts[part].content; 424 | } 425 | const set = (part, value) => { 426 | parts[part].content = value; 427 | global[part+'_value'] = value; 428 | } 429 | eval(code); 430 | }, 431 | +ms 432 | ) 433 | } 434 | 435 | const schedule_time = (name, ms = 50) => { 436 | const code = `incrM('${name}_minutes'); if (+get('${name}_minutes') >= 60) { incr('${name}_hours'); set('${name}_minutes', 0)}`; 437 | 438 | timers[name] = setPseudoInterval( 439 | () => { 440 | const incr = (part) => { 441 | parts[part].content = +parts[part].content + 1; 442 | global[part+'_value'] = parts[part].content 443 | } 444 | const incrM = (part) => { 445 | parts[part].content = +parts[part].content + 7; 446 | global[part+'_value'] = parts[part].content 447 | } 448 | const get = (part) => { 449 | return parts[part].content; 450 | } 451 | const set = (part, value) => { 452 | parts[part].content = eval(value); 453 | global[part+'_value'] = eval(value) 454 | } 455 | eval(code) 456 | }, 457 | +ms 458 | ) 459 | } 460 | 461 | const unschedule = (name) => { 462 | // chack that schedulled and drop warn 463 | delete timers[name]; 464 | } 465 | 466 | 467 | 468 | let frameAbsIndexByHTMLHash = {}; 469 | let reuseAbsFrameIndexForAbsFrameIndex = {}; 470 | let frameHashByAbsIndex = {}; 471 | let cacheDir = ''; 472 | 473 | const runGeneration = async (lang) => { 474 | initVariables(); 475 | initUnique(); 476 | 477 | function getFilename() { 478 | const baseFilename = proc_args.filename ? proc_args.filename: proc_args.input.split('.').slice(0, -1).join('.'); 479 | let prefix = ''; 480 | if (lang !== 'default') { 481 | prefix = `_${lang}`; 482 | } 483 | return `${baseFilename}${prefix}`; 484 | } 485 | 486 | const doFrame = async () => { 487 | 488 | if (cntr < skipFrames || (globalLastFrame && cntr > globalLastFrame)) { 489 | cntr += 1; 490 | return; 491 | } 492 | totalFramesCount += 1; 493 | 494 | const html = genHtml(parts); 495 | const hash = crypto.createHash('sha1').update(html).digest('base64url'); 496 | frameHashByAbsIndex[cntr] = hash; 497 | 498 | if (!frameAbsIndexByHTMLHash[hash]) { 499 | frameAbsIndexByHTMLHash[hash] = cntr; 500 | 501 | const screenshotCachedPath = path.join(cacheDir, `${hash}.${FORMAT}`); 502 | 503 | const noHTMLNeeded = false; 504 | try { 505 | await fs.access(screenshotCachedPath, fsConstants.F_OK); 506 | noHTMLNeeded = true; 507 | } catch (e) { 508 | // no file exists to copy, so need generate 509 | } 510 | if (!noHTMLNeeded) { 511 | await fs.writeFile(`${FRAMES_DIR}/_index${(''+cntr).padStart(MAX_FILENAME_DIGS, '0')}.html`, html, function(err) { 512 | if (err) { 513 | return console.log(err); 514 | } 515 | }); 516 | } 517 | } else { 518 | reuseAbsFrameIndexForAbsFrameIndex[cntr] = frameAbsIndexByHTMLHash[hash]; 519 | } 520 | 521 | cntr += 1; 522 | log(`HTML pages gen: ${(cntr * 100.0 / (totalFrames + 1)).toFixed(2)}%`, '\033[F'); 523 | } 524 | 525 | const scriptBuffer = await fs.readFile(proc_args.basedir + '/' + proc_args.input); 526 | const script = scriptBuffer.toString(); 527 | if (! script) { 528 | throw "Please specify .smte file e.g. -i demo.smte" 529 | } 530 | 531 | cacheDir = path.join(os.tmpdir(), 'scriptimateCache'); 532 | await fs.mkdir(cacheDir, { recursive: true }); 533 | 534 | let totalMs = 0; 535 | for (v of script.split('\n')) { 536 | const d1 = v.split(' '); 537 | const cmd = d1[0]; 538 | if (cmd.startsWith('animate_')) { 539 | totalMs += +cmd.replace('animate_', ''); 540 | totalFrames += Math.round(+cmd.replace('animate_', '') / 1.0e3 * FPS); 541 | } 542 | } 543 | 544 | await fs.rm(FRAMES_DIR, { recursive: true }); 545 | await fs.mkdir(FRAMES_DIR, { recursive: true }); 546 | 547 | const processed_lines = [] 548 | for (const lineIter of script.split('\n')) { 549 | let line = lineIter; 550 | if (!line.trim() || line.trim().startsWith(';')) { 551 | // empty line 552 | continue; 553 | } 554 | if (lang !== 'default') { 555 | const strings = translationsDict[lang]; 556 | Object.keys(strings).forEach((tr) => { 557 | // allows to translate constants in script too 558 | line = line.replaceAll(`'${tr}'`, `'${strings[tr]}'`).replaceAll(`"${tr}"`, `"${strings[tr]}"`); 559 | }); 560 | } 561 | 562 | if (line.trim().startsWith('&&')) { 563 | processed_lines[processed_lines.length - 1] += ` ${line.trim()} `; 564 | } else { 565 | processed_lines.push(line); 566 | } 567 | } 568 | 569 | for (const [file_line, line] of processed_lines.entries()) { 570 | if (line.startsWith(' ') || line.startsWith('\t')) { 571 | if (groupToAddNext) { 572 | if(!groups[groupToAddNext]) { 573 | groups[groupToAddNext] = []; 574 | } 575 | groups[groupToAddNext].push(line.trim()); 576 | continue; 577 | } else { 578 | throw `Line can start with whitespace only if it follows after define_group on line ${file_line + 1}:\n${line}`; 579 | } 580 | } else { 581 | groupToAddNext = null; 582 | } 583 | 584 | const handleActionsInAnimate = (i, argSets, frames, cmd) => { 585 | let atLeastOneFrameMade = false; 586 | const first_frame_in_animate = i === 1; 587 | 588 | for (let ags of argSets) { 589 | const ags_arr = ags.trim().split(' '); 590 | const action = ags_arr.shift(); 591 | let mode = 'linear'; 592 | if (Object.keys(animationHandlersByMode).includes(ags_arr[0])) { 593 | mode = ags_arr.shift() 594 | } 595 | ACTION_HANDLERS[action](i, ags_arr, first_frame_in_animate, frames, mode, cmd); 596 | atLeastOneFrameMade = true; 597 | } 598 | return atLeastOneFrameMade; 599 | } 600 | 601 | const process_line = async (line, group) => { 602 | const line_splitted_by_whitespace = line.split(' '); 603 | const cmd = line_splitted_by_whitespace[0]; 604 | let argSets = line_splitted_by_whitespace.slice(1).join(' '); 605 | 606 | if (argSets) { 607 | argSets = argSets.split('&&'); 608 | } 609 | if (cmd === 'set_frame_size' || cmd === 'init_page') { //init_page is legacy 610 | if (pageW) { 611 | return; 612 | } 613 | const args = argSets[0].split(' '); 614 | pageScale = firstDefined(args[2], 1); 615 | pageW = Math.round(eval(args[0]) * pageScale); 616 | pageH = Math.round(eval(args[1]) * pageScale); 617 | if (proc_args.fromsecond) { 618 | skipFrames = proc_args.fromsecond * FPS; 619 | } 620 | log(`🎥 Format selected: ${proc_args.format} 621 | 📁 Filename ${getFilename()}.${proc_args.format} 622 | 📺 Resolution: ${pageW}x${pageH} 623 | ✂ Start from second: ${proc_args.fromsecond}s 624 | \n`); 625 | } 626 | else if (cmd === 'const' || cmd === 'var'){ 627 | argSets[0].split(' ').forEach((s)=>{ 628 | let v = s.split('=') 629 | global[v[0]] = eval(v[1]) 630 | }) 631 | } else if (cmd === 'exit') { 632 | globalLastFrame = globalLastFrame || globalFramesCounter; 633 | } else if (cmd === 'place') { 634 | let args = argSets[0].split(' '); 635 | await addPart(lang, ...args); 636 | } else if (cmd === 'moveToTop') { 637 | let args = argSets[0].split(' '); 638 | await moveToTop(...args); 639 | } 640 | else if (cmd === 'place_div') { 641 | let args = argSets[0].split(' '); 642 | addDiv(...args); 643 | } 644 | else if (cmd === 'place_boxhole') { 645 | let args = argSets[0].split(' '); 646 | addBoxHole(...args); 647 | } 648 | else if (cmd === 'schedule_eval') { 649 | let args = argSets[0].split(' '); 650 | schedule_eval(...args); 651 | } 652 | else if (cmd === 'schedule_time') { 653 | let args = argSets[0].split(' '); 654 | schedule_time(...args); 655 | } 656 | else if (cmd === 'unschedule') { 657 | let args = argSets[0].split(' '); 658 | unschedule(...args); 659 | } 660 | else if (cmd === 'addstyle') { 661 | let args = argSets[0].split(' '); 662 | addStyle(args[0], args.slice(1).join(' ')); 663 | } 664 | else if (cmd === 'define_group') { 665 | let grpName = argSets[0].trim(); 666 | if (grpName.endsWith(':')) { 667 | grpName = grpName.slice(0, -1) 668 | } 669 | groupToAddNext = grpName; 670 | } 671 | else if (cmd.startsWith('animate_')) { 672 | const duration_ms = +cmd.replace('animate_', ''); 673 | const frames = Math.round(duration_ms / 1.0e3 * FPS); 674 | 675 | for (let i = 1; i <= frames; i += 1) { 676 | Object.values(timers).forEach((t) => t.tick(1000.0 / FPS)); 677 | handleActionsInAnimate(i, argSets, frames, cmd) 678 | await doFrame(); 679 | globalFramesCounter += 1; 680 | } 681 | } 682 | else if (cmd.startsWith('run_groups_together')) { 683 | const executing_groups = argSets[0].split(' ').map(g => g.trim()); 684 | let needNextIteration = true; 685 | const needOperationByGroup = {}; 686 | const animationStateByGroup = {} 687 | while (needNextIteration) { 688 | 689 | let atLeastOneFrameMade = false; 690 | for (const grp of executing_groups) { 691 | if (!needOperationByGroup[grp]) { 692 | while (groups[grp].length) { 693 | needOperationByGroup[grp] = groups[grp].shift() 694 | lineIn = needOperationByGroup[grp] 695 | 696 | if (needOperationByGroup[grp].startsWith('animate_')) { 697 | break; // we found next animate 698 | } else { 699 | await process_line(lineIn, grp); 700 | needOperationByGroup[grp] = null; // something which does not need a frames, omit it 701 | } 702 | } 703 | if (needOperationByGroup[grp]) { 704 | // this is a new next animation 705 | const line_splitted_by_whitespace = needOperationByGroup[grp].split(' '); 706 | const cmd = line_splitted_by_whitespace[0]; 707 | let argSets = line_splitted_by_whitespace.slice(1).join(' '); 708 | 709 | if (argSets) { 710 | argSets = argSets.split('&&'); 711 | } 712 | 713 | const duration_ms = +cmd.replace('animate_', ''); 714 | const frames = Math.round(duration_ms / 1.0e3 * FPS); 715 | animationStateByGroup[grp] = { 716 | argSets, 717 | frames, 718 | i: 1, 719 | } 720 | } 721 | } 722 | 723 | if (needOperationByGroup[grp]) { 724 | const state = animationStateByGroup[grp]; 725 | 726 | if (handleActionsInAnimate(state.i, state.argSets, state.frames)) { 727 | atLeastOneFrameMade = true; 728 | } 729 | 730 | state.i += 1; 731 | if (state.i > state.frames) { 732 | needOperationByGroup[grp] = null; // next cycle should pick up something 733 | } 734 | } 735 | } 736 | 737 | needNextIteration = atLeastOneFrameMade; 738 | if (atLeastOneFrameMade) { 739 | Object.values(timers).forEach((t) => t.tick(1000.0 / FPS)); 740 | globalFramesCounter += 1; 741 | await doFrame(); 742 | } 743 | } 744 | 745 | } 746 | } 747 | await process_line(line); 748 | } 749 | 750 | 751 | log('✅ [2/4] HTML generation done') 752 | log(`🕗 Total duration: ${(globalFramesCounter / FPS).toFixed(1)}s 🎞️ FPS: ${FPS}`) 753 | 754 | const THREADS = + proc_args.threads; 755 | let totalGenCntr = 0; 756 | 757 | async function genScreenshots(index) { 758 | const absoluteIndex = index + skipFrames; 759 | const htmlHash = frameHashByAbsIndex[absoluteIndex]; 760 | const screenshotCachedPath = path.join(cacheDir, `${htmlHash}.${FORMAT}`); 761 | const dstFile = `${FRAMES_DIR}/${(''+(index)).padStart(MAX_FILENAME_DIGS, '0')}.${FORMAT}`; 762 | 763 | try { 764 | if (!proc_args.nocache) { 765 | await fs.copyFile(screenshotCachedPath, dstFile); 766 | return; 767 | } 768 | } catch (e) { 769 | // console.log('errr', e); 770 | // no file exists to copy, so need generate 771 | } 772 | await new Promise((resolve) => { 773 | if (!reuseAbsFrameIndexForAbsFrameIndex[absoluteIndex]) { 774 | 775 | const proc = spawn('node', [ 776 | path.resolve(__dirname, 'puWorker.js'), 777 | pageW, pageH, 778 | absoluteIndex, 779 | totalFramesCount, 780 | FRAMES_DIR, 781 | FORMAT, 782 | QUALITY, skipFrames || 0 783 | ], { shell: true }); 784 | proc.stdout.on('data', (data) => { 785 | // console.log(`NodeOUT: ${data}`); 786 | }); 787 | proc.stderr.on('data', (data) => { 788 | console.error(`NodeERR: ${data}`); 789 | }); 790 | proc.on('close', async (code) => { 791 | totalGenCntr += 1; 792 | log(`Frames gen: ${(totalGenCntr * 100.0 / totalFramesCount).toFixed(2)}%`, '\033[F'); 793 | if (code !== 0) { 794 | log('🔴 node failed') 795 | process.exit(-1) 796 | } 797 | await fs.copyFile(dstFile, screenshotCachedPath); 798 | resolve(); 799 | }); 800 | } else { 801 | totalGenCntr += 1; 802 | resolve(); 803 | } 804 | }); 805 | } 806 | 807 | async function copyReusedScreenshots(index) { 808 | const absoluteIndex = index + skipFrames; 809 | if (reuseAbsFrameIndexForAbsFrameIndex[absoluteIndex]) { 810 | const reuseAbsIndex = reuseAbsFrameIndexForAbsFrameIndex[absoluteIndex]; 811 | const srcFile = `${FRAMES_DIR}/${(''+(reuseAbsIndex - skipFrames)).padStart(MAX_FILENAME_DIGS, '0')}.${FORMAT}`; 812 | const dstFile = `${FRAMES_DIR}/${(''+(index)).padStart(MAX_FILENAME_DIGS, '0')}.${FORMAT}`; 813 | try { 814 | await fs.copyFile(srcFile, dstFile); 815 | } catch (e) { 816 | log(`🔴 failed to copy frame ${srcFile} to ${dstFile}`) 817 | throw e; 818 | } 819 | } 820 | } 821 | 822 | async function genScreenshotsForChunk(indexesChunk) { 823 | for (let i=0; i < indexesChunk.length; i+=1) { 824 | await genScreenshots(indexesChunk[i]); 825 | } 826 | } 827 | 828 | const indexes = Array.from( Array(totalFramesCount).keys() ); 829 | 830 | await Promise.all( 831 | arrayChunks(indexes, Math.round( (indexes.length) / THREADS) ).map(async (indexesChunk) => await genScreenshotsForChunk(indexesChunk)) 832 | ) 833 | 834 | // another run to copy all duplicate files 835 | indexes.forEach(copyReusedScreenshots); 836 | 837 | 838 | 839 | 840 | log('✅ [3/4] Frames generation done') 841 | 842 | await (new Promise((resolve) => { 843 | 844 | const formats = proc_args.format.split(','); 845 | formats.forEach((format) => { 846 | if (!format.trim()) { 847 | return; 848 | } 849 | let ffmpeg_args = ['-framerate', `${FPS}/1`, '-i', `${FRAMES_DIR}/%0${MAX_FILENAME_DIGS}d.${FORMAT}`, ]; 850 | if (format === 'webm') { 851 | ffmpeg_args = [...ffmpeg_args, '-c:v', 'libvpx-vp9', '-crf', '30', '-b:v', '0', '-r', ''+FPS, `${getFilename()}.${format}`, '-y'] 852 | } else if (format === 'mp4') { 853 | ffmpeg_args = [...ffmpeg_args, '-c:v', 'libx264', '-r', ''+FPS, `${getFilename()}.${format}`, '-y'] 854 | } else if (format === 'mov') { 855 | ffmpeg_args = [...ffmpeg_args, '-c:v', 'hevc_videotoolbox', '-allow_sw', '1', '-alpha_quality', '0.75', '-vtag', 'hvc1', '-r', ''+FPS, `${getFilename()}.${format}`, '-y'] 856 | } else if (format === 'gif') { 857 | // to gen palled for each frame use stats_mode=single and add :new=1 to paletteuse options 858 | ffmpeg_args = [...ffmpeg_args, '-vf', `fps=${FPS},split[s0][s1];[s0]palettegen=stats_mode=full[p];[s1][p]paletteuse=dither=sierra2_4a:bayer_scale=5`, '-loop', '0', `${getFilename()}.${format}`, '-y'] 859 | 860 | } else { 861 | console.error(`Unknown format: ${format}`); 862 | } 863 | log(`💿 Running encoder:\nffmpeg ${ffmpeg_args.join(' ')}`) 864 | 865 | const ls = spawn('ffmpeg', ffmpeg_args); 866 | 867 | ls.stdout.on('data', (data) => { 868 | console.log(`ffmpeg: ${data}`); 869 | }); 870 | 871 | ls.stderr.on('data', (data) => { 872 | console.error(`ffmpeg: ${data}`); 873 | }); 874 | 875 | ls.on('close', (code) => { 876 | console.log(`ffmpeg exited with code ${code}`); 877 | if (code === 0) { 878 | log('✅ [4/4] Video encoding done') 879 | } else { 880 | log('🔴 [4/4] Video encoding failed, se output above') 881 | } 882 | resolve(); 883 | }); 884 | }); 885 | 886 | })); 887 | }; 888 | 889 | (async () => { 890 | try { 891 | const transBuffer = await fs.readFile(proc_args.basedir + '/translations.yml'); 892 | const transStr = transBuffer.toString(); 893 | if (transStr) { 894 | translationsDict = YAML.parse(transStr); 895 | } 896 | } catch (e) { 897 | console.log('Running without translations.yml', e) 898 | } 899 | 900 | for (let lang of [...Object.keys(translationsDict), 'default']) { 901 | await runGeneration(lang); 902 | } 903 | })(); 904 | -------------------------------------------------------------------------------- /examples/1_helloworld.smte: -------------------------------------------------------------------------------- 1 | set_frame_size 600 300 2 | place boomerang 0 100 3 | animate_3000 move boomerang 500 - 4 | 5 | -------------------------------------------------------------------------------- /examples/2_rotate_scale_opacity.smte: -------------------------------------------------------------------------------- 1 | set_frame_size 600 300 2 | place boomerang 0 100 3 | animate_1000 move boomerang 250 - 4 | animate_1000 rotate boomerang 360 5 | animate_1000 scale boomerang 2 6 | animate_1000 opacity boomerang 0 -------------------------------------------------------------------------------- /examples/3_parallel_animations.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devforth/scriptimate/54317f4f693036c1be62cd1f21dc063f332e91e9/examples/3_parallel_animations.gif -------------------------------------------------------------------------------- /examples/3_parallel_animations.smte: -------------------------------------------------------------------------------- 1 | set_frame_size 600 300 2 | place boomerang 0 100 3 | animate_1000 move boomerang 400 - && rotate boomerang 720 && scale boomerang 2 -------------------------------------------------------------------------------- /examples/4_constants_and_expressions.smte: -------------------------------------------------------------------------------- 1 | const $frameWidth=600 $frameHeight=300 2 | const $numberOfSpins=5 3 | const $ScaleFactor=0.01 4 | 5 | set_frame_size $frameWidth $frameHeight 6 | place boomerang 0 $frameHeight-$boomerang__HEIGHT 7 | animate_1000 move boomerang $frameWidth-$boomerang__WIDTH 0 && rotate boomerang 360*$numberOfSpins && scale boomerang $ScaleFactor -------------------------------------------------------------------------------- /examples/5_easing.smte: -------------------------------------------------------------------------------- 1 | set_frame_size 600 300 2 | 3 | place boomerang1 0 0 4 | place boomerang2 0 $boomerang1__HEIGHT 5 | place boomerang3 0 $boomerang1__HEIGHT*2 6 | place boomerang4 0 $boomerang1__HEIGHT*3 7 | 8 | animate_3000 move boomerang1 600-$boomerang1__WIDTH - 9 | && move easein boomerang2 600-$boomerang1__WIDTH - 10 | && move easeout boomerang3 600-$boomerang1__WIDTH - 11 | && move easeinout boomerang4 600-$boomerang1__WIDTH - -------------------------------------------------------------------------------- /examples/6_groups.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devforth/scriptimate/54317f4f693036c1be62cd1f21dc063f332e91e9/examples/6_groups.gif -------------------------------------------------------------------------------- /examples/6_groups.smte: -------------------------------------------------------------------------------- 1 | const $frameW=600 $frameH=300 2 | set_frame_size $frameW $frameH 3 | 4 | place boomerang1 0 $boomerang1__HEIGHT/2 5 | place boomerang2 0 $boomerang1__HEIGHT*3/2 6 | 7 | 8 | define_group scenario1: 9 | animate_1000 move easein boomerang1 270 - 10 | animate_2000 move easeout boomerang1 $frameW-$boomerang1__WIDTH $frameH-$boomerang1__HEIGHT 11 | 12 | 13 | define_group scenario2: 14 | animate_1000 move boomerang2 250 - 15 | animate_1000 pause 16 | animate_1000 move boomerang2 $frameW-$boomerang1__WIDTH 0 17 | 18 | define_group rotator: 19 | animate_3000 rotate boomerang1 360*4 && rotate boomerang2 360*5 20 | 21 | run_groups_together scenario1 scenario2 rotator -------------------------------------------------------------------------------- /examples/7_dashoffset.smte: -------------------------------------------------------------------------------- 1 | set_frame_size 300 300 2 | 3 | place stroke 10 10 1 1 0 -1000 4 | addstyle stroke stroke-dasharray:1000; 5 | 6 | animate_2000 dashoffset stroke 0 -------------------------------------------------------------------------------- /examples/groups/main.smte: -------------------------------------------------------------------------------- 1 | init_page 300 160 2 2 | 3 | place bg 0 0 4 | 5 | place blue 0 0 6 | place red 0 50 7 | 8 | define_group g1: 9 | animate_80 move blue 120 0 10 | animate_160 move blue 240 0 11 | 12 | 13 | define_group g2: 14 | animate_120 move red 120 50 15 | animate_120 move red 240 50 16 | 17 | 18 | 19 | run_groups_together g1 g2 20 | -------------------------------------------------------------------------------- /examples/groups_adv/main.smte: -------------------------------------------------------------------------------- 1 | init_page 500 500 2 2 | 3 | place blue 0 200 4 | place red 0 300 5 | 6 | define_group g1: 7 | animate_500 move blue 300 100 8 | animate_500 move red 450 200 9 | 10 | define_group g2: 11 | animate_500 move red 300 400 12 | animate_500 move blue 450 300 13 | 14 | run_groups_together g1 g2 15 | -------------------------------------------------------------------------------- /examples/groups_adv2/main.smte: -------------------------------------------------------------------------------- 1 | init_page 500 500 2 2 | 3 | place blue 0 200 4 | place red 0 300 5 | 6 | animate_1000 move easyin blue 400 200 && move red 400 300 7 | 8 | 9 | define_group g1: 10 | animate_400 move red 100 300 11 | animate_300 move red 200 300 12 | animate_100 move red 300 300 13 | animate_50 move red 400 300 14 | 15 | define_group g2: 16 | animate_50 move blue 100 200 17 | animate_100 move blue 200 200 18 | animate_300 move blue 300 200 19 | animate_400 move blue 400 200 20 | 21 | run_groups_together g1 g2 22 | 23 | animate_300 move blue 0 200 && move red 0 300 24 | 25 | define_group g3: 26 | animate_200 move blue 200 0 27 | 28 | 29 | define_group g4: 30 | animate_200 move red 200 400 31 | 32 | 33 | run_groups_together g3 g4 34 | 35 | animate_300 move blue 400 200 && move red 400 200 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/src/boomerang.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/src/boomerang1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/src/boomerang2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/src/boomerang3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/src/boomerang4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/src/stroke.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scriptimate", 3 | "version": "1.2.28", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@tootallnate/once": { 8 | "version": "2.0.0", 9 | "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", 10 | "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" 11 | }, 12 | "@types/node": { 13 | "version": "17.0.14", 14 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz", 15 | "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==", 16 | "optional": true 17 | }, 18 | "@types/yauzl": { 19 | "version": "2.9.2", 20 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", 21 | "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", 22 | "optional": true, 23 | "requires": { 24 | "@types/node": "*" 25 | } 26 | }, 27 | "abab": { 28 | "version": "2.0.5", 29 | "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", 30 | "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" 31 | }, 32 | "acorn": { 33 | "version": "8.7.0", 34 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", 35 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" 36 | }, 37 | "acorn-globals": { 38 | "version": "6.0.0", 39 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", 40 | "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", 41 | "requires": { 42 | "acorn": "^7.1.1", 43 | "acorn-walk": "^7.1.1" 44 | }, 45 | "dependencies": { 46 | "acorn": { 47 | "version": "7.4.1", 48 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 49 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" 50 | } 51 | } 52 | }, 53 | "acorn-walk": { 54 | "version": "7.2.0", 55 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", 56 | "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" 57 | }, 58 | "agent-base": { 59 | "version": "6.0.2", 60 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 61 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 62 | "requires": { 63 | "debug": "4" 64 | } 65 | }, 66 | "argparse": { 67 | "version": "2.0.1", 68 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 69 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 70 | }, 71 | "asynckit": { 72 | "version": "0.4.0", 73 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 74 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 75 | }, 76 | "balanced-match": { 77 | "version": "1.0.2", 78 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 79 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 80 | }, 81 | "base64-js": { 82 | "version": "1.5.1", 83 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 84 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 85 | }, 86 | "bl": { 87 | "version": "4.1.0", 88 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 89 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 90 | "requires": { 91 | "buffer": "^5.5.0", 92 | "inherits": "^2.0.4", 93 | "readable-stream": "^3.4.0" 94 | } 95 | }, 96 | "brace-expansion": { 97 | "version": "1.1.11", 98 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 99 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 100 | "requires": { 101 | "balanced-match": "^1.0.0", 102 | "concat-map": "0.0.1" 103 | } 104 | }, 105 | "browser-process-hrtime": { 106 | "version": "1.0.0", 107 | "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", 108 | "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" 109 | }, 110 | "buffer": { 111 | "version": "5.7.1", 112 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 113 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 114 | "requires": { 115 | "base64-js": "^1.3.1", 116 | "ieee754": "^1.1.13" 117 | } 118 | }, 119 | "buffer-crc32": { 120 | "version": "0.2.13", 121 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 122 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" 123 | }, 124 | "chownr": { 125 | "version": "1.1.4", 126 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 127 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 128 | }, 129 | "combined-stream": { 130 | "version": "1.0.8", 131 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 132 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 133 | "requires": { 134 | "delayed-stream": "~1.0.0" 135 | } 136 | }, 137 | "concat-map": { 138 | "version": "0.0.1", 139 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 140 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 141 | }, 142 | "cssom": { 143 | "version": "0.5.0", 144 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", 145 | "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" 146 | }, 147 | "cssstyle": { 148 | "version": "2.3.0", 149 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", 150 | "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", 151 | "requires": { 152 | "cssom": "~0.3.6" 153 | }, 154 | "dependencies": { 155 | "cssom": { 156 | "version": "0.3.8", 157 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", 158 | "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" 159 | } 160 | } 161 | }, 162 | "data-urls": { 163 | "version": "3.0.1", 164 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.1.tgz", 165 | "integrity": "sha512-Ds554NeT5Gennfoo9KN50Vh6tpgtvYEwraYjejXnyTpu1C7oXKxdFk75REooENHE8ndTVOJuv+BEs4/J/xcozw==", 166 | "requires": { 167 | "abab": "^2.0.3", 168 | "whatwg-mimetype": "^3.0.0", 169 | "whatwg-url": "^10.0.0" 170 | } 171 | }, 172 | "debug": { 173 | "version": "4.3.1", 174 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 175 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 176 | "requires": { 177 | "ms": "2.1.2" 178 | } 179 | }, 180 | "decimal.js": { 181 | "version": "10.3.1", 182 | "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", 183 | "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" 184 | }, 185 | "deep-is": { 186 | "version": "0.1.4", 187 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 188 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" 189 | }, 190 | "delayed-stream": { 191 | "version": "1.0.0", 192 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 193 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 194 | }, 195 | "devtools-protocol": { 196 | "version": "0.0.901419", 197 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz", 198 | "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==" 199 | }, 200 | "domexception": { 201 | "version": "4.0.0", 202 | "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", 203 | "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", 204 | "requires": { 205 | "webidl-conversions": "^7.0.0" 206 | } 207 | }, 208 | "end-of-stream": { 209 | "version": "1.4.4", 210 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 211 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 212 | "requires": { 213 | "once": "^1.4.0" 214 | } 215 | }, 216 | "escodegen": { 217 | "version": "2.0.0", 218 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", 219 | "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", 220 | "requires": { 221 | "esprima": "^4.0.1", 222 | "estraverse": "^5.2.0", 223 | "esutils": "^2.0.2", 224 | "optionator": "^0.8.1", 225 | "source-map": "~0.6.1" 226 | } 227 | }, 228 | "esprima": { 229 | "version": "4.0.1", 230 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 231 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 232 | }, 233 | "estraverse": { 234 | "version": "5.3.0", 235 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 236 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" 237 | }, 238 | "esutils": { 239 | "version": "2.0.3", 240 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 241 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" 242 | }, 243 | "extract-zip": { 244 | "version": "2.0.1", 245 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", 246 | "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", 247 | "requires": { 248 | "@types/yauzl": "^2.9.1", 249 | "debug": "^4.1.1", 250 | "get-stream": "^5.1.0", 251 | "yauzl": "^2.10.0" 252 | } 253 | }, 254 | "fast-levenshtein": { 255 | "version": "2.0.6", 256 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 257 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" 258 | }, 259 | "fd-slicer": { 260 | "version": "1.1.0", 261 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 262 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", 263 | "requires": { 264 | "pend": "~1.2.0" 265 | } 266 | }, 267 | "find-up": { 268 | "version": "4.1.0", 269 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 270 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 271 | "requires": { 272 | "locate-path": "^5.0.0", 273 | "path-exists": "^4.0.0" 274 | } 275 | }, 276 | "form-data": { 277 | "version": "4.0.0", 278 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 279 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 280 | "requires": { 281 | "asynckit": "^0.4.0", 282 | "combined-stream": "^1.0.8", 283 | "mime-types": "^2.1.12" 284 | } 285 | }, 286 | "fs-constants": { 287 | "version": "1.0.0", 288 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 289 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 290 | }, 291 | "fs.realpath": { 292 | "version": "1.0.0", 293 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 294 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 295 | }, 296 | "get-stream": { 297 | "version": "5.2.0", 298 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", 299 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", 300 | "requires": { 301 | "pump": "^3.0.0" 302 | } 303 | }, 304 | "glob": { 305 | "version": "7.2.0", 306 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 307 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 308 | "requires": { 309 | "fs.realpath": "^1.0.0", 310 | "inflight": "^1.0.4", 311 | "inherits": "2", 312 | "minimatch": "^3.0.4", 313 | "once": "^1.3.0", 314 | "path-is-absolute": "^1.0.0" 315 | } 316 | }, 317 | "html-encoding-sniffer": { 318 | "version": "3.0.0", 319 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", 320 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", 321 | "requires": { 322 | "whatwg-encoding": "^2.0.0" 323 | } 324 | }, 325 | "http-proxy-agent": { 326 | "version": "5.0.0", 327 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", 328 | "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", 329 | "requires": { 330 | "@tootallnate/once": "2", 331 | "agent-base": "6", 332 | "debug": "4" 333 | } 334 | }, 335 | "https-proxy-agent": { 336 | "version": "5.0.0", 337 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 338 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 339 | "requires": { 340 | "agent-base": "6", 341 | "debug": "4" 342 | } 343 | }, 344 | "iconv-lite": { 345 | "version": "0.6.3", 346 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 347 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 348 | "requires": { 349 | "safer-buffer": ">= 2.1.2 < 3.0.0" 350 | } 351 | }, 352 | "ieee754": { 353 | "version": "1.2.1", 354 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 355 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 356 | }, 357 | "inflight": { 358 | "version": "1.0.6", 359 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 360 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 361 | "requires": { 362 | "once": "^1.3.0", 363 | "wrappy": "1" 364 | } 365 | }, 366 | "inherits": { 367 | "version": "2.0.4", 368 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 369 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 370 | }, 371 | "is-potential-custom-element-name": { 372 | "version": "1.0.1", 373 | "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", 374 | "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" 375 | }, 376 | "jsdom": { 377 | "version": "19.0.0", 378 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", 379 | "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", 380 | "requires": { 381 | "abab": "^2.0.5", 382 | "acorn": "^8.5.0", 383 | "acorn-globals": "^6.0.0", 384 | "cssom": "^0.5.0", 385 | "cssstyle": "^2.3.0", 386 | "data-urls": "^3.0.1", 387 | "decimal.js": "^10.3.1", 388 | "domexception": "^4.0.0", 389 | "escodegen": "^2.0.0", 390 | "form-data": "^4.0.0", 391 | "html-encoding-sniffer": "^3.0.0", 392 | "http-proxy-agent": "^5.0.0", 393 | "https-proxy-agent": "^5.0.0", 394 | "is-potential-custom-element-name": "^1.0.1", 395 | "nwsapi": "^2.2.0", 396 | "parse5": "6.0.1", 397 | "saxes": "^5.0.1", 398 | "symbol-tree": "^3.2.4", 399 | "tough-cookie": "^4.0.0", 400 | "w3c-hr-time": "^1.0.2", 401 | "w3c-xmlserializer": "^3.0.0", 402 | "webidl-conversions": "^7.0.0", 403 | "whatwg-encoding": "^2.0.0", 404 | "whatwg-mimetype": "^3.0.0", 405 | "whatwg-url": "^10.0.0", 406 | "ws": "^8.2.3", 407 | "xml-name-validator": "^4.0.0" 408 | }, 409 | "dependencies": { 410 | "ws": { 411 | "version": "8.5.0", 412 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", 413 | "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==" 414 | } 415 | } 416 | }, 417 | "levn": { 418 | "version": "0.3.0", 419 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 420 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 421 | "requires": { 422 | "prelude-ls": "~1.1.2", 423 | "type-check": "~0.3.2" 424 | } 425 | }, 426 | "locate-path": { 427 | "version": "5.0.0", 428 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 429 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 430 | "requires": { 431 | "p-locate": "^4.1.0" 432 | } 433 | }, 434 | "mime-db": { 435 | "version": "1.51.0", 436 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 437 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" 438 | }, 439 | "mime-types": { 440 | "version": "2.1.34", 441 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 442 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 443 | "requires": { 444 | "mime-db": "1.51.0" 445 | } 446 | }, 447 | "minimatch": { 448 | "version": "3.0.4", 449 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 450 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 451 | "requires": { 452 | "brace-expansion": "^1.1.7" 453 | } 454 | }, 455 | "minimist": { 456 | "version": "1.2.5", 457 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 458 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 459 | }, 460 | "mkdirp": { 461 | "version": "0.5.5", 462 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 463 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 464 | "requires": { 465 | "minimist": "^1.2.5" 466 | } 467 | }, 468 | "ms": { 469 | "version": "2.1.2", 470 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 471 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 472 | }, 473 | "node-fetch": { 474 | "version": "2.6.1", 475 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 476 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 477 | }, 478 | "nwsapi": { 479 | "version": "2.2.0", 480 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", 481 | "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" 482 | }, 483 | "once": { 484 | "version": "1.4.0", 485 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 486 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 487 | "requires": { 488 | "wrappy": "1" 489 | } 490 | }, 491 | "optionator": { 492 | "version": "0.8.3", 493 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", 494 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", 495 | "requires": { 496 | "deep-is": "~0.1.3", 497 | "fast-levenshtein": "~2.0.6", 498 | "levn": "~0.3.0", 499 | "prelude-ls": "~1.1.2", 500 | "type-check": "~0.3.2", 501 | "word-wrap": "~1.2.3" 502 | } 503 | }, 504 | "p-limit": { 505 | "version": "2.3.0", 506 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 507 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 508 | "requires": { 509 | "p-try": "^2.0.0" 510 | } 511 | }, 512 | "p-locate": { 513 | "version": "4.1.0", 514 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 515 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 516 | "requires": { 517 | "p-limit": "^2.2.0" 518 | } 519 | }, 520 | "p-try": { 521 | "version": "2.2.0", 522 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 523 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 524 | }, 525 | "parse5": { 526 | "version": "6.0.1", 527 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", 528 | "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" 529 | }, 530 | "path-exists": { 531 | "version": "4.0.0", 532 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 533 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" 534 | }, 535 | "path-is-absolute": { 536 | "version": "1.0.1", 537 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 538 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 539 | }, 540 | "pend": { 541 | "version": "1.2.0", 542 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 543 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" 544 | }, 545 | "pkg-dir": { 546 | "version": "4.2.0", 547 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 548 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 549 | "requires": { 550 | "find-up": "^4.0.0" 551 | } 552 | }, 553 | "prelude-ls": { 554 | "version": "1.1.2", 555 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 556 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" 557 | }, 558 | "progress": { 559 | "version": "2.0.1", 560 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", 561 | "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==" 562 | }, 563 | "proxy-from-env": { 564 | "version": "1.1.0", 565 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 566 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 567 | }, 568 | "psl": { 569 | "version": "1.8.0", 570 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 571 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" 572 | }, 573 | "pump": { 574 | "version": "3.0.0", 575 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 576 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 577 | "requires": { 578 | "end-of-stream": "^1.1.0", 579 | "once": "^1.3.1" 580 | } 581 | }, 582 | "punycode": { 583 | "version": "2.1.1", 584 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 585 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 586 | }, 587 | "puppeteer": { 588 | "version": "10.4.0", 589 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-10.4.0.tgz", 590 | "integrity": "sha512-2cP8mBoqnu5gzAVpbZ0fRaobBWZM8GEUF4I1F6WbgHrKV/rz7SX8PG2wMymZgD0wo0UBlg2FBPNxlF/xlqW6+w==", 591 | "requires": { 592 | "debug": "4.3.1", 593 | "devtools-protocol": "0.0.901419", 594 | "extract-zip": "2.0.1", 595 | "https-proxy-agent": "5.0.0", 596 | "node-fetch": "2.6.1", 597 | "pkg-dir": "4.2.0", 598 | "progress": "2.0.1", 599 | "proxy-from-env": "1.1.0", 600 | "rimraf": "3.0.2", 601 | "tar-fs": "2.0.0", 602 | "unbzip2-stream": "1.3.3", 603 | "ws": "7.4.6" 604 | } 605 | }, 606 | "readable-stream": { 607 | "version": "3.6.0", 608 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 609 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 610 | "requires": { 611 | "inherits": "^2.0.3", 612 | "string_decoder": "^1.1.1", 613 | "util-deprecate": "^1.0.1" 614 | } 615 | }, 616 | "rimraf": { 617 | "version": "3.0.2", 618 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 619 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 620 | "requires": { 621 | "glob": "^7.1.3" 622 | } 623 | }, 624 | "safe-buffer": { 625 | "version": "5.2.1", 626 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 627 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 628 | }, 629 | "safer-buffer": { 630 | "version": "2.1.2", 631 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 632 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 633 | }, 634 | "sax": { 635 | "version": "1.2.4", 636 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 637 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 638 | }, 639 | "saxes": { 640 | "version": "5.0.1", 641 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", 642 | "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", 643 | "requires": { 644 | "xmlchars": "^2.2.0" 645 | } 646 | }, 647 | "source-map": { 648 | "version": "0.6.1", 649 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 650 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 651 | "optional": true 652 | }, 653 | "string_decoder": { 654 | "version": "1.3.0", 655 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 656 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 657 | "requires": { 658 | "safe-buffer": "~5.2.0" 659 | } 660 | }, 661 | "svg-dimensions": { 662 | "version": "1.0.2", 663 | "resolved": "https://registry.npmjs.org/svg-dimensions/-/svg-dimensions-1.0.2.tgz", 664 | "integrity": "sha1-s1oDW91X/tJxaJMbmz1bC7VebtI=", 665 | "requires": { 666 | "xml2js": "^0.4.5" 667 | } 668 | }, 669 | "symbol-tree": { 670 | "version": "3.2.4", 671 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", 672 | "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" 673 | }, 674 | "tar-fs": { 675 | "version": "2.0.0", 676 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", 677 | "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", 678 | "requires": { 679 | "chownr": "^1.1.1", 680 | "mkdirp": "^0.5.1", 681 | "pump": "^3.0.0", 682 | "tar-stream": "^2.0.0" 683 | } 684 | }, 685 | "tar-stream": { 686 | "version": "2.2.0", 687 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 688 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 689 | "requires": { 690 | "bl": "^4.0.3", 691 | "end-of-stream": "^1.4.1", 692 | "fs-constants": "^1.0.0", 693 | "inherits": "^2.0.3", 694 | "readable-stream": "^3.1.1" 695 | } 696 | }, 697 | "through": { 698 | "version": "2.3.8", 699 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 700 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 701 | }, 702 | "tough-cookie": { 703 | "version": "4.0.0", 704 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", 705 | "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", 706 | "requires": { 707 | "psl": "^1.1.33", 708 | "punycode": "^2.1.1", 709 | "universalify": "^0.1.2" 710 | } 711 | }, 712 | "tr46": { 713 | "version": "3.0.0", 714 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", 715 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", 716 | "requires": { 717 | "punycode": "^2.1.1" 718 | } 719 | }, 720 | "type-check": { 721 | "version": "0.3.2", 722 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 723 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 724 | "requires": { 725 | "prelude-ls": "~1.1.2" 726 | } 727 | }, 728 | "unbzip2-stream": { 729 | "version": "1.3.3", 730 | "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", 731 | "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", 732 | "requires": { 733 | "buffer": "^5.2.1", 734 | "through": "^2.3.8" 735 | } 736 | }, 737 | "universalify": { 738 | "version": "0.1.2", 739 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 740 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" 741 | }, 742 | "util-deprecate": { 743 | "version": "1.0.2", 744 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 745 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 746 | }, 747 | "w3c-hr-time": { 748 | "version": "1.0.2", 749 | "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", 750 | "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", 751 | "requires": { 752 | "browser-process-hrtime": "^1.0.0" 753 | } 754 | }, 755 | "w3c-xmlserializer": { 756 | "version": "3.0.0", 757 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", 758 | "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", 759 | "requires": { 760 | "xml-name-validator": "^4.0.0" 761 | } 762 | }, 763 | "webidl-conversions": { 764 | "version": "7.0.0", 765 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 766 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" 767 | }, 768 | "whatwg-encoding": { 769 | "version": "2.0.0", 770 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", 771 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", 772 | "requires": { 773 | "iconv-lite": "0.6.3" 774 | } 775 | }, 776 | "whatwg-mimetype": { 777 | "version": "3.0.0", 778 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", 779 | "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==" 780 | }, 781 | "whatwg-url": { 782 | "version": "10.0.0", 783 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", 784 | "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", 785 | "requires": { 786 | "tr46": "^3.0.0", 787 | "webidl-conversions": "^7.0.0" 788 | } 789 | }, 790 | "word-wrap": { 791 | "version": "1.2.3", 792 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 793 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" 794 | }, 795 | "wrappy": { 796 | "version": "1.0.2", 797 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 798 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 799 | }, 800 | "ws": { 801 | "version": "7.4.6", 802 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", 803 | "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" 804 | }, 805 | "xml-name-validator": { 806 | "version": "4.0.0", 807 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", 808 | "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==" 809 | }, 810 | "xml2js": { 811 | "version": "0.4.23", 812 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", 813 | "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", 814 | "requires": { 815 | "sax": ">=0.6.0", 816 | "xmlbuilder": "~11.0.0" 817 | } 818 | }, 819 | "xmlbuilder": { 820 | "version": "11.0.1", 821 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", 822 | "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" 823 | }, 824 | "xmlchars": { 825 | "version": "2.2.0", 826 | "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", 827 | "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" 828 | }, 829 | "yaml": { 830 | "version": "1.10.2", 831 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 832 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" 833 | }, 834 | "yauzl": { 835 | "version": "2.10.0", 836 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 837 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", 838 | "requires": { 839 | "buffer-crc32": "~0.2.3", 840 | "fd-slicer": "~1.1.0" 841 | } 842 | } 843 | } 844 | } 845 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scriptimate", 3 | "version": "1.2.29", 4 | "description": "Open-source SVG animation tool", 5 | "main": "bin/index.js", 6 | "keywords": [ 7 | "svg animation tool", 8 | "SVG", 9 | "animation", 10 | "vector", 11 | "figma", 12 | "design", 13 | "motion design", 14 | "webm animation", 15 | "mp4 animation" 16 | ], 17 | "scripts": { 18 | "scriptimate": "node --max_old_space_size=7096 іndex.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/devforth/scriptimate.git" 23 | }, 24 | "author": "Ivan Borshchov", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/devforth/scriptimate/issues" 28 | }, 29 | "bin": { 30 | "scriptimate": "bin/scriptimate.js" 31 | }, 32 | "homepage": "https://tracklify.com/blog/scriptimate-an-open-source-tool-to-create-svg-animations-in-a-coding-way/", 33 | "dependencies": { 34 | "argparse": "^2.0.1", 35 | "jsdom": "^19.0.0", 36 | "puppeteer": "^10.4.0", 37 | "svg-dimensions": "^1.0.2", 38 | "yaml": "^1.10.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | > 🙏🙏🙏 Scream for help to Ukraine 🇺🇦 3 | > 4 | > This alert created by company based in Ukraine from shelter. 5 | > 6 | > 24 February 2022, Russia started bombing cities with peacefully civilized population in whole Ukraine. Breaking the laws of war. Their bombs has been killing children and adults. This deserves Hague court. 7 | > - 🏠 If you are from Russia, please stop your government by any means including protests, don't trust local media, they are bribed by the government. They always was. I am sure you already feel lie by unexplainable crazy things in your country caused by world sanctions. 8 | > - 💣 Please spread the information about bombing of Ukraine in all social way you could. Russia treacherously broke into the territory of a sovereign state. Do not trust to anything from Russian media, most likely it will be bullshit 9 | > - 💼 If you have any kind of business cooperation with Russia, please block it now and keep most of money on your side, this is the only possible ethical decision 10 | > - ☢️ Ask your government to stop Russia from spreading invasion in any way. Russia is nuclear threat to the whole world. You think it is not possible? We thought that bombing of independent country with population of 44.13 million is also not possible. 11 | 12 | 13 | # Scriptimate – OpenSource svg animation tool 14 | 15 | Create webm/mp4/gif videos by animating qualitative SVG files (e.g. exported from Figma or any other vector image editor). 16 | 17 | > ⚠️ for now Supported Node version is 16+ (Probably 14, but 12 is not working) 18 | 19 | Works on on 🪟Windows WSL 2 🐧Ubuntu 🍏Mac 20 | 21 | 22 | ## Typical example 23 | 24 | Create text file `demo.smte`: 25 | 26 | ``` 27 | set_frame_size 600 300 28 | place boomerang 0 100 29 | animate_1000 move boomerang 400 - && rotate boomerang 720 && scale boomerang 2 30 | ``` 31 | 32 | Place `boomerang.svg` into `src/` folder. E.g. this one: [boomerang.svg](./examples/src/boomerang.svg) 33 | 34 | Execute scriptimate to compile video: 35 | 36 | ``` 37 | npx scriptimate@latest -i demo.smte -f gif 38 | ``` 39 | 40 | You will get: 41 | 42 | ![](./examples/3_parallel_animations.gif) 43 | 44 | [Read guide here](https://tracklify.com/blog/scriptimate-an-open-source-tool-to-create-svg-animations-in-a-coding-way/) 45 | 46 | ## Prerequirements 47 | 48 | You need to have next packages on your system (works for Ubuntu and [Windows WSL2](https://hinty.io/devforth/how-to-install-wsl-2-best-way-to-run-real-linux-on-windows/)): 49 | 50 | ``` 51 | sudo apt install libnss3-dev libatk-bridge2.0-0 libcups2 libgtk-3-0 libgbm-dev ffmpeg 52 | ``` 53 | 54 | ( All apart `ffmpeg` required to run pupeeter which is used to generate high-qaulity frames, some taken from here https://gist.github.com/winuxue/cfef08e2f5fe9dfc16a1d67a4ad38a01 ) 55 | 56 | Required version of `ffmpeg >=4.x` (Will be installed automatically in Ubuntu 20.04+, when in 18.04 it will be 3.x, which is not compatible) 57 | 58 | If you are using custom changable texts, please make sure you have all fonts that you use in styles installed into OS, e.g. on Ubuntu: 59 | 60 | ``` 61 | sudo apt install fonts-roboto fonts-open-sans 62 | ``` 63 | 64 | ## Getting started 65 | 66 | If you want to start using scriptimate, please read carefully these 2 posts: 67 | 68 | * [Getting started with Scriptimate on Tracklify Blog](https://tracklify.com/blog/scriptimate-an-open-source-tool-to-create-svg-animations-in-a-coding-way/) 69 | * [Figma exort hints on hinty.io](https://hinty.io/ivictbor/prepare-figma-exports-for-scriptimate/) 70 | 71 | 72 | ## How to run examples from this repo 73 | 74 | 1. Pull the repo 75 | 2. `cd example` 76 | 3. Execute `npx scriptimate -i 1_helloworld.smte` 77 | 78 | 79 | 80 | # .smte syntax 81 | 82 | Please start with reading [scriptimate getting started blog post](https://tracklify.com/blog/scriptimate-an-open-source-tool-to-create-svg-animations-in-a-coding-way/). 83 | This reference could be used for advanced use-cases. 84 | 85 | Supported commands: 86 | 87 | ## place: Place part 88 | 89 | Part is svg file which is basic part of animation. 90 | Filename should slug-compatible (latin, no spaces, etc). 91 | Also this filename is used as id of part anywhere 92 | 93 | ``` 94 | place <[optional] opcaity: 0-1> <[optional] scale> <[optional] whichBoxHoleToAdd> <[optional] dashOffset> 95 | ``` 96 | 97 | Example: 98 | Place cursor.svg at 400 120: 99 | ``` 100 | place cursor 400 120 101 | ``` 102 | 103 | 104 | 105 | ## place_div: place a div 106 | 107 | Usecases: 108 | * dynamically change content of div via schedule_eval 109 | * appear some content on page 110 | 111 | ``` 112 | place_div <[optional] opcaity 0-1> <[optional] content of div> <[optional] whichBoxHoleToAdd> 113 | ``` 114 | 115 | ## place_boxhole: place boxhole 116 | 117 | Boxhole is a rectangle zone on page which relatively places parts in it with hidden overflow (with ability to hide out of the box and then slide to zone) 118 | 119 | ``` 120 | place_boxhole 121 | ``` 122 | 123 | ## addstyle: add style to part or div 124 | 125 | ``` 126 | addstyle 127 | ``` 128 | 129 | ## moveToTop: move some part to top over all elements 130 | 131 | ``` 132 | moveToTop 133 | ``` 134 | 135 | Example: 136 | 137 | ``` 138 | moveToTop cursor 139 | ``` 140 | 141 | ## schedule_eval: schedule running of JavaScript 142 | 143 | ``` 144 | schedule_eval 145 | ``` 146 | 147 | Example: 148 | 149 | ``` 150 | # schedule_eval task_time 10 incr('task_time_secs', 2); if (+get('task_time_secs') >= 60) { incr('task_time_mins'); set('task_time_secs', 0)} 151 | ``` 152 | 153 | ## animate_xxx: apply animators during xxx milliseconds 154 | 155 | You can pass one or multiple animators(via '&&'). 156 | If multiple animators are specified they will be executed in parallel 157 | 158 | 159 | ``` 160 | animate_ <[optional] mode> [args of animator 1] && <[optional] mode> [args of animator 2] and so on 161 | ``` 162 | 163 | `mode`: could be one of: 164 | * linear (default) 165 | * easein 166 | * easeout 167 | * easeinout 168 | 169 | Available animators: 170 | 171 | ### pause : do nothing, just wait (sleep) 172 | 173 | ``` 174 | pause 175 | ``` 176 | 177 | Example: sleep for 3.5 seconds: 178 | 179 | ``` 180 | animate_3500 pause 181 | ``` 182 | 183 | ### move: move part 184 | 185 | ``` 186 | move <[optional] mode> 187 | ``` 188 | 189 | ### scale: scale part 190 | 191 | ``` 192 | scale <[optional] mode> 193 | ``` 194 | 195 | `` is css transform-origin, could be e.g. 196 | 197 | * center (default) 198 | * top left 199 | * bottom right 200 | * etc 201 | 202 | ### rotate: rotate part 203 | 204 | ``` 205 | rotate <[optional] mode> 206 | ``` 207 | 208 | ### opacity: change part opacity 209 | 210 | ``` 211 | opacity <[optional] mode> 212 | ``` 213 | 214 | ### dashoffset: change dash offset 215 | 216 | Used to draw strokes. Added by [@maxm123](https://github.com/maxm123) 217 | 218 | 219 | ``` 220 | dashoffset <[optional] mode> 221 | ``` 222 | 223 | ### resize_div 224 | 225 | Could be used to create animated bars. 226 | This animator only changes width and height css attributes, so div should have display:flex or something added via `addstyle`. 227 | ``` 228 | resize_div 229 | ``` 230 | 231 | 232 | 233 | # define_group: define group of commands 234 | 235 | Could be used to define several parallel complex scenarious. 236 | 237 | ``` 238 | define_group : 239 | command args 240 | command args 241 | etc 242 | 243 | ``` 244 | 245 | Then `run_groups_together` should be used to start them 246 | 247 | ``` 248 | run_groups_together etc. 249 | ``` 250 | 251 | Example: 252 | 253 | ``` 254 | define_group scenario1: 255 | animate_1000 move easein boomerang1 270 - 256 | animate_2000 move easeout boomerang1 $frameW-$boomerang1__WIDTH $frameH-$boomerang1__HEIGHT 257 | 258 | 259 | define_group scenario2: 260 | animate_1000 move boomerang2 250 - 261 | animate_1000 pause 262 | animate_1000 move boomerang2 $frameW-$boomerang1__WIDTH 0 263 | 264 | define_group rotator: 265 | animate_3000 rotate boomerang1 360*4 && rotate boomerang2 360*5 266 | 267 | run_groups_together scenario1 scenario2 rotator 268 | ``` 269 | 270 | 271 | # Constants 272 | 273 | Anywhere in smte you can define a constant with using: 274 | 275 | ``` 276 | const = 277 | ``` 278 | 279 | Example: 280 | 281 | ``` 282 | const $PositionX=600 $underLocation=300 283 | ``` 284 | 285 | ## Built-in constants 286 | 287 | When part is added there are internal constants 288 | 289 | ``` 290 | $__WIDTH # width of SVG part in px 291 | $__HEIGHT # height of SVG part in px 292 | $__LEFT # current left coordinate of SVG part in px 293 | $__TOP # current top coordinate of SVG part in px 294 | ``` 295 | They return dimensions of SVG image. 296 | 297 | Example. Place cake.svg and plate.svg directly under it: 298 | 299 | ``` 300 | place cake 0 0 301 | place plate 0 $cake__HEIGHT 302 | ``` 303 | 304 | 305 | ## Advanced ussage 306 | 307 | Under the hood next commands are used: 308 | 309 | ``` 310 | ffmpeg -framerate 25/1 -i frames/%07d.jpg -c:v libx264 -r 25 out.mp4 -y 311 | ``` 312 | 313 | Or for webm: 314 | 315 | ``` 316 | ffmpeg -framerate 25/1 -i frames/%07d.jpg -c:v libvpx-vp9 -b:v 2M -r 25 out.webm -y 317 | ``` 318 | 319 | After generation phace we frames folder will be persisted so feel free to change ffmpeg command in any way you want to produce some exotic format. 320 | 321 | 322 | 323 | 324 | ## CLI reference 325 | 326 | 327 | After installing just use: 328 | 329 | ``` 330 | scriptimate -h 331 | ``` 332 | 333 | To show all available options. 334 | 335 | ``` 336 | usage: scriptimate.js [-h] [-v] [-f FORMAT] [-i INPUT] [-fn FILENAME] [-t THREADS] [-fs FROMSECOND] [-d DEBUGHTML] [-bd BASEDIR] 337 | [-fps FPS] [-if INTERMEDIATEFORMAT] [-ijq INTERMEDIATEJPEGQUALITY] [-nc NOCACHE] 338 | 339 | Scriptimate v1.2.18 340 | 341 | optional arguments: 342 | -h, --help show this help message and exit 343 | -v, --version show program's version number and exit 344 | -f FORMAT, --format FORMAT 345 | output file format, or multiple via comma: "webm,mp4". Available formats: mov, mp4, gif, webm, default 346 | is mp4 347 | -i INPUT, --input INPUT 348 | Input .smte script file 349 | -fn FILENAME, --filename FILENAME 350 | output filename 351 | -t THREADS, --threads THREADS 352 | Threads count used during compiling, defaults to 4 353 | -fs FROMSECOND, --fromsecond FROMSECOND 354 | Start from defined second (could be used to debug animation faster, also you can use "exis" keyword in 355 | smte script) 356 | -d DEBUGHTML, --debughtml DEBUGHTML 357 | Create HTML files near image to debug 358 | -bd BASEDIR, --basedir BASEDIR 359 | Input directory (folder where src subfolder and .smte file is located) 360 | -fps FPS, --fps FPS FPS 361 | -if INTERMEDIATEFORMAT, --intermediateFormat INTERMEDIATEFORMAT 362 | Screenshots format used to compile video png|jpeg, defaults to png 363 | -ijq INTERMEDIATEJPEGQUALITY, --intermediateJpegQuality INTERMEDIATEJPEGQUALITY 364 | JPEG quality 0.0 - 1.0, defaults to 1 365 | -nc NOCACHE, --nocache NOCACHE 366 | Don't use screenshots cache (but still generate it), for scriptimate develeopmnt 367 | ``` 368 | 369 | # Build performance 370 | 371 | 🪧 Scriptimate uses `/tmp` to store build cache, so to improve build speed even more, make sure `/tmp` is mounted on RAM in `/etc/fstab` 372 | 373 | ``` 374 | tmpfs /tmp tmpfs nosuid,nodev,noatime 0 0 375 | ``` 376 | 377 | 378 | > ⚠️ If you made changes out of project sources (e.g. updated sytem font and re-built video), and see there are no updates in results, please use no cache parameter (`-nc 1`) 379 | 380 | # Development 381 | 382 | Just do: 383 | 384 | ``` 385 | npm ci 386 | cd examples 387 | node ../bin/scriptimate.js -i 7_dashoffset.smte -f gif 388 | ``` 389 | 390 | ## Known bugs and improvements 391 | 392 | * HTML Pages gen xx% shows more then 100% if run_groups_together is used. Only visual status bug, compiled video is correct 393 | * HTML pages generation process is not cached and not parallelized. 394 | * boxhole should be removed, instead we should specify div id and count relational coordinates from div 395 | --------------------------------------------------------------------------------