├── .gitignore ├── LICENSE ├── README.md ├── background.js ├── content_script.js ├── export.js ├── manifest.json ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Murali K G 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puppetcam 2 | 3 | Example to export chrome tab as a video 4 | 5 | 6 | 1. Exported videos are stored in Downloads folder 7 | 2. Specify bitrate to control quality of the exported video by adjusting `videoBitsPerSecond` property in `background.js` 8 | 9 | 10 | ### Dependencies 11 | 12 | 1. xvfb 13 | 2. npm modules listed in package.json 14 | 15 | ### Usage 16 | 17 | ```sh 18 | npm install 19 | node export.js http://tobiasahlin.com/spinkit/ spinner.webm 20 | ``` 21 | 22 | 23 | Thanks to [@cretz](https://github.com/cretz) for helping with automatic tab selection and avoiding the permission dialog 24 | 25 | #### Motivation 26 | 27 | Was looking for a method to export a video of user actions rendered using our custom player used in [uxlens](https://uxlens.com). Export has to happen on a server in an automated fashion and hence the usage of xvfb. 28 | 29 | #### Sample video 30 | [![Puppetcam](https://img.youtube.com/vi/f7Vdd0ExWiY/0.jpg)](https://www.youtube.com/watch?v=f7Vdd0ExWiY "Puppetcam") 31 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | /* global chrome, MediaRecorder, FileReader */ 2 | 3 | let recorder = null; 4 | let filename = null; 5 | chrome.runtime.onConnect.addListener(port => { 6 | 7 | port.onMessage.addListener(msg => { 8 | console.log(msg); 9 | switch (msg.type) { 10 | case 'SET_EXPORT_PATH': 11 | filename = msg.filename 12 | break 13 | case 'REC_STOP': 14 | recorder.stop() 15 | break 16 | case 'REC_CLIENT_PLAY': 17 | if(recorder){ 18 | return 19 | } 20 | const tab = port.sender.tab 21 | tab.url = msg.data.url 22 | chrome.desktopCapture.chooseDesktopMedia(['tab', 'audio'], streamId => { 23 | // Get the stream 24 | navigator.webkitGetUserMedia({ 25 | // audio: false, 26 | audio: { 27 | mandatory: { 28 | chromeMediaSource: 'system' 29 | } 30 | }, 31 | video: { 32 | mandatory: { 33 | chromeMediaSource: 'desktop', 34 | chromeMediaSourceId: streamId, 35 | minWidth: 1280, 36 | maxWidth: 1280, 37 | minHeight: 720, 38 | maxHeight: 720, 39 | minFrameRate: 60, 40 | } 41 | } 42 | }, stream => { 43 | var chunks=[]; 44 | recorder = new MediaRecorder(stream, { 45 | videoBitsPerSecond: 2500000, 46 | ignoreMutedMedia: true, 47 | mimeType: 'video/webm' 48 | }); 49 | recorder.ondataavailable = function (event) { 50 | if (event.data.size > 0) { 51 | chunks.push(event.data); 52 | } 53 | }; 54 | 55 | recorder.onstop = function () { 56 | var superBuffer = new Blob(chunks, { 57 | type: 'video/webm' 58 | }); 59 | 60 | var url = URL.createObjectURL(superBuffer); 61 | // var a = document.createElement('a'); 62 | // document.body.appendChild(a); 63 | // a.style = 'display: none'; 64 | // a.href = url; 65 | // a.download = 'test.webm'; 66 | // a.click(); 67 | 68 | chrome.downloads.download({ 69 | url: url, 70 | filename: filename 71 | }, ()=>{ 72 | }); 73 | } 74 | 75 | recorder.start(); 76 | }, error => console.log('Unable to get user media', error)) 77 | }) 78 | break 79 | default: 80 | console.log('Unrecognized message', msg) 81 | } 82 | }) 83 | 84 | chrome.downloads.onChanged.addListener(function(delta) { 85 | if (!delta.state ||(delta.state.current != 'complete')) { 86 | return; 87 | } 88 | try{ 89 | port.postMessage({downloadComplete: true}) 90 | } 91 | catch(e){} 92 | }); 93 | 94 | }) 95 | -------------------------------------------------------------------------------- /content_script.js: -------------------------------------------------------------------------------- 1 | window.onload = () => { 2 | if (window.recorderInjected) return 3 | Object.defineProperty(window, 'recorderInjected', { value: true, writable: false }) 4 | 5 | // Setup message passing 6 | const port = chrome.runtime.connect(chrome.runtime.id) 7 | port.onMessage.addListener(msg => window.postMessage(msg, '*')) 8 | window.addEventListener('message', event => { 9 | // Relay client messages 10 | if (event.source === window && event.data.type) { 11 | port.postMessage(event.data) 12 | } 13 | if(event.data.type === 'PLAYBACK_COMPLETE'){ 14 | port.postMessage({ type: 'REC_STOP' }, '*') 15 | } 16 | if(event.data.downloadComplete){ 17 | document.querySelector('html').classList.add('downloadComplete') 18 | } 19 | }) 20 | 21 | document.title = 'puppetcam' 22 | window.postMessage({ type: 'REC_CLIENT_PLAY', data: { url: window.location.origin } }, '*') 23 | } 24 | -------------------------------------------------------------------------------- /export.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const Xvfb = require('xvfb'); 3 | var width = 1280; 4 | var height = 720; 5 | var xvfb = new Xvfb({silent: true, xvfb_args: ["-screen", "0", `${width}x${height}x24`, "-ac"],}); 6 | var options = { 7 | headless: false, 8 | args: [ 9 | '--enable-usermedia-screen-capturing', 10 | '--allow-http-screen-capture', 11 | '--auto-select-desktop-capture-source=puppetcam', 12 | '--load-extension=' + __dirname, 13 | '--disable-extensions-except=' + __dirname, 14 | '--disable-infobars', 15 | `--window-size=${width},${height}`, 16 | ], 17 | } 18 | 19 | async function main() { 20 | xvfb.startSync() 21 | var url = process.argv[2], exportname = process.argv[3] 22 | if(!url){ url = 'http://tobiasahlin.com/spinkit/' } 23 | if(!exportname){ exportname = 'spinner.webm' } 24 | const browser = await puppeteer.launch(options) 25 | const pages = await browser.pages() 26 | const page = pages[0] 27 | await page._client.send('Emulation.clearDeviceMetricsOverride') 28 | await page.goto(url, {waitUntil: 'networkidle2'}) 29 | await page.setBypassCSP(true) 30 | 31 | // Perform any actions that have to be captured in the exported video 32 | await page.waitFor(8000) 33 | 34 | await page.evaluate(filename=>{ 35 | window.postMessage({type: 'SET_EXPORT_PATH', filename: filename}, '*') 36 | window.postMessage({type: 'REC_STOP'}, '*') 37 | }, exportname) 38 | 39 | // Wait for download of webm to complete 40 | await page.waitForSelector('html.downloadComplete', {timeout: 0}) 41 | await browser.close() 42 | xvfb.stopSync() 43 | } 44 | 45 | main() 46 | 47 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Video Capture Attempt #1", 3 | "version": "0.1.0", 4 | "manifest_version": 2, 5 | "background": { 6 | "scripts": ["background.js"] 7 | }, 8 | "content_scripts": [{ 9 | "matches": [""], 10 | "js": ["content_script.js"], 11 | "run_at": "document_start" 12 | }], 13 | "permissions": [ 14 | "desktopCapture", 15 | "", 16 | "downloads" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppetcam", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/mime-types": { 8 | "version": "2.1.0", 9 | "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz", 10 | "integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=" 11 | }, 12 | "agent-base": { 13 | "version": "5.1.1", 14 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", 15 | "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" 16 | }, 17 | "async-limiter": { 18 | "version": "1.0.1", 19 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 20 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" 21 | }, 22 | "balanced-match": { 23 | "version": "1.0.0", 24 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 25 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 26 | }, 27 | "brace-expansion": { 28 | "version": "1.1.11", 29 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 30 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 31 | "requires": { 32 | "balanced-match": "^1.0.0", 33 | "concat-map": "0.0.1" 34 | } 35 | }, 36 | "buffer-crc32": { 37 | "version": "0.2.13", 38 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 39 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" 40 | }, 41 | "buffer-from": { 42 | "version": "1.1.1", 43 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 44 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 45 | }, 46 | "concat-map": { 47 | "version": "0.0.1", 48 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 49 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 50 | }, 51 | "concat-stream": { 52 | "version": "1.6.2", 53 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 54 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 55 | "requires": { 56 | "buffer-from": "^1.0.0", 57 | "inherits": "^2.0.3", 58 | "readable-stream": "^2.2.2", 59 | "typedarray": "^0.0.6" 60 | } 61 | }, 62 | "core-util-is": { 63 | "version": "1.0.2", 64 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 65 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 66 | }, 67 | "debug": { 68 | "version": "4.1.1", 69 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 70 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 71 | "requires": { 72 | "ms": "^2.1.1" 73 | } 74 | }, 75 | "extract-zip": { 76 | "version": "1.7.0", 77 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", 78 | "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", 79 | "requires": { 80 | "concat-stream": "^1.6.2", 81 | "debug": "^2.6.9", 82 | "mkdirp": "^0.5.4", 83 | "yauzl": "^2.10.0" 84 | }, 85 | "dependencies": { 86 | "debug": { 87 | "version": "2.6.9", 88 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 89 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 90 | "requires": { 91 | "ms": "2.0.0" 92 | } 93 | }, 94 | "ms": { 95 | "version": "2.0.0", 96 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 97 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 98 | } 99 | } 100 | }, 101 | "fd-slicer": { 102 | "version": "1.1.0", 103 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 104 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", 105 | "requires": { 106 | "pend": "~1.2.0" 107 | } 108 | }, 109 | "fs.realpath": { 110 | "version": "1.0.0", 111 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 112 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 113 | }, 114 | "glob": { 115 | "version": "7.1.6", 116 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 117 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 118 | "requires": { 119 | "fs.realpath": "^1.0.0", 120 | "inflight": "^1.0.4", 121 | "inherits": "2", 122 | "minimatch": "^3.0.4", 123 | "once": "^1.3.0", 124 | "path-is-absolute": "^1.0.0" 125 | } 126 | }, 127 | "https-proxy-agent": { 128 | "version": "4.0.0", 129 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", 130 | "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", 131 | "requires": { 132 | "agent-base": "5", 133 | "debug": "4" 134 | } 135 | }, 136 | "inflight": { 137 | "version": "1.0.6", 138 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 139 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 140 | "requires": { 141 | "once": "^1.3.0", 142 | "wrappy": "1" 143 | } 144 | }, 145 | "inherits": { 146 | "version": "2.0.4", 147 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 148 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 149 | }, 150 | "isarray": { 151 | "version": "1.0.0", 152 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 153 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 154 | }, 155 | "mime": { 156 | "version": "2.4.6", 157 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", 158 | "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" 159 | }, 160 | "mime-db": { 161 | "version": "1.44.0", 162 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 163 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 164 | }, 165 | "mime-types": { 166 | "version": "2.1.27", 167 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 168 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 169 | "requires": { 170 | "mime-db": "1.44.0" 171 | } 172 | }, 173 | "minimatch": { 174 | "version": "3.0.4", 175 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 176 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 177 | "requires": { 178 | "brace-expansion": "^1.1.7" 179 | } 180 | }, 181 | "minimist": { 182 | "version": "1.2.5", 183 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 184 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 185 | }, 186 | "mkdirp": { 187 | "version": "0.5.5", 188 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 189 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 190 | "requires": { 191 | "minimist": "^1.2.5" 192 | } 193 | }, 194 | "ms": { 195 | "version": "2.1.2", 196 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 197 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 198 | }, 199 | "nan": { 200 | "version": "2.14.1", 201 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", 202 | "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", 203 | "optional": true 204 | }, 205 | "once": { 206 | "version": "1.4.0", 207 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 208 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 209 | "requires": { 210 | "wrappy": "1" 211 | } 212 | }, 213 | "path-is-absolute": { 214 | "version": "1.0.1", 215 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 216 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 217 | }, 218 | "pend": { 219 | "version": "1.2.0", 220 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 221 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" 222 | }, 223 | "process-nextick-args": { 224 | "version": "2.0.1", 225 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 226 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 227 | }, 228 | "progress": { 229 | "version": "2.0.3", 230 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 231 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 232 | }, 233 | "proxy-from-env": { 234 | "version": "1.1.0", 235 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 236 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 237 | }, 238 | "puppeteer": { 239 | "version": "2.1.0", 240 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-2.1.0.tgz", 241 | "integrity": "sha512-PC4oKMtwAElo8YtS/cYnk2/dew/3TonsGKKzjpFLWwkhBCteFsOZCVOXTt2QlP6w53mH0YsJE+fPLPzOW+DCug==", 242 | "requires": { 243 | "@types/mime-types": "^2.1.0", 244 | "debug": "^4.1.0", 245 | "extract-zip": "^1.6.6", 246 | "https-proxy-agent": "^4.0.0", 247 | "mime": "^2.0.3", 248 | "mime-types": "^2.1.25", 249 | "progress": "^2.0.1", 250 | "proxy-from-env": "^1.0.0", 251 | "rimraf": "^2.6.1", 252 | "ws": "^6.1.0" 253 | } 254 | }, 255 | "readable-stream": { 256 | "version": "2.3.7", 257 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 258 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 259 | "requires": { 260 | "core-util-is": "~1.0.0", 261 | "inherits": "~2.0.3", 262 | "isarray": "~1.0.0", 263 | "process-nextick-args": "~2.0.0", 264 | "safe-buffer": "~5.1.1", 265 | "string_decoder": "~1.1.1", 266 | "util-deprecate": "~1.0.1" 267 | } 268 | }, 269 | "rimraf": { 270 | "version": "2.7.1", 271 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 272 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 273 | "requires": { 274 | "glob": "^7.1.3" 275 | } 276 | }, 277 | "safe-buffer": { 278 | "version": "5.1.2", 279 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 280 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 281 | }, 282 | "sleep": { 283 | "version": "6.1.0", 284 | "resolved": "https://registry.npmjs.org/sleep/-/sleep-6.1.0.tgz", 285 | "integrity": "sha512-Z1x4JjJxsru75Tqn8F4tnOFeEu3HjtITTsumYUiuz54sGKdISgLCek9AUlXlVVrkhltRFhNUsJDJE76SFHTDIQ==", 286 | "optional": true, 287 | "requires": { 288 | "nan": "^2.13.2" 289 | } 290 | }, 291 | "string_decoder": { 292 | "version": "1.1.1", 293 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 294 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 295 | "requires": { 296 | "safe-buffer": "~5.1.0" 297 | } 298 | }, 299 | "typedarray": { 300 | "version": "0.0.6", 301 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 302 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 303 | }, 304 | "util-deprecate": { 305 | "version": "1.0.2", 306 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 307 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 308 | }, 309 | "wrappy": { 310 | "version": "1.0.2", 311 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 312 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 313 | }, 314 | "ws": { 315 | "version": "6.2.1", 316 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", 317 | "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", 318 | "requires": { 319 | "async-limiter": "~1.0.0" 320 | } 321 | }, 322 | "xvfb": { 323 | "version": "0.3.0", 324 | "resolved": "https://registry.npmjs.org/xvfb/-/xvfb-0.3.0.tgz", 325 | "integrity": "sha512-Ags69RYsWIxyLpnrOxPZfk3CpHy5vDuMmgg9kYE52hxfa7I5nJX1aAYh0KTFBK7nqcmKM9MuaOfPXAEmVppYsQ==", 326 | "requires": { 327 | "sleep": "6.1.0" 328 | } 329 | }, 330 | "yauzl": { 331 | "version": "2.10.0", 332 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 333 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", 334 | "requires": { 335 | "buffer-crc32": "~0.2.3", 336 | "fd-slicer": "~1.1.0" 337 | } 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppetcam", 3 | "version": "0.0.1", 4 | "description": "Capture all automated actions in a browser and export as a video", 5 | "scripts": { 6 | "example": "node export.js http://tobiasahlin.com/spinkit/ spinner.webm" 7 | }, 8 | "keywords": [ 9 | "record", 10 | "chrome", 11 | "puppeteer", 12 | "screencast" 13 | ], 14 | "dependencies": { 15 | "puppeteer": "^2.1.0", 16 | "xvfb": "^0.3.0" 17 | } 18 | } 19 | --------------------------------------------------------------------------------