Skip to content
forked from tungs/timesnap

Take screenshots of web pages at smooth intervals

License

Notifications You must be signed in to change notification settings

ymoussaba/timesnap

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

timesnap

timesnap is a Node.js program that records screenshots of web pages that use JavaScript animations. It uses 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 v6.4.0 or higher and npm.

To record screenshots and compile them into a video using only one command, see timecut.

# timesnap Limitations

timesnap only overwrites JavaScript functions, so pages where changes occur via other means (e.g. through video or transitions/animations from CSS rules) will likely not render as intended.

Read Me Contents

# From the Command Line

# Global Install and Use

To install:

Due to an issue in puppeteer 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/truchet-tiles-original/#autoplay=true&switchStyle=random" \ 
  -S "#container" \ 
  --left=20 --top=40 --right=6 --bottom=30 \
  --duration=20 --output-directory=frames

Opens https://tungs.github.io/truchet-tiles-original/#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.
  • # Output stdout: --output-stdout
    • Outputs images to stdout. Useful for piping.
  • # Viewport: -V, --viewport dimensions
    • Viewport dimensions, in pixels. For example, 800 (for width) or 800,600 (for width and height).
  • # 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.
  • # 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.
  • # 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:

const timesnap = require('timesnap');
timesnap({
  url: 'https://tungs.github.io/truchet-tiles-original/#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 (Requires Node v7.6.0 or higher):

const timesnap = require('timesnap');
var pages = [
  {
    url: 'https://tungs.github.io/truchet-tiles-original/#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, and config.frameProcessor.

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).
      • # 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).
    • # 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.
    • # startDelay <number> Waits config.startDelay real seconds after loading before starting (default: 0).
    • # quiet <boolean> Suppresses console logging.
    • # logToStdErr <boolean> Logs to stderr instead of stdout. Doesn't do anything if config.quiet is set to true.
    • # 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.
  • # returns: <Promise> resolves after all the frames have been captured.

# 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, 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.

About

Take screenshots of web pages at smooth intervals

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 100.0%