From 0e21dc1c429ef225ef0a482ccd976c5fd01d6e0b Mon Sep 17 00:00:00 2001 From: Chris Johnson <49479599+workeffortwaste@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:37:56 +0000 Subject: [PATCH] Add post processing --- README.md | 34 +++++++++++++++++++++++++++++++++- cli.js | 1 + index.js | 46 +++++++++++++++++++++++++++++++++++++++++++--- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 42f5058..1aee27d 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ What makes `gsap-video-export` different from other solutions is rather than sim - [gsap-video-export](#gsap-video-export) - [Contents](#contents) - [What's New](#whats-new) + - [2.1.0 🆕](#210-) - [2.0.3 🆕](#203-) - [2.0.2 🆕](#202-) - [2.0.1 🆕](#201-) @@ -32,6 +33,7 @@ What makes `gsap-video-export` different from other solutions is rather than sim - [Lossless\* Export](#lossless-export) - [Timeweb Frame Advancement 🆕](#timeweb-frame-advancement-) - [Alpha Transparency 🆕](#alpha-transparency-) + - [Post Processing 🆕](#post-processing-) - [Advanced](#advanced) - [Cookies 🆕](#cookies-) - [Chrome 🆕](#chrome-) @@ -44,6 +46,10 @@ What makes `gsap-video-export` different from other solutions is rather than sim ## What's New +### 2.1.0 🆕 + +* Added a `post-process` option, allowing a script to modify the image buffer before it's saved to temp storage. + ### 2.0.3 🆕 * Added `prepare-page` and `prepare-frame` options, allowing a script to be run once at page load or before each frame. @@ -141,9 +147,10 @@ All additional options are available when used as a module or via the CLI. | `--help` | | General | Show help | `boolean` | | | `--version` | | General | Show version number | `boolean` | | | `-q`, `--verbose` | `verbose` | General | Verbose output. | `boolean` | `true` | -| `-i`, `--info` | `info` | General | Information only. | `boolean` | `false` | +| `-i`, `--info` | `info` | General | Information only. | `boolean` | `false` | | `-s`, `--prepare-page`, `--script` | `preparePage`, `script` | Browser | Custom script to run once on page load. | `string`, `function` | | | `--prepare-frame` | `prepareFrame` | Browser | Custom script to run before every frame. | `string`, `function` | | +| `--post-process` | `postProcess` | Browser | Custom script to modify the image buffer. | `string`, `function` | | | `-S`, `--selector` | `selector` | Browser | DOM selector of element to capture. | `string` | `"document"` | | `-t`, `--timeline` | `timeline` | Browser | GSAP timeline object. | `string` | `"gsap"` | | `-z`, `--scale` | `scale` | Browser | Scale factor for higher quality capture. | `number` | `1` | @@ -310,6 +317,31 @@ gsap-video-export https://codepen.io/defaced/pen/GRVbwNQ -S svg -v 1080x1080 -o ``` The important part of the command is `-o video.mov -p transparent -c prores_ks -C mov -- -E "'-pix_fmt yuva444p10le'"` which sets ffmpeg to use a video format that's compatible with transparency and tells `gsap-video-export` to respect transparent backgrounds. +### Post Processing 🆕 + +`gsap-video-export` now lets you modify the image buffer of each frame before it is saved to disk using the `post-processs` option. `post-process` supplies an image buffer to your function and expects you to return one. + +Here's an example that dithers each frame with a CGA palette. + +```javascript +import { videoExport } from 'gsap-video-export' +import DitherJS from 'ditherjs/server.js' + +const dither = new DitherJS({ + step: 6, + algorithm: 'diffusion' +}) + +await videoExport({ + url: 'https://codepen.io/cassie-codes/pen/eYzOBGq', + selector: 'svg', + scale: 2, + postProcess: async (buffer) => { return dither.dither(buffer) } +}) +``` + +https://github.com/user-attachments/assets/467c6a87-e3d1-459d-99be-928d8749026e + ## Advanced ### Cookies 🆕 diff --git a/cli.js b/cli.js index 6c02d09..8b7432d 100644 --- a/cli.js +++ b/cli.js @@ -50,6 +50,7 @@ const options = _yargs .usage('$0 ', 'Export GreenSock (GSAP) animation to video') .describe('s', '[browser] Custom script (Page)') .describe('prepare-frame', '[browser] Custom script (Frame)') + .describe('post-process', '[browser] Custom script (Post Process)') .describe('S', '[browser] DOM selector') .describe('t', '[browser] GSAP timeline object') .describe('z', '[browser] Scale factor') diff --git a/index.js b/index.js index b77008f..14406ad 100644 --- a/index.js +++ b/index.js @@ -311,7 +311,7 @@ const videoExport = async (options) => { log(padCenter('Browser', 'OK'), options.verbose) /* If a custom script is specified and exists */ - if (options.script) options.preparePage = options.script + if (options.script) options.preparePage = options.script if (options.preparePage) { let customScript @@ -529,8 +529,48 @@ const videoExport = async (options) => { /* Select the DOM element via the specified selector */ const el = options.selector === 'document' ? page : await page.$(options.selector) - /* Take a screenshot */ - await el.screenshot({ path: tmpobj.name + '/' + frameStep + '.png', omitBackground: options.color === 'transparent' }) + /* If we're not supplying a post processor script then we can just take a screenshot and save it */ + if (!options.postProcess) await el.screenshot({ path: tmpobj.name + '/' + frameStep + '.png', omitBackground: options.color === 'transparent' }) + + /* Otherwise we need to take a screenshot and then run the post processor script */ + if (options.postProcess) { + /* Take a screenshot */ + const screenshot = await el.screenshot({ omitBackground: options.color === 'transparent' }) + + /* If a custom frame script is specified and exists */ + let customScript + + if (typeof options.postProcess === 'function') { + customScript = options.postProcess + } else { + if (!fs.existsSync(options.postProcess)) { + await dirtyExit(browser, 'The specified script does not exist') + } + /* Load the script */ + customScript = fs.readFileSync(options.postProcess, 'utf8') + } + + const screenshotBuffer = Buffer.from(screenshot, 'base64') + + let updatedScreenshotBuffer + try { + if (typeof customScript === 'function') { + updatedScreenshotBuffer = await customScript(screenshotBuffer) + } else { + // eslint-disable-next-line no-new-func + updatedScreenshotBuffer = await (new Function('imageBuffer', customScript))(screenshotBuffer) + } + + /* Write the updated screenshot to the tmp directory */ + fs.writeFileSync(tmpobj.name + '/' + frameStep + '.png', updatedScreenshotBuffer) + } catch (err) { + if (options.cli) { + await cleanExit(browser) + } else { + await dirtyExit(browser, 'Unable to run the specified script: ' + err) + } + } + } /* Increment and update the CLI export progress bar */ if (options.verbose) b1.increment() diff --git a/package-lock.json b/package-lock.json index beb0b0d..2e74a96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gsap-video-export", - "version": "2.0.4", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gsap-video-export", - "version": "2.0.4", + "version": "2.1.0", "license": "MIT", "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", diff --git a/package.json b/package.json index 9b8aed4..33d5c53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gsap-video-export", - "version": "2.0.4", + "version": "2.1.0", "description": "Export GreenSock (GSAP) animation to video.", "main": "index.js", "type": "module",