timecut-core
v0.3.3
Published
Record smooth movies of web pages
Downloads
15
Readme
timecut
timecut is a Node.js program that records smooth videos of web pages that use JavaScript animations. It uses timeweb, timesnap, and puppeteer to open a web page, overwrite its time-handling functions, take snapshots of the web page, and then passes the results to ffmpeg to encode those frames into a video. This allows for slower-than-realtime and/or virtual high-fps capture of frames, while the resulting video is smooth.
It requires ffmpeg, Node v8.9.0 or higher, and npm.
timecut-core
timecut-core is a version of timecut that uses timesnap-core which does not automatically bundle puppeteer. It differs from timecut
by requiring a config.launcher
function or a config.browser
object to be passed, and does not have a command line interface. It's stored on the core
branch of timecut
and derived from its code. All pull requests should be based on the main branches of timecut
and timesnap
instead of the core branches, unless in the rare event that it's particular only to the core branches.
To only record screenshots and save them as pictures, see timesnap and timesnap-core. For using virtual time in browser, see timeweb.
# timeweb, timecut, and timesnap Limitations
timeweb (and timesnap and timecut 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 Node.js
# Node Install
npm install timecut-core --save
timecut-core
also requires ffmpeg to be installed.
# Node Examples
For these examples, we'll use puppeteer version 2.1.1, which requires fewer additional libraries to be installed. ffmpeg also needs to be installed.
npm install [email protected] --save
# Basic Use:
Specify a config.launcher
function that creates a browser instance with certain launch options.
const timecut = require('timecut-core');
const puppeteer = require('puppeteer');
timecut({
launcher: launchOptions => puppeteer.launch(launchOptions),
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
output: 'video.mp4' // to video.mp4 of the current working directory
}).then(function () {
console.log('Done!');
});
# Using config.browser
:
You can also use config.browser
, though it might ignore / disable some launch options like config.quiet
, config.logToStdErr
, config.headless
, config.executablePath
, and config.launchArguments
. You can specify custom launch arguments through puppeteer.launch()
.
const timecut = require('timecut-core');
const puppeteer = require('puppeteer');
timecut({
browser: puppeteer.launch({ dumpio: true }), // can add custom launch options here
url: 'https://tungs.github.io/amuse/truchet-tiles/#autoplay=true&switchStyle=random',
selector: '#container',
output: 'truchet-tiles.mp4'
}).then(function () {
console.log('Done!');
});
# 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
, config.navigatePageToURL
, config.preparePage
, config.preparePageForScreenshot
, config.outputStream
, config.logger
, and certain config.viewport
properties.
timecut(config)
- #
config
<Object>- #
launcher
<function(Object)> A function that returns or resolves a puppeteer or puppeteer-like browser. It is passed a launch options argument, which should be passed topuppeteer.launch
, if possible. - #
browser
<Object> The instance of a puppeteer or puppeteer-like browser. Note that certain configuration options might not work as intended or might be ignored, likeconfig.quiet
,config.logToStdErr
,config.headless
,config.executablePath
, andconfig.launchArguments
. - #
url
<string> The url to load. It can be a web url, likehttps://github.com
or a file path, with relative paths resolving in the current working directory (default:index.html
). - #
output
<string> Tells ffmpeg to save the video as name. Its file extension determines encoding if not explicitly specified. Default name:video.mp4
. - #
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. - #
frameCache
<string|boolean> Saves each frame temporarily to disk before ffmpeg processes it. Ifconfig.frameCache
is a string, uses that as the directory to save the temporary files. Ifconfig.frameCache
is a booleantrue
, temporarily creates a directory in the current working directory. See cache frame mode. - #
pipeMode
<boolean> Experimental. If set totrue
, pipes frames directly to ffmpeg, without saving to disk. See pipe mode. - #
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 themeta 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. Can provide an optional image format (e.g.
png
), otherwise it uses the saved image's extension, or defaults topng
if the format is not specified or supported. Can prefix the format withimmediate:
(e.g.immediate:png
) to immediately capture pixel data after rendering, which is sometimes needed for some WebGL renderers. Specify the canvas by settingconfig.selector
, otherwise it defaults to the first canvas in the document.
- Experimental. Captures images from canvas data instead of screenshots. See canvas capture mode. Can provide an optional image format (e.g.
- #
start
<number> Runs code forconfig.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. Only works if the output video format supports transparency. - #
roundToEvenWidth
<boolean> Rounds capture width up to the nearest even number (default:true
). - #
roundToEvenHeight
<boolean> Rounds capture height up to the nearest even number (default:true
). - #
left
<number> Left edge of capture, in pixels. Equivalent toconfig.xOffset
. - #
right
<number> Right edge of capture, in pixels. Ignored ifconfig.width
is specified. - #
top
<number> Top edge of capture, in pixels. Equivalent toconfig.yOffset
. - #
bottom
<number> Bottom edge of capture, in pixels. Ignored ifconfig.height
is specified. - #
unrandomize
<boolean | string | number | Array<number>> OverwritesMath.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 atconfig.executablePath
for puppeteer. - #
ffmpegPath
<string> Uses the ffmpeg path for running ffmpeg. - #
launchArguments
<Array <string>> Extra arguments for Puppeteer/Chromium. Example:['--single-process']
. A list of arguments can be found here. - #
headless
<boolean> Runs puppeteer in headless (nonwindowed) mode (default:true
). - #
screenshotType
<string> Output image format for the screenshots. By default,'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 and 0.8 otherwise. - #
inputOptions
<Array <string>> Extra arguments for ffmpeg input. Example:['-framerate', '30']
- #
outputOptions
<Array <string>> Extra arguments for ffmpeg output. Example:['-vf', 'scale=320:240']
- #
pixFmt
<string> Pixel format for output video (default:yuv420p
). - #
startDelay
<number> Waitsconfig.startDelay
real seconds after loading before starting (default:0
). - #
keepFrames
<boolean> If set to true, doesn't delete frames after processing them. Doesn't do anything in pipe mode. - #
quiet
<boolean> Suppresses console logging. - #
logger
<function(...Object)> Replaces console logging with a particular function. The passed arguments are the same as those toconsole.log
(in this case, usually one string). - #
logToStdErr
<boolean> Logs to stderr instead of stdout. Doesn't do anything ifconfig.quiet
is set to true. - #
stopFunctionName
<string> function name that the client web page can call to stop capturing. For instance,'stopCapture'
could be called in the client, viastopCapture()
. - #
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: - #
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. - #
outputStream
<stream()> A node stream to write data to from ffmpeg - #
outputStreamOptions
<Object> Optional configuration object when usingconfig.outputStream
- #
- # returns: <Promise> resolves after all the frames have been captured.
# timecut Modes
# Capture Modes
timecut can capture frames to 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 and the output video 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 or setconfig.canvasCaptureMode
from Node.js. Also specify the canvas using a css selector, using the--selector
option from the command line or settingconfig.selector
from Node.js, otherwise it uses the first canvas element.
# Frame Transfer Modes
timecut can pass frames to ffmpeg using one of two modes:
- # Cache frame mode stores each frame temporarily before running ffmpeg on all of the images. This mode can use a lot of temporary disk space (hundreds of megabytes per second of recorded time), but takes up less memory and is more stable than pipe mode. This is currently enabled by default, though it may change in the future. To explicitly use this mode, use the
--frame-cache
option from the command line or setconfig.frameCache
from Node.js totrue
or to a directory name. - # Pipe mode (experimental) pipes each frame directly to
ffmpeg
, without saving each frame. This takes up less temporary space than cache frame mode, but it currently has some observed stability issues. To use this mode, use the--pipe-mode
option from the command line or setconfig.pipeCache
totrue
from Node.js. If you run into issues, you may want to try cache frame mode or to install and use timesnap and pipe it directly to ffmpeg. Both alternative implementations seem more stable than the current pipe mode.
# How it works
timecut uses timesnap to record frames to send to ffmpeg
. 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.