├── .gitignore ├── .travis.yml ├── README.md ├── appveyor.yml ├── header.html ├── index.js ├── logo.svg ├── netlify.toml ├── package.json └── test ├── cli-test.js └── mocha.opts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | yarn.lock 4 | web.gif 5 | website/ 6 | .nyc_output/ 7 | coverage/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | 5 | language: node_js 6 | 7 | node_js: 8 | - 7 9 | - 8 10 | - 9 11 | - 10 12 | 13 | after_script: 14 | - sleep 10 15 | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_NODE_VERSION" == "8" ]]; then cat ./coverage/lcov.info | npx coveralls; fi 16 | 17 | matrix: 18 | exclude: 19 | - os: osx 20 | node_js: 7 21 | - os: osx 22 | node_js: 9 23 | 24 | jobs: 25 | include: 26 | - stage: npm release 27 | node_js: "8" 28 | script: echo "Deploying to npm ..." && npm version 29 | deploy: 30 | provider: npm 31 | email: $NPM_EMAIL 32 | api_key: $NPM_TOKEN 33 | skip_cleanup: true 34 | on: 35 | branch: master 36 | tags: true 37 | repo: anishkny/webgif 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

logo

2 | 3 | ## *Easily generate animated GIFs from websites* 4 | 5 | [![Build Status](https://travis-ci.org/anishkny/webgif.svg?branch=master)](https://travis-ci.org/anishkny/webgif) 6 | [![Build status](https://ci.appveyor.com/api/projects/status/ji5c66ex9ifog9hk/branch/master?svg=true)](https://ci.appveyor.com/project/anishkny/webgif/branch/master) 7 | [![Greenkeeper badge](https://badges.greenkeeper.io/anishkny/webgif.svg)](https://greenkeeper.io/) 8 | [![Coverage Status](https://coveralls.io/repos/github/anishkny/webgif/badge.svg)](https://coveralls.io/github/anishkny/webgif) 9 | ![node](https://img.shields.io/node/v/webgif.svg) 10 | [![NPM Version](https://img.shields.io/npm/v/webgif.svg)](https://www.npmjs.com/package/webgif) 11 | 12 | * **Easy** 👉 *Just point it to a URL and get an animated GIF!* 13 | * **Cross-platform** 👉 *Works on Windows, Mac, Linux, without Docker!* 14 | * **Headless** 👉 *Uses [GoogleChrome/puppeteer](https://github.com/GoogleChrome/puppeteer)* 15 | * **Inspired** 👉 *By [asciicast2gif](https://github.com/asciinema/asciicast2gif) and wanting to make it easier to use* 16 | 17 | ### Installation 18 | ```bash 19 | yarn global add webgif || npm i -g webgif 20 | ``` 21 | 22 | ### Usage 23 | 24 | To navigate to `https://giphy.com/search/lol` and make an animated GIF of duration `10` seconds, execute: 25 | 26 | ```bash 27 | webgif -u https://giphy.com/search/lol -d 10 28 | 29 | Navigating to URL: https://giphy.com/search/lol 30 | Taking screenshots: ............. 31 | Encoding GIF: /home/user/web.gif 32 | ``` 33 | 34 | ### Options 35 | 36 | ```bash 37 | webgif -u URL -d DURATION [-o OUTFILE] 38 | 39 | Options: 40 | --url, -u URL to generate GIF from 41 | [default: "https://giphy.com/search/lol"] 42 | --duration, -d GIF duration in seconds [default: 10] 43 | --output, -o Output file name 44 | [default: "web.gif"] 45 | -h, --help Show help [boolean] 46 | -V, --version Show version number [boolean] 47 | ``` 48 | 49 | ### Sample GIF 50 | 51 | ![Sample GIF](https://storage.googleapis.com/webgif/web.gif) 52 | 53 | ### How it works 54 | 55 | 1. Use [Puppeteer](https://github.com/GoogleChrome/puppeteer) to launch a headless Chrome window 56 | 1. Use `setInterval` to take screenshots and save them to disk 57 | 1. Navigate to target URL and wait for specified duration 58 | 1. Use [gifencoder](https://github.com/eugeneware/gifencoder) and [png-file-stream](https://github.com/eugeneware/png-file-stream) to create animated GIF out of saved screenshots 59 | 60 | See code: [`index.js`](https://github.com/anishkny/webgif/blob/master/index.js) 61 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the latest version of this Node.js version 2 | environment: 3 | matrix: 4 | - nodejs_version: "8" 5 | - nodejs_version: "10" 6 | 7 | # Install scripts. (runs after repo cloning) 8 | install: 9 | # Get the latest stable version of Node.js or io.js 10 | - ps: Install-Product node $env:nodejs_version 11 | # install modules 12 | - npm install 13 | 14 | # Post-install test scripts. 15 | test_script: 16 | # Output useful info for debugging. 17 | - node --version 18 | - npm --version 19 | # run tests 20 | - npm test 21 | 22 | # Don't actually build. 23 | build: off 24 | -------------------------------------------------------------------------------- /header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webgif - Easily generate animated GIFs from websites 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const GIFEncoder = require('gifencoder'); 5 | const path = require('path'); 6 | const pngFileStream = require('png-file-stream'); 7 | const puppeteer = require('puppeteer'); 8 | const tempdir = require('tempdir'); 9 | 10 | const argv = require('yargs') 11 | .alias('url', 'u').default('url', 'https://giphy.com/search/lol') 12 | .describe('url', 'URL to generate GIF from') 13 | .alias('duration', 'd').default('duration', 10) 14 | .describe('duration', 'GIF duration in seconds') 15 | .alias('output', 'o').default('output', `${process.cwd()}${require('path').sep}web.gif`) 16 | .describe('output', 'Output file name') 17 | .alias('h', 'help') 18 | .alias('V', 'version') 19 | .usage('webgif -u URL -d DURATION [-o OUTFILE]') 20 | .version() 21 | .argv; 22 | 23 | (async () => { 24 | const browser = await puppeteer.launch({ 25 | ignoreHTTPSErrors: true, 26 | args: ['--allow-running-insecure-content', '--disable-setuid-sandbox', '--no-sandbox', ], 27 | }); 28 | const page = await browser.newPage(); 29 | const workdir = await tempdir(); 30 | 31 | page.setViewport({ 32 | width: 1024, 33 | height: 768, 34 | }); 35 | 36 | console.log(`Navigating to URL: ${argv.url}`); 37 | await page.goto(argv.url); 38 | 39 | process.stdout.write('Taking screenshots: .'); 40 | const screenshotPromises = []; 41 | for (let i = 1; i <= argv.duration; ++i) { 42 | filename = `${workdir}/T${new Date().getTime()}.png`; 43 | process.stdout.write('.'); 44 | screenshotPromises.push(page.screenshot({ path: filename, })); 45 | await delay(1000); 46 | } 47 | 48 | await delay(1000); 49 | await Promise.all(screenshotPromises); 50 | console.log(`\nEncoding GIF: ${argv.output}`); 51 | const encoder = new GIFEncoder(1024, 768); 52 | await pngFileStream(`${workdir}/T*png`) 53 | .pipe(encoder.createWriteStream({ repeat: 0, delay: 200, quality: 20 })) 54 | .pipe(fs.createWriteStream(`${argv.output}`)); 55 | await page.close(); 56 | await browser.close(); 57 | 58 | })(); 59 | 60 | /* istanbul ignore next */ 61 | process.on('unhandledRejection', function(reason, p) { 62 | throw new Error(reason); 63 | }); 64 | 65 | function delay(ms) { 66 | return new Promise((resolve) => setTimeout(resolve, ms)); 67 | } 68 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | background 7 | 8 | 9 | 10 | Layer 1 11 | webgif 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [Settings] 2 | ID = "webgif" 3 | 4 | [build] 5 | publish = "website/" 6 | command = "npm run website" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgif", 3 | "version": "1.1.0", 4 | "description": "Easily generate animated GIFs from websites", 5 | "main": "index.js", 6 | "bin": { 7 | "webgif": "./index.js" 8 | }, 9 | "scripts": { 10 | "test": "nyc -r text -r html -r lcov mocha", 11 | "website": "mkdir -p website; (cat header.html; showdown makehtml -m -i README.md) > website/index.html" 12 | }, 13 | "engines": { 14 | "node": ">=7.6.0" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/anishkny/webgif.git" 19 | }, 20 | "keywords": [ 21 | "gif", 22 | "puppeteer", 23 | "web" 24 | ], 25 | "author": "Anish Karandikar ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/anishkny/webgif/issues" 29 | }, 30 | "homepage": "https://github.com/anishkny/webgif#readme", 31 | "dependencies": { 32 | "gifencoder": "^1.1.0", 33 | "png-file-stream": "^1.0.0", 34 | "puppeteer": "^1.2.0", 35 | "tempdir": "^2.0.0", 36 | "yargs": "^12.0.1" 37 | }, 38 | "devDependencies": { 39 | "mocha": "^5.2.0", 40 | "nyc": "^12.0.1", 41 | "shelljs": "^0.8.1", 42 | "showdown": "^1.8.6" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/cli-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const shell = require('shelljs'); 3 | const tempdir = require('tempdir'); 4 | 5 | shell.config.verbose = true; 6 | 7 | describe('CLI tests', async () => { 8 | 9 | it('default options', async () => { 10 | assertShellOutput('node index.js'); 11 | }); 12 | 13 | it('help, version', async () => { 14 | assertShellOutput('node index.js -h'); 15 | assertShellOutput('node index.js -V'); 16 | }); 17 | 18 | it('other options', async () => { 19 | assertShellOutput('node index.js -u http://google.com'); 20 | assertShellOutput('node index.js -d 5'); 21 | }); 22 | 23 | }); 24 | 25 | function assertShellOutput(cmd) { 26 | const runoutput = shell.exec(cmd); 27 | assert(runoutput.code === 0, runoutput); 28 | } 29 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --timeout 120000 2 | --------------------------------------------------------------------------------