• Stars
    star
    205
  • Rank 191,264 (Top 4 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 4 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Record the canvas as an image, mp4 video, or gif from the browser

canvas-capture

NPM Package Build Size NPM Downloads License

A small wrapper around CCapture.js and ffmpeg.wasm to record the canvas as an image (png/jpeg), video (mp4/webm), or gif – all from the browser!

Demo at: apps.amandaghassaei.com/canvas-capture/demo/

All media formats are currently supported by both Chrome and Firefox (see Caveats for more details about browser support and server header requirements).

This project doesn't expose all the features of either CCapture.js or ffmpeg.wasm, but it packages some of the most useful functionality into a few simple methods. This package can be installed via npm and run in the browser (I'm mostly using this in projects built with webpack). Some key features:

  • export mp4 and webm video from the browser (via CCapture.js and ffmpeg.wasm)
  • export still images as png / jpeg
  • export animated gifs (via CCapture.js)
  • export zipped png/jpeg frame sequences (via JSZip)
  • helper functions to bind recording and screen-shotting to hotkeys
  • an optional recording indicator (red dot) on screen to let you know when recording is happening
  • other optional modal dialog features
  • type declarations for everything – this project is written in Typescript

Installation

Install via NPM

npm install canvas-capture

Then import via:

import { CanvasCapture } from 'canvas-capture';

Install as JS

OR in the browser you can add canvas-capture.js or canvas-capture.min.js to your html:

<html>
    <head>
        ....
        <script src="canvas-capture.js"></script>
    </head>
    <body>
    </body>
</html>

Then in your js files, you can access the global variable CanvasCaptureLib:

const { CanvasCapture } = CanvasCaptureLib;

See a demo importing canvas-capture via html at apps.amandaghassaei.com/canvas-capture/demo-simple/

Use

There are a few ways to call canvas-capture. You can bind hotkeys to start/stop recording:

import { CanvasCapture } from 'canvas-capture';

// Initialize and pass in canvas.
CanvasCapture.init(
  document.getElementById('my-canvas'),
  { showRecDot: true }, // Options are optional, more info below.
);

// Bind key presses to begin/end recordings.
CanvasCapture.bindKeyToVideoRecord('v', {
  format: 'mp4', // Options are optional, more info below.
  name: 'myVideo',
  quality: 0.6,
});
CanvasCapture.bindKeyToGIFRecord('g');
// Download a series of frames as a zip.
CanvasCapture.bindKeyToPNGFramesRecord('f', {
  onProgress: (progress) => { // Options are optional, more info below.
    console.log(`Zipping... ${Math.round(progress * 100)}% complete.`);
  },
}); // Also try bindKeyToJPEGFramesRecord().

// These methods immediately save a single snapshot on keydown.
CanvasCapture.bindKeyToPNGSnapshot('p'); 
CanvasCapture.bindKeyToJPEGSnapshot('j', {
  name: 'myJpeg', // Options are optional, more info below.
  quality: 0.8,
});

function loop() {
   requestAnimationFrame(loop);

  // Render something...

  // It is recommended to use checkHotkeys() right after rendering
  // frame in render loop to ensure that PNG and JPEG
  // snapshots are triggered at the right time.
  // Otherwise, blank images may be generated due to the browser
  // clearing the render buffer before onKeyDown is triggered.
  CanvasCapture.checkHotkeys();

  // You need to call recordFrame() only if you are recording
  // a video, gif, or frames.
  if (CanvasCapture.isRecording()) CanvasCapture.recordFrame();
}

loop(); // Start loop.

Alternatively, you can call beginXXXRecord and takeXXXSnapshot directly:

import { CanvasCapture } from 'canvas-capture';

// Initialize and pass in canvas.
CanvasCapture.init(
  document.getElementById('my-canvas'),
  { showRecDot: true }, // Options are optional, more info below.
);

CanvasCapture.beginGIFRecord({ name: 'myGif', fps: 10 });
.... // Draw something.
CanvasCapture.recordFrame();
.... // Draw something.
CanvasCapture.recordFrame();
CanvasCapture.stopRecord();

// Now you may start another recording.
CanvasCapture.beginVideoRecord({ format: CanvasCapture.MP4 });
CanvasCapture.recordFrame();
....
CanvasCapture.stopRecord();

// Also try beginJPEGFramesRecord(jpegOptions)
// and beginPNGFramesRecord(pngOptions)

