Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/tungs/timesnap
Node.js program that takes screenshots at smooth intervals of web pages with JavaScript animations
https://github.com/tungs/timesnap
nodejs puppeteer
Last synced: 9 days ago
JSON representation
Node.js program that takes screenshots at smooth intervals of web pages with JavaScript animations
- Host: GitHub
- URL: https://github.com/tungs/timesnap
- Owner: tungs
- License: bsd-3-clause
- Created: 2018-02-19T18:25:44.000Z (almost 7 years ago)
- Default Branch: main
- Last Pushed: 2024-01-17T10:30:54.000Z (11 months ago)
- Last Synced: 2024-11-19T22:53:11.383Z (24 days ago)
- Topics: nodejs, puppeteer
- Language: JavaScript
- Homepage:
- Size: 234 KB
- Stars: 234
- Watchers: 9
- Forks: 57
- Open Issues: 28
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-puppeteer - timesnap - Take screenshots of web pages at smooth intervals. (Packages)
README
# timesnap
**timesnap** is a Node.js program that records screenshots of web pages that use JavaScript animations. It uses **[timeweb](https://github.com/tungs/timeweb)** and [puppeteer](https://github.com/GoogleChrome/puppeteer) to open a web page, overwrite its time-handling functions, and record snapshots at virtual times. For some web pages, this allows frames to be recorded slower than real time, while appearing smooth and consistent when recreated into a video.
You can use **timesnap** from the command line or as a Node.js library. It requires Node v8.9.0 or higher and npm.
To record screenshots and compile them into a video using only one command, see **[timecut](https://github.com/tungs/timecut)**. For using virtual time in browser, see **[timeweb](https://github.com/tungs/timeweb)**.
## # **timeweb** and **timesnap** Limitations
**timeweb** (and **timesnap** by extension) only overwrites JavaScript functions and video playback, so pages where changes occur via other means (e.g. through transitions/animations from CSS rules) will likely not render as intended.## Read Me Contents
* [From the Command Line](#from-cli)
* [Global Install and Use](#cli-global-install)
* [Local Install and Use](#cli-local-install)
* [Command Line *url*](#cli-url-use)
* [Command Line Examples](#cli-examples)
* [Command Line *options*](#cli-options)
* [From Node.js](#from-node)
* [Node Install](#node-install)
* [Node Examples](#node-examples)
* [Node API](#node-api)
* [timesnap Modes](#modes)
* [How it works](#how-it-works)## # From the Command Line
### # Global Install and Use
To install:Due to [an issue in puppeteer](https://github.com/GoogleChrome/puppeteer/issues/375) with permissions, timesnap is not supported for global installation for root. You can configure `npm` to install global packages for a specific user by following this guide: https://docs.npmjs.com/getting-started/fixing-npm-permissions#option-two-change-npms-default-directory
After configuring, run:
```
npm install -g timesnap
```To use:
```
timesnap "url" [options]
```### # Local Install and Use
To install:
```
cd /path/to/installation/directory
npm install timesnap
```To use:
```
node /path/to/installation/directory/node_modules/timesnap/cli.js "url" [options]
```*Alternatively*:
To install:
```
cd /path/to/installation/directory
git clone https://github.com/tungs/timesnap.git
cd timesnap
npm install
```To use:
```
node /path/to/installation/directory/timesnap/cli.js "url" [options]
```### # Command Line *url*
The url can be a web url (e.g. `https://github.com`) or a file path, with relative paths resolving in the current working directory. If no url is specified, defaults to `index.html`. Remember to enclose urls that contain special characters (like `#` and `&`) with quotes.### # Command Line Examples
**# Default behavior**:
```
timesnap
```
Opens `index.html` in the current working directory, sets the viewport to 800x600, captures at 60 frames per second for 5 virtual seconds, and saves the frames to `001.png` to `300.png` in the current working directory. The defaults may change in the future, so for long-term scripting, it's a good idea to explicitly pass these options, like in the following example.**# Setting viewport size, frames per second, duration, and output pattern**:
```
timesnap index.html --viewport="800,600" --fps=60 --duration=5 --output-pattern="%03d.png"
```
Equivalent to the current default `timesnap` invocation, but with explicit options. Opens `index.html` in the current working directory, sets the viewport to 800x600, captures at 60 frames per second for 5 virtual seconds, and saves the frames to `001.png` to `300.png` in the current working directory.**# Using a selector**:
```
timesnap drawing.html -S "canvas,svg" --output-pattern="frames/%03d.png"
```
Opens `drawing.html` in the current working directory, crops each frame to the bounding box of the first canvas or svg element, and captures frames using default settings (5 seconds @ 60fps), saving to `frames/001.png`... `frames/300.png` in the current working directory, making the directory `frames` if needed.**# Using offsets**:
```
timesnap "https://tungs.github.io/amuse/truchet-tiles/#autoplay=true&switchStyle=random" \
-S "#container" \
--left=20 --top=40 --right=6 --bottom=30 \
--duration=20 --output-directory=frames
```
Opens https://tungs.github.io/amuse/truchet-tiles/#autoplay=true&switchStyle=random (note the quotes in the url are necessary because of the `#` and `&`). Crops each frame to the `#container` element, with an additional crop of 20px, 40px, 6px, and 30px for the left, top, right, and bottom, respectively. Captures frames for 20 virtual seconds at 60fps to `frames/0001.png`... `frames/1200.png` in the current working directory, making the directory `frames` if needed.**# Piping**:
```
timesnap https://breathejs.org/examples/Drawing-US-Counties.html \
-V "1920,1080" -S "#draw-canvas" --fps=60 --duration=10 \
--round-to-even-width --round-to-even-height \
--output-stdout | ffmpeg -framerate 60 -i pipe:0 -y -pix_fmt yuv420p video.mp4
```
Opens https://breathejs.org/examples/Drawing-US-Counties.html, sets the viewport size to 1920x1080, crops each frame to the bounding box of `#draw-canvas`, records at 60 frames per second for ten virtual seconds, and pipes the output to `ffmpeg`, which reads in the data from stdin, encodes the frames using pixel format `yuv420p`, and saves the result as `video.mp4` in the current working directory. It does not save individual frames to disk. It uses the `--round-to-even-width` and `--round-to-even-height` options to ensure the dimensions of the frames are even numbers, which ffmpeg requires for certain encodings.### # Command Line *options*
* # Output Directory: `-o`, `--output-directory` *directory*
* Saves images to a *directory* (default `./`).
* # Output Pattern: `-O`, `--output-pattern` *pattern*
* Sets each file name according to a printf-style *pattern* (e.g. `image-%03d.png`).
* # Frame Rate: `-R`, `--fps` *frame rate*
* Frame rate (in frames per virtual second) of capture (default: `60`).
* # Duration: `-d`, `--duration` *seconds*
* Duration of capture, in *seconds* (default: `5`).
* # Frames: `--frames` *count*
* Number of frames to capture.
* # Selector: `-S`, `--selector` "*selector*"
* Crops each frame to the bounding box of the first item found by the [CSS *selector*][CSS selector].
* # Viewport: `-V`, `--viewport` *dimensions,otherOptions*
* Viewport dimensions, in pixels, followed by optional keys. For example, `800` (for width), or `"800,600"` (for width and height), or `"800,600,deviceScaleFactor=2"` for (width, height, and deviceScaleFactor). When running in Windows, quotes may be necessary for parsing commas. For a list of optional keys, see [`config.viewport`](#js-config-viewport).
* # Canvas Capture Mode: `--canvas-capture-mode` *\[format\]*
* Experimental. Captures images from canvas data instead of screenshots. See [canvas capture mode](#canvas-capture-mode). Can provide an optional image format (e.g. `png`), otherwise it uses the saved image's extension, or defaults to `png` if the format is not specified or supported. Can prefix the format with `immediate:` (e.g. `immediate:png`) to immediately capture pixel data after rendering, which is sometimes needed for some WebGL renderers. Specify the canvas [using the `--selector` option](#cli-options-selector), otherwise it defaults to the first canvas in the document.
* # Start: `-s`, `--start` *n seconds*
* Runs code for n virtual seconds before saving any frames (default: `0`).
* # X Offset: `-x`, `--x-offset` *pixels*
* X offset of capture, in pixels (default: `0`).
* # Y Offset: `-y`, `--y-offset` *pixels*
* Y offset of capture, in pixels (default: `0`).
* # Width: `-W`, `--width` *pixels*
* Width of capture, in pixels.
* # Height: `-H`, `--height` *pixels*
* Height of capture, in pixels.
* # Round to Even Width: `--round-to-even-width`
* Rounds width up to the nearest even number.
* # Round to Even Height: `--round-to-even-height`
* Rounds height up to the nearest even number.
* # Transparent Background: `--transparent-background`
* Allows background to be transparent if there is no background styling.
* # Left: `-l`, `--left` *pixels*
* Left edge of capture, in pixels. Equivalent to `--x-offset`.
* # Right: `-r`, `--right` *pixels*
* Right edge of capture, in pixels. Ignored if `width` is specified.
* # Top: `-t`, `--top` *pixels*
* Top edge of capture, in pixels. Equivalent to `--y-offset`.
* # Bottom: `-b`, `--bottom` *pixels*
* Bottom edge of capture, in pixels. Ignored if `height` is specified.
* # Unrandomize: `-u`, `--unrandomize` *\[seeds\]*
* Overwrites `Math.random` with a seeded pseudorandom number generator. Can provide optional seeds as up to four comma separated integers (e.g. `--unrandomize 2,3,5,7` or `--unrandomize 42`). If `seeds` is `random-seed` (i.e. `--unrandomize random-seed`), a random seed will be generated, displayed (if not in quiet mode), and used. If `seeds` is not provided, it uses the seeds `10,0,20,0`.
* # Executable Path: `--executable-path` *path*
* Uses the Chromium/Chrome instance at *path* for puppeteer.
* # Remote URL: `--remote-url` *path*
* URL of remote Chromium/Chrome instance to connect using *puppeteer.connect()*.
* # Puppeteer Launch Arguments: `-L`, `--launch-arguments` *arguments*
* Arguments to pass to Puppeteer/Chromium, enclosed in quotes. Example: `--launch-arguments="--single-process"`. A list of arguments can be found [here](https://peter.sh/experiments/chromium-command-line-switches).
* # No Headless: `--no-headless`
* Runs Chromium/Chrome in windowed mode.
* # Screenshot Type: `--screenshot-type` *type*
* Output image format for the screenshots. By default, the file extension is used to infer type, and failing that, `png` is used. `jpeg` is also available.
* # Screenshot Quality: `--screenshot-quality` *number*
* Quality level between 0 to 1 for lossy screenshots. Defaults to 0.92 when in [canvas capture mode](#cli-options-canvas-capture-mode) and 0.8 otherwise.
* # Start Delay: `--start-delay` *n seconds*
* Waits *n real seconds* after loading the page before starting the virtual timeline.
* # Quiet: `-q`, `--quiet`
* Suppresses console logging.
* # Output stdout: `--output-stdout`
* Outputs images to stdout. Useful for piping.
* # Stop Function Name: `--stop-function-name` *function name*
* Creates a function with *function name* that the client web page can call to stop capturing. For instance, `--stop-function-name=stopCapture` could be called in the client, via `stopCapture()`.
* # Version: `-v`, `--version`
* Displays version information. Immediately exits.
* # Help: `-h`, `--help`
* Displays command line options. Immediately exits.## # From Node.js
**timesnap** can also be included as a library inside Node.js programs.### # Node Install
```
npm install timesnap --save
```### # Node Examples
**# Basic Use:**
```node
const timesnap = require('timesnap');
timesnap({
url: 'https://tungs.github.io/amuse/truchet-tiles/#autoplay=true&switchStyle=random',
viewport: {
width: 800, // sets the viewport (window size) to 800x600
height: 600
},
selector: '#container', // crops each frame to the bounding box of '#container'
left: 20, top: 40, // further crops the left by 20px, and the top by 40px
right: 6, bottom: 30, // and the right by 6px, and the bottom by 30px
fps: 30, // saves 30 frames for each virtual second
duration: 20, // for 20 virtual seconds
outputDirectory: 'frames' // to frames/001.png... frames/600.png
// of the current working directory
}).then(function () {
console.log('Done!');
});
```**# Multiple pages:**
```node
const timesnap = require('timesnap');
var pages = [
{
url: 'https://tungs.github.io/amuse/truchet-tiles/#autoplay=true',
outputDirectory: 'truchet-tiles'
}, {
url: 'https://breathejs.org/examples/Drawing-US-Counties.html',
outputDirectory: 'counties'
}
];
(async () => {
for (let page of pages) {
await timesnap({
url: page.url,
outputDirectory: page.outputDirectory,
viewport: {
width: 800,
height: 600
},
duration: 20
});
}
})();
```### # Node API
The Node API is structured similarly to the command line options, but there are a few options for the Node API that are not accessible through the command line interface: [`config.logToStdErr`](#js-config-log-to-std-err), [`config.frameProcessor`](#js-config-frame-processor), [`config.navigatePageToURL`](#js-config-navigate-page-to-url), [`config.preparePage`](#js-config-prepare-page), [`config.preparePageForScreenshot`](#js-config-prepare-page-for-screenshot), [`config.logger`](#js-config-logger), and [`config.shouldSkipFrame`](#js-config-should-skip-frame).
**timesnap(config)**
* # `config` <[Object][]>
* # `url` <[string][]> The url to load. It can be a web url, like `https://github.com` or a file path, with relative paths resolving in the current working directory (default: `index.html`).
* # `outputDirectory` <[string][]> Saves images to a directory. Makes one if necessary.
* # `outputPattern` <[string][]> Sets each file name according to a printf-style pattern (e.g. `image-%03d.png`)
* # `fps` <[number][]> Frame rate, in frames per virtual second, of capture (default: `60`).
* # `duration` <[number][]> Duration of capture, in seconds (default: `5`).
* # `frames` <[number][]> Number of frames to capture. Overrides default fps or default duration.
* # `selector` <[string][]> Crops each frame to the bounding box of the first item found by the specified [CSS selector][].
* # `viewport` <[Object][]>
* # `width` <[number][]> Width of viewport, in pixels (default: `800`).
* # `height` <[number][]> Height of viewport, in pixels (default: `600`).
* # `deviceScaleFactor` <[number][]> Device scale factor (default: `1`). Note that the captured image resolution is multiplied by the device scale factor.
* # `isMobile` <[boolean][]> Specifies whether the `meta viewport` tag should be used (default: `false`).
* # `hasTouch` <[boolean][]> Specifies whether the viewport supports touch (default: `false`).
* # `isLandscape` <[boolean][]> Specifies whether the viewport is in landscape mode (default: `false`).
* # `canvasCaptureMode` <[boolean][] | [string][]>
* Experimental. Captures images from canvas data instead of screenshots. See [canvas capture mode](#canvas-capture-mode). Can provide an optional image format (e.g. `png`), otherwise it uses the saved image's extension, or defaults to `png` if the format is not specified or supported. Can prefix the format with `immediate:` (e.g. `immediate:png`) to immediately capture pixel data after rendering, which is sometimes needed for some WebGL renderers. Specify the canvas by [setting `config.selector`](#js-config-selector), otherwise it defaults to the first canvas in the document.
* # `start` <[number][]> Runs code for `config.start` virtual seconds before saving any frames (default: `0`).
* # `xOffset` <[number][]> X offset of capture, in pixels (default: `0`).
* # `yOffset` <[number][]> Y offset of capture, in pixels (default: `0`).
* # `width` <[number][]> Width of capture, in pixels.
* # `height` <[number][]> Height of capture, in pixels.
* # `transparentBackground` <[boolean][]> Allows background to be transparent if there is no background styling.
* # `roundToEvenWidth` <[boolean][]> Rounds capture width up to the nearest even number.
* # `roundToEvenHeight` <[boolean][]> Rounds capture height up to the nearest even number.
* # `left` <[number][]> Left edge of capture, in pixels. Equivalent to `config.xOffset`.
* # `right` <[number][]> Right edge of capture, in pixels. Ignored if `config.width` is specified.
* # `top` <[number][]> Top edge of capture, in pixels. Equivalent to `config.yOffset`.
* # `bottom` <[number][]> Bottom edge of capture, in pixels. Ignored if `config.height` is specified.
* # `unrandomize` <[boolean][] | [string][] | [number][] | [Array][]<[number][]>> Overwrites `Math.random` with a seeded pseudorandom number generator. If it is a number, an array of up to four numbers, or a string of up to four comma separated numbers, then those values are used as the initial seeds. If it is true, then the default seed is used. If it is the string 'random-seed', a random seed will be generated, displayed (if quiet mode is not enabled), and used.
* # `executablePath` <[string][]> Uses the Chromium/Chrome instance at `config.executablePath` for puppeteer.
* # `remoteUrl` <[string][]> URL of remote Chromium/Chrome instance to connect using `puppeteer.connect()`.
* # `launchArguments` <[Array][] <[string][]>> Extra arguments for Puppeteer/Chromium. Example: `['--single-process']`. A list of arguments can be found [here](https://peter.sh/experiments/chromium-command-line-switches).
* # `headless` <[boolean][]> Runs puppeteer in headless (nonwindowed) mode (default: `true`).
* # `screenshotType` <[string][]> Output image format for the screenshots. By default, the file extension is used to infer type, and failing that, `'png'` is used. `'jpeg'` is also available.
* # `screenshotQuality` <[number][]> Quality level between 0 to 1 for lossy screenshots. Defaults to 0.92 when in [canvas capture mode](#js-config-canvas-capture-mode) and 0.8 otherwise.
* # `startDelay` <[number][]> Waits `config.startDelay` real seconds after loading before starting (default: `0`).
* # `quiet` <[boolean][]> Suppresses console logging.
* # `logger` <[function][](...[Object][])> Replaces console logging with a particular function. The passed arguments are the same as those to `console.log` (in this case, usually one string).
* # `logToStdErr` <[boolean][]> Logs to stderr instead of stdout. Doesn't do anything if `config.quiet` is set to true.
* # `shouldSkipFrame` <[function][]([Object][])> A function that determines whether a current frame should be skipped for capturing. It should return `true` if the current frame should be skipped, `false` if not. It is passed the following object:
* # `frameCount` <[number][]> The current frame count, starting at 1.
* # `framesToCapture` <[number][]> The total number of frames to be captured.
* # `page` <[Page][]> the puppeteer page.
* # `stopFunctionName` <[string][]> *function name* that the client web page can call to stop capturing. For instance, `'stopCapture'` could be called in the client, via `stopCapture()`.
* # `frameProcessor` <[function][]([Buffer][], [number][], [number][])> A function that will be called after capturing each frame. If `config.outputDirectory` and `config.outputPattern` aren't specified, enabling this suppresses automatic file output. After capturing each frame, `config.frameProcessor` is called with three arguments, and if it returns a promise, capture will be paused until the promise resolves:
* `screenshotData` <[Buffer][]> A buffer of the screenshot data.
* `frameNumber` <[number][]> The current frame number (1 based).
* `totalFrames` <[number][]> The total number of frames.
* # `navigatePageToURL` <[function][]([Object][])> A function that navigates a puppeteer page to a URL, overriding the default navigation to a URL. The function should return a promise that resolves once the page is finished navigating. The function is passed the following object:
* # `page` <[Page][]> the puppeteer page
* # `url` <[string][]> the url to navigate to
* # `preparePage` <[function][]([Page][])> A setup function that will be called one time before taking screenshots. If it returns a promise, capture will be paused until the promise resolves.
* `page` <[Page][]> The puppeteer instance of the page being captured.
* # `preparePageForScreenshot` <[function][]([Page][], [number][], [number][])> A setup function that will be called before each screenshot. If it returns a promise, capture will be paused until the promise resolves.
* `page` <[Page][]> The puppeteer instance of the page being captured.
* `frameNumber` <[number][]> The current frame number (1 based).
* `totalFrames` <[number][]> The total number of frames.
* # returns: <[Promise][]> resolves after all the frames have been captured.## # **timesnap** Modes
**timesnap** can capture frames using one of two modes:
* # **Screenshot capture mode** (default) uses puppeteer's built-in API to take screenshots of Chromium/Chrome windows. It can capture most parts of a webpage (e.g. div, svg, canvas) as they are rendered on the webpage. It can crop images, round to even widths/heights, but it usually runs slower than canvas capture mode.
* # **Canvas capture mode** (experimental) directly copies data from a canvas element and is often faster than using screenshot capture mode. If the background of the canvas is transparent, it may show up as transparent or black depending on the captured image format. Configuration options that adjust the crop and round to an even width/height do not currently have an effect. To use this mode, [use the `--canvas-capture-mode` option from the command line](#cli-options-canvas-capture-mode) or [set `config.canvasCaptureMode` from Node.js](#js-config-canvas-capture-mode). Also specify the canvas using a css selector, [using the `--selector` option from the command line](#cli-options-selector) or [setting `config.selector` from Node.js](#js-config-selector), otherwise it uses the first canvas element.## # How it works
**timesnap** uses puppeteer's `page.evaluateOnNewDocument` feature to automatically overwrite a page's native time-handling JavaScript functions and objects (`new Date()`, `Date.now`, `performance.now`, `requestAnimationFrame`, `setTimeout`, `setInterval`, `cancelAnimationFrame`, `cancelTimeout`, and `cancelInterval`) to custom ones that use a virtual timeline, allowing for JavaScript computation to complete before taking a screenshot.This work was inspired by [a talk by Noah Veltman](https://github.com/veltman/d3-unconf), who described altering a document's `Date.now` and `performance.now` functions to refer to a virtual time and using `puppeteer` to change that virtual time and take snapshots.
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type
[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type
[function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
[CSS selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors
[Buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer
[Page]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page