// Or you can call `takeXXXSnapshot` to take a single snapshot.
// No need to call `recordFrame` or `stopRecord` for these methods.
CanvasCapture.takePNGSnapshot();
CanvasCapture.takeJPEGSnapshot({ dpi: 600, onExport: (blob, filename) => {
  // Instead of automatically downloading the file, you can pass an
  // optional onExport callback to handle blob manually.
}});

Available options for each capture type - passed in as an optional argument to bindKeyToXXX, beginXXXRecord, or takeXXXSnapshot:

videoOptions = {
  format: CanvasCapture.MP4 | CanvasCapture.WEBM, // Defaults to 'CanvasCapture.MP4'.
  name: string, // Defaults to 'Video_Capture'.
  fps: number, // Frames per second of the output video, defaults to 60.
  quality: number, // A number between 0 and 1, defaults to 1.
  motionBlurFrames: number, // Number of intermediary frames used to calculate motion blur.
  onExportProgress: (progress: number) => void, // progress: range [0-1].
  onExport: (blob: Blob, filename: string) => void, // Handle blob manually.
  onExportFinish: () => void, // Callback after successful export.
  onError: (error: Error | any) => void, // Callback on error.
  // Options below for ffmpeg conversion to mp4, not used for webm export.
  ffmpegOptions?: { [key: string]: string }, // FFMPEG option flags
  // Defaults to
  // {  '-c:v': 'libx264',
  //    '-preset': 'slow',
  //    '-crf': '22',
  //    '-pix_fmt': 'yuv420p' }
  // Internally the ffmpeg conversion runs with additional flags to crop
  // to an even number of px dimensions (required for mp4):
  // '-vf crop=trunc(iw/2)*2:trunc(ih/2)*2'
  // and export no audio channel: '-an'
}
gifOptions = {
  name: string, // Defaults to 'GIF_Capture'.
  fps: number, // The frames per second of the output gif, defaults to 60.
  quality: number, // A number between 0 and 1, defaults to 1.
  motionBlurFrames: number, // Number of intermediary frames used to calculate motion blur.
  onExportProgress: (progress: number) => void, // progress: range [0-1].
  onExport: (blob: Blob, filename: string) => void, // Handle blob manually.
  onExportFinish: () => void, // Callback after successful export.
  onError: (error: Error | any) => void, // Callback on error.
}
pngOptions = {
  name: string, // Defaults to 'PNG_Capture'.
  dpi: number, // Defaults to screen resolution (72 dpi).
  onExport: (blob: Blob, filename: string) => void, // Handle blob manually.
  // onExportProgress and onExportFinish gives zipping updates for
  // recording PNG frames (only used by bindKeyToPNGFramesRecord()
  // and beginPNGFramesRecord()):
  onExportProgress: (progress: number) => void, // progress: range [0-1].
  onExportFinish: () => void, // Callback after successful export.
  onError: (error: Error | any) => void, // Callback on error.
}
jpegOptions = {
  name: string, // Defaults to 'JPEG_Capture'.
  quality: number, // A number between 0 and 1, defaults to 1.
  dpi: number, // Defaults to screen resolution (72 dpi).
  onExport: (blob: Blob, filename: string) => void, // Handle blob manually.
  // onExportProgress and onExportFinish gives zipping updates for
  // recording JPEG frames (only used by bindKeyToJPEGFramesRecord()
  // and beginJPEGFramesRecord()):
  onExportProgress: (progress: number) => void, // progress: range [0-1].
  onExportFinish: () => void, // Callback after successful export.
  onError: (error: Error | any) => void, // Callback on error.
}

Note that changing the dpi of png/jpeg does not change the amount of pixels captured, just the dimensions of the resulting image.

You can initialize CanvasCapture with the following options:

import { CanvasCapture } from 'canvas-capture';

CanvasCapture.init(document.getElementById('my-canvas'), {
  // Verbosity of console output.
  verbose: true, // Default is false.
  // Show a red dot on the screen during records.
  showRecDot: true, // Default is false.
  // CSS overrides for record dot.
  recDotCSS: { right: '0', top: '0', margin: '10px' }, // Default is {}.
  // Show alert dialogs during export in case of errors.
  showAlerts: true, // Default is false.
  // Show informational dialogs during export.
  showDialogs: true, // Default is false.
  // Path to a copy of ffmpeg-core to be loaded asynchronously.
  // ffmpeg-core has not been included in this library by default because
  // it is very large (~25MB) and is only needed for mp4 export.
  ffmpegCorePath: './node_modules/@ffmpeg/core/dist/ffmpeg-core.js', 
  // By default, ffmpegCorePath is set to load remotely from
  // https://unpkg.com/@ffmpeg/[email protected]/dist/ffmpeg-core.js
  // If you would like to load locally, you can set ffmpegCorePath to
  // load from node_modules:
  // './node_modules/@ffmpeg/core/dist/ffmpeg-core.js'
  // using a copy of @ffmpeg/core installed via npm, or copy the files
  // (ffmpeg-core.js, ffmpeg-core.wasm, and ffmpeg-core.worker.js), save
  // them in your project, and set ffmpegCorePath to point to
  // ffmpeg-core.js
});

The baseline CSS for the record dot places it in the upper right corner of the screen, any of these params can be overwritten via options.recDotCSS:

background: "red",
width: "20px",
height: "20px",
"border-radius": "50%", // Make circle.
position: "absolute",
top: "0",
right: "0",
"z-index": "10",
margin: "20px",

Additionally, you can set the verbosity of the console output at any time by:

CanvasCapture.setVerbose(false);

I've also included a helper function to show a simple modal dialog with a title and message:

const options = {
  // Set the amount of time to wait before auto-closing dialog,
  // or -1 to disable auto-close.
  autoCloseDelay: 7000, // Default is -1.
};
// title and message are strings, options are optional.
CanvasCapture.showDialog(title, message, options);

Additionally, if you want to unbind all events from CanvasCapture:

CanvasCapture.dispose();

Caveats

mp4 export currently works best in Chrome, but it does work in the latest release of Firefox (96.0.1), now that this Firefox bug seems to have been addressed. I have noticed that ffmpeg can get stuck in Firefox, but only when the console/devtools are open, see this issue.

This repo depends on ffmpeg.wasm to export mp4 video, not all browsers are supported:

Only browsers with SharedArrayBuffer support can use ffmpeg.wasm, you can check HERE for the complete list.

In order for mp4 export to work, you need to configure your (local or remote) server correctly:

SharedArrayBuffer is only available to pages that are cross-origin isolated. So you need to host your own server with Cross-Origin-Embedder-Policy: require-corp and Cross-Origin-Opener-Policy: same-origin headers to use ffmpeg.wasm.

I've included a script for initializing a local server with the correct Cross-Origin policy at canvas-capture/server.js. If you need to start up your own server for testing, try running the code below to boot up a server at localhost:8080:

node node_modules/canvas-capture/server.js

If you're building an application with webpack-dev-server, you can add the appropriate headers to your webpack.config.js:

module.exports = {
  ...
  devServer: {
    ...
    headers: {
      "Cross-Origin-Embedder-Policy": "require-corp",
      "Cross-Origin-Opener-Policy": "same-origin",
    }
  }
}

If you're hosting an application on Github Pages, I recommend checking out coi-serviceworker to get the correct headers on your page. I was able to get this to work for my demo page.

Additionally, you can test for browser support with the following methods:

// Returns true if the browser supports webm recording.
CanvasCapture.browserSupportsWEBM();

// Returns true if the browser supports mp4 recording.
CanvasCapture.browserSupportsMP4();

// Returns true if the browser supports gif recording.
CanvasCapture.browserSupportsGIF();

I'm not aware of any browser limitations for the image export options (obviously, the browser must support canvas as a bare minimum).

Another thing to be aware of: this library defaults to pulling a copy of ffmpeg.wasm remotely from unpkg.com/@ffmpeg/[email protected]/dist/, so it requires an internet connection to export mp4. If you want to host your own copy of ffmpeg-core, you'll need to provide a path to ffmpeg-core.js with the ffmpegCorePath option in CanvasCapture.init(). Be sure to also include ffmpeg-core.wasm and ffmpeg-core.worker.js in the same folder.

Additional Notes

  • webm export is ready to download immediately after all frames are captured. mp4 videos are generated by recording as webm, then converting into mp4 in the browser using ffmpeg.wasm. Since mp4 export requires an additional conversion step after the frames have been captured, you may find that ffmpeg conversion to mp4 takes too long for very large videos.
  • webm videos are significantly larger than mp4.
  • png export preserves the alpha channel of canvas, and jpeg/gif exporters will draw alpha = 0 as black, but the video exporter creates nasty artifacts when handling transparent/semi-transparent regions of the canvas – best to avoid this.
  • You cannot record gif and video (or multiple gifs / multiple videos) at the same time. This appears to be a limitation of CCapture.js. You can capture screenshots or record png/jpeg frames as zip while recording a video/gif.
  • beginXXXRecord methods return a capture object that can be passed to CanvasCapture.recordFrame(capture) or CanvasCapture.stopRecord(capture) to target a specific recording. If recordFrame or stopRecord are called with no arguments, all active captures are affected.
  • gif.js (a dependency of CCapture.js) has some performance limitations and takes a significant amount of time to process after all frames have been captured, be careful if capturing a lot of frames. Exported gifs tend to be quite large and uncompressed, you might want to optimize them further (I like ezgif for this).
  • Recording png/jpeg frames is currently set to save a zip with no compression with JSZip. Even so, the zipping process takes some time and you might be better off saving the frames individually with takeXXXSnapshot() if you have a lot of files to save.

Converting WEBM to Other Formats

Not all browsers support mp4 export, and even if they do, you may decide to export webm anyway for performance reasons (I tend to do this, they are much faster to export). Webm is a bit annoying as a format though – I've found that I can play webm videos with VLC player, but the framerate tends to be choppy, and very few websites/programs support them. If you want to convert your webms to mp4 (or any other format) after you've already downloaded them, I recommend using ffmpeg from the terminal:

ffmpeg -i PATH/FILENAME.webm -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" -c:v libx264 -preset slow -crf 22 -pix_fmt yuv420p -an PATH/FILENAME.mp4

-vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" crops the video so that its dimensions are even numbers (required for mp4)

-c:v libx264 -preset slow -crf 22 encodes as h.264 with better compression settings

-pix_fmt yuv420p makes it compatible with the web browser

-an creates a video with no audio

For Mac users: I recommend checking out MacOS Automator and creating a Quick Action for these types of conversions. I have some instructions for that here. I have a Quick Action for "Convert to MP4" that invokes the above ffmpeg command on whatever file I've selected in the Finder – it's a big time saver!

License

The code in this repo is licensed under an MIT license, but it depends on other codebases and proprietary video codecs with different licenses. Please be aware of this and check this project's dependencies for more info, specifically:

@ffmpeg/core contains WebAssembly code which is transpiled from original FFmpeg C code with minor modifications, but overall it still following the same licenses as FFmpeg and its external libraries (as each external libraries might have its own license).

Development

Pull requests welcome!

Install development dependencies by running:

npm install

To build src to dist run and recompile demo:

npm run build

Please note that this repo depends on CCapture.js, but there is currently some weirdness around importing CCapture with npm. I'm using a copy of CCapture from the npm-fix branch at github.com/amandaghassaei/ccapture.js/tree/npm-fix. I'm not proud of the changes I had to make to get this to work (see diff here), but it's fine for now. In order to package this (canvas-capture) repo up nicely for npm and remove all github-hosted dependencies, I had to make a copy of my CCapture npm-fix branch in the src/CCapture.js/ directory of this repo. It's ugly, but hopefully this can all be cleared up at some point in the future.

Also, in order to get the CCapture constructor to work correctly, I had to call window.CCapture() rather than using the module import directly. You'll also see I had to assign the default export from CCapture to an unused temp variable to make sure it was included in the build:

// Importing my local copy of CCapture.js.
import CCapture from './CCapture.js/CCapture';
// This is an unused variable, but critically necessary.
const temp = CCapture;

....

const capturer = new window.CCapture({
  format: 'webm',
  name: 'WEBM_Capture',
  framerate: 60,
  quality: 63,
  verbose: false,
});
// This didn't work:
// const capturer = new CCapture({
//   format: 'webm',
//   name: 'WEBM_Capture',
//   framerate: 60,
//   quality: 63,
//   verbose: false,
// });

Hopefully this will all be fixed in the future, see notes here:

spite/ccapture.js#78

Demo

This repo also includes a demo for testing, currently hosted at apps.amandaghassaei.com/canvas-capture/demo/. You can find the demo source code at demo/src/index.ts. An even simpler demo (no webpack, no compilation, import canvas-capture directly in HTML) can be found at apps.amandaghassaei.com/canvas-capture/demo-simple/.

To build the demo folder, run:

npm run build-demo

To run the demo locally, run:

npm run start

This will boot up a local server with the correct Cross-Origin policies to support ffmpeg.wasm (a dependency for exporting mp4 video). Navigate to the following address in your browser:

localhost:8080/demo/

More Repositories

1

gpu-io

A GPU-accelerated computing library for running physics simulations and other GPGPU computations in a web browser.
TypeScript
1,204
star
2

OrigamiSimulator

Realtime WebGL origami simulator
JavaScript
1,083
star
3

FluidSimulation

WebGL shader for mixed grid-particle fluid simulation
TypeScript
273
star
4

VortexShedding

A realtime fluid flow simulation on the GPU using WebGL
JavaScript
142
star
5

Fusion360-Scripts

A collection of Fusion360 scripts, mostly for generating animations
Python
103
star
6

LaserCutRecord

generate vector cutting paths from digital audio to make a working record
Processing
55
star
7

SoapFlow

Jupyter Notebook
42
star
8

MassSpringShader

WebGL Shader that implements a mass-spring-damper physical simulation
HTML
41
star
9

3DPrintedRecord

3d printable record stl generator using Processing and Python
Processing
37
star
10

tellurion-orrery

CAD files and info for building an orrery from scratch
JavaScript
34
star
11

ShellFormFinding

Web-based simulation tool that allows you to design 3D forms in pure compression
JavaScript
28
star
12

ConwayShader

WebGL Shader for Conway's Game of Life
TypeScript
22
star
13

Lithograph3DPrint

converts an image to a 3d printable heightmap or lithophane (http://en.wikipedia.org/wiki/Lithophane)
Processing
20
star
14

MeshWriter

Live three.js coding with STL and OBJ export
JavaScript
19
star
15

botanigram

Create "growing" animations of plants from a single photograph
TypeScript
18
star
16

Sugarcube-Arduino-Library

A grid-based midi instrument, with accelerometer and gyroscope for playful interactions
Max
18
star
17

Genetic-Images

a Processing sketch that generates images from translucent polygons using a genetic algorithm
Processing
18
star
18

marbling-experiment

An early experiment toward building a marbling simulation
TypeScript
16
star
19

msh-parser

Finite element .msh format parser, written in TypeScript
TypeScript
15
star
20

MotionMagnification

Eulerian, Lagrangian, and Phase-Based Motion Magnification in MATLAB
MATLAB
13
star
21

stl-parser

Standalone module for parsing binary and ASCII STL files, written in TypeScript
TypeScript
12
star
22

salesforce-tower

3D visualization for artwork on Salesforce Tower
JavaScript
11
star
23

TrussOptimization2D

Web-based design and optimization tool that uses real-time simulation feedback to inform the design process.
JavaScript
11
star
24

ProjectionMappingAlignment

WebGL app that breaks out many of the threejs camera parameters to fine tune projection mapping alignment
JavaScript
11
star
25

ScratchSerialExtension

A Serial Communication Extension for Scratch
JavaScript
11
star
26

ffmpeg-scripts

A collection of ffmpeg scripts I'm using to generate gifs and mp4
Shell
10
star
27

vector-math

A minimal vector math library to handle 2D/3D translations and rotations.
TypeScript
10
star
28

ReactionDiffusionShader

WebGL Shader for a Gray-Scott reaction diffusion system
JavaScript
10
star
29

MichellStructures

Web-based simulation tool that visualizes geometric properties and static internal forces in a loaded Michell cantilever
JavaScript
9
star
30

CurvedCreases

JavaScript
6
star
31

AMOEBA

JavaScript
5
star
32

LinkageOptimization

JavaScript
4
star
33

Linkages

A planar linkage optimization tool
nesC
3
star
34

LenticularProcessor

a Processing sketch for preparing a stack of images for a lenticlar lens
Processing
2
star
35

AMOEBA-2.5D

JavaScript
2
star
36

NodeSerialPortBoilerplate

Simple demo to get serial ports up and running with node and client-side javascript
JavaScript
2
star
37

event-dispatcher

Parent class to support custom event listeners, written in TypeScript.
TypeScript
2
star
38

StepperRotaryStage

A geared, rotary stage made from 3D printed and laser cut parts (and some off the shelf components). Modeled parametrically in Fusion360.
1
star
39

ActiveBendingHybridStructures

JavaScript
1
star
40

ms-thesis

TeX
1
star
41

Processing-Linkage-Generator

leg linkages designed in Processing, exported to STL for 3D Printing via ModelBuilder Library
Processing
1
star
42

ImageMagick

a collection of ImageMagick scripts
Shell
1
star
43

type-checks

A collection of JavaScript type checks.
TypeScript
1
star
44

RecordProcessor

A web app for designing custom records, outputs to laser cutter, CNC mill...
JavaScript
1
star