├── .gitignore ├── COPYING ├── LICENSE ├── README.md ├── camera-capture-demo ├── .gitignore ├── LICENSE ├── README.md ├── formatCodeSettings.json ├── package.json ├── src │ ├── app2.ts │ ├── n.png │ ├── showModal.ts │ └── state.ts └── tsconfig.json ├── camera-capture ├── .gitignore ├── .npmignore ├── COPYING ├── LICENSE ├── README.md ├── ava.config-js.js ├── ava.config.js ├── formatCodeSettings.json ├── package-lock.json ├── package.json ├── src │ ├── assets │ │ └── buffer-5.4.3.min.js │ ├── browser.ts │ ├── capture.ts │ ├── captureBase.ts │ ├── index.html │ ├── index.ts │ ├── staticServer.ts │ └── types.ts ├── test │ ├── assets │ │ └── lenna.jpg │ ├── captureTest.ts │ ├── performanceProbes.ts │ └── recordTest.ts └── tsconfig.json ├── docs ├── README.md ├── classes │ ├── _browser_.mediarecorder.md │ ├── _capture_.videocapture.md │ └── _capturebase_.capturebase.md ├── interfaces │ ├── _browser_.startrecordingoptions.md │ ├── _types_.capturebaseoptions.md │ ├── _types_.captureoptions.md │ └── _types_.imagedata.md └── modules │ ├── _browser_.md │ ├── _capture_.md │ ├── _capturebase_.md │ ├── _index_.md │ ├── _staticserver_.md │ └── _types_.md └── saudade ├── .gitignore ├── .npmignore ├── COPYING ├── LICENSE ├── README.md ├── ava.config-js.js ├── ava.config.js ├── bin └── saudade.js ├── formatCodeSettings.json ├── package-lock.json ├── package.json ├── src ├── cli.ts └── cliMain.ts ├── test └── helpTest.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | .cache 4 | .DS_Store 5 | *.tgz 6 | .nyc_output 7 | coverage 8 | /package 9 | test-browser-outdir 10 | tmp* 11 | working_tmp 12 | bkps 13 | emscripten_prefix 14 | apps/font2bitmap 15 | apps/sample-app 16 | yode* 17 | .DS_Store 18 | node-canvas-app-test -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sebastián Gurin 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sebastián Gurin 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 | Please read [the project's README.md](camera-capture/README.md) -------------------------------------------------------------------------------- /camera-capture-demo/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | .cache 4 | .DS_Store 5 | *.tgz 6 | .nyc_output 7 | coverage 8 | /package 9 | test-browser-outdir 10 | tmp* 11 | working_tmp 12 | bkps 13 | emscripten_prefix 14 | apps/font2bitmap 15 | apps/sample-app 16 | yode* 17 | .DS_Store 18 | *.zip 19 | out-* 20 | camera-capture-demo-v* -------------------------------------------------------------------------------- /camera-capture-demo/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sebastián Gurin 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 | -------------------------------------------------------------------------------- /camera-capture-demo/README.md: -------------------------------------------------------------------------------- 1 | # camera-capture-demo 2 | 3 | ## Contents 4 | 5 | 6 | 7 | 8 | 9 | ## Demos 10 | 11 | ## Summary 12 | 13 | ## Design 14 | 15 | ## Status 16 | 17 | ## Install 18 | 19 | ## TODO / Road map 20 | 21 | -------------------------------------------------------------------------------- /camera-capture-demo/formatCodeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "insertSpaceBeforeAndAfterBinaryOperators": true, 3 | "insertSpaceAfterCommaDelimiter": true, 4 | "insertSpaceAfterSemicolonInForStatements": true, 5 | "insertSpaceAfterConstructor": false, 6 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, 7 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, 8 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, 9 | "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false, 10 | "insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, 11 | "insertSpaceAfterTypeAssertion": false, 12 | "insertSpaceBeforeFunctionParenthesis": false, 13 | "placeOpenBraceOnNewLineForFunctions": false, 14 | "placeOpenBraceOnNewLineForControlBlocks": false, 15 | "insertSpaceBeforeTypeAnnotation": false, 16 | "indentMultiLineObjectLiteralBeginningOnBlankLine": true, 17 | "indentSize": 2, 18 | "tabSize": 2, 19 | "convertTabsToSpaces": true, 20 | "quotePreference": "single", 21 | "importModuleSpecifierPreference": "relative", 22 | "importModuleSpecifierEnding": "minimal", 23 | "allowTextChangesInNewFiles": true, 24 | "trailingSemicolon": false 25 | } -------------------------------------------------------------------------------- /camera-capture-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "camera-capture-demo", 3 | "version": "0.0.3", 4 | "private": true, 5 | "description": "Demo desktop app for camera-capture", 6 | "main": "dist/src/app2.js", 7 | "scripts": { 8 | "postinstall": "npx yackage install", 9 | "clean": "rm -rf tmp* dist test-browser-outdir working_tmp out-* camera-capture-demo-v*", 10 | "build": "npm run clean && npx tsc && cp src/n.png dist/src ", 11 | "start": "npm run build && yode/yode-v0.4.2-darwin-x64/yode dist/src/app2", 12 | "format": "npx ts-refactor format \"src/**/*.ts*\" \"test/**/*.ts*\" ./formatCodeSettings.json --tsConfigPath ./tsconfig.json --dontAsk", 13 | "organizeImports": "npx ts-refactor organizeImports \"src/**/*.ts*\" \"test/**/*.ts*\" ./formatCodeSettings.json --tsConfigPath ./tsconfig.json --dontAsk ", 14 | "lint": "npm run organizeImports && npm run format", 15 | "all": "npm run clean && npm run lint && npm run build", 16 | "package-mac": " npm run build && npx yackage --app-dir $PWD dist out-mac ", 17 | "package-linux": " npm run build && npx yackage --app-dir $PWD dist out-linux ", 18 | "package-win": " npm run build && npx yackage --platform win --arch x64 --app-dir $PWD dist out-win " 19 | }, 20 | "license": "MIT", 21 | "dependencies": { 22 | "camera-capture": "0.0.11", 23 | "gui": "github:cancerberosgx/node-gui", 24 | "misc-utils-of-mine-generic": "^0.2.34", 25 | "puppeteer": "^1.20.0" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^12.7.8", 29 | "@types/puppeteer": "^1.20.0", 30 | "asar": "^2.0.1", 31 | "ts-node": "^8.4.1", 32 | "ts-refactor": "0.0.9", 33 | "typescript": "^3.6.3", 34 | "yackage": "^0.2.11" 35 | }, 36 | "author": { 37 | "name": "Sebastian Gurin", 38 | "email": "sebastigurin@gmail.com", 39 | "url": "http://sgurin.com" 40 | }, 41 | "homepage": "https://www.npmjs.com/package/camera-capture", 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/cancerberoSgx/camera-capture.git" 45 | }, 46 | "bugs": { 47 | "url": "https://github.com/cancerberoSgx/camera-capture" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /camera-capture-demo/src/app2.ts: -------------------------------------------------------------------------------- 1 | // this example takes rgba image data frames abd uses jimp to convert to jpeg to finally render in node-gui. 2 | // is not optimal - imagedata is basically not used/not supported - see app2.ts for more optimal approach 3 | import { showModal } from './showModal' 4 | import { VideoCapture, ImageData } from 'camera-capture' 5 | import { Window, Container, MessageLoop, Image, Label, Button, FileOpenDialog, FileDialog, FileSaveDialog } from 'gui' 6 | import { realpathSync, readFileSync, writeFileSync } from 'fs' 7 | import { join } from 'path' 8 | import { getExtensionsForMimeType } from 'misc-utils-of-mine-generic' 9 | import { getProp } from './state' 10 | 11 | async function main() { 12 | 13 | // let frameBuffer: Buffer = readFileSync(realpathSync(join(__dirname, 'n.png'))) 14 | let img: Image = Image.createEmpty()//.createFromBuffer(frameBuffer, 1) 15 | 16 | const win = Window.create({}) 17 | win.onClose = () => { 18 | capture.stopCamera().then(() => process.exit(0)).catch(() => process.exit(1)) 19 | } 20 | const contentView = Container.create() 21 | win.setContentView(contentView) 22 | win.setContentSize({ width: 600, height: 600, }) 23 | 24 | contentView.setStyle({ flex: 1, width: '100%', height: '100%', flexDirection: 'column' }) 25 | const options = Container.create() 26 | options.setStyle({ flex: 1, width: '100%', maxHeight: '20px', flexDirection: 'row' }) 27 | contentView.addChildView(options) 28 | 29 | 30 | const pause = Button.create('pause') 31 | pause.setStyle({ flexDirection: 'column' }) 32 | options.addChildView(pause) 33 | pause.onClick = async () => await capture.pause() 34 | 35 | const resume = Button.create('resume') 36 | resume.setStyle({ flexDirection: 'column' }) 37 | options.addChildView(resume) 38 | resume.onClick = async () => await capture.resume() 39 | 40 | const stop = Button.create('stopCamera') 41 | stop.setStyle({ flexDirection: 'column' }) 42 | options.addChildView(stop) 43 | stop.onClick = async () => { 44 | MessageLoop.postDelayedTask(200, async () => await capture.stop()) 45 | } 46 | 47 | const start = Button.create('startCamera') 48 | start.setStyle({ flexDirection: 'column' }) 49 | options.addChildView(start) 50 | start.onClick = async () => { 51 | MessageLoop.postDelayedTask(200, async () => await capture.start()) 52 | } 53 | 54 | const save = Button.create('Save photo') 55 | save.setStyle({ flexDirection: 'column' }) 56 | options.addChildView(save) 57 | save.onClick = async () => { 58 | await capture.pause() 59 | await handleSave(win, Buffer.from(lastFrame.data), getExtensionsForMimeType(getProp('mime')) || ['png']) 60 | await capture.resume() 61 | } 62 | 63 | const open = Button.create('Open file') 64 | open.setStyle({ flexDirection: 'column' }) 65 | open.onClick = async () => { 66 | await capture.pause() 67 | await showModal({ parent: win, title: 'Not implemented yet' }) 68 | await capture.resume() 69 | } 70 | options.addChildView(open) 71 | 72 | const fps = Label.create('0 FPS') 73 | fps.setStyle({ flexDirection: 'column' }) 74 | options.addChildView(fps) 75 | 76 | const startRecording = Button.create('Start recording') 77 | startRecording.setStyle({ flexDirection: 'column' }) 78 | startRecording.onClick = async () => { 79 | await capture.startRecording() 80 | } 81 | options.addChildView(startRecording) 82 | 83 | const stopRecording = Button.create('stop recording') 84 | stopRecording.setStyle({ flexDirection: 'column' }) 85 | stopRecording.onClick = async () => { 86 | const data = await capture.stopRecording() 87 | if(data){ 88 | await capture.pause() 89 | await handleSave(win, Buffer.from(data), ['webm']) 90 | await capture.resume() 91 | } 92 | } 93 | options.addChildView(stopRecording) 94 | 95 | 96 | 97 | 98 | const canvas = Container.create() 99 | canvas.setStyle({ flex: 1, width: '100%', height: '100%', flexDirection: 'row', flexGrow: 1 }) 100 | contentView.addChildView(canvas) 101 | canvas.onDraw = (self, painter, dirty) => { 102 | painter.drawImageFromRect(img, dirty, dirty) 103 | } 104 | 105 | let counter = 0 106 | setInterval(() => { 107 | fps.setText(`${counter} FPS`) 108 | counter = 0 109 | }, 1000) 110 | 111 | const capture = new VideoCapture({ 112 | debug: true, 113 | width: 480, 114 | height: 320, 115 | mkdirServed: true, 116 | port: 8081, 117 | mime: getProp('mime'), 118 | constrains: { 119 | audio: false, video: { 120 | aspectRatio: 1, 121 | width: 480, 122 | height: 320 123 | } 124 | } 125 | }) 126 | 127 | let lastFrame: ImageData 128 | capture.addFrameListener(async frame => { 129 | lastFrame = frame 130 | img = frame && frame.data && frame.data.length ? Image.createFromBuffer(frame.data, 1) : img 131 | canvas.schedulePaint() 132 | counter++ 133 | }) 134 | 135 | startEventLoop() 136 | 137 | function startEventLoop() { 138 | console.log('Starting event loop'); 139 | // heads up - we need to activate here and not after the capturer fr some reason 140 | win.center() 141 | win.activate() 142 | if (!process.versions.yode) { 143 | MessageLoop.run() 144 | } 145 | MessageLoop.postDelayedTask(1000, async () => await capture.start()) 146 | } 147 | } 148 | 149 | main() 150 | 151 | 152 | 153 | // async function handleOpen() { 154 | // const dialog = FileOpenDialog.create() 155 | // dialog.setOptions(FileDialog.optionShowHidden) 156 | // dialog.setFilters([ 157 | // { description: 'Images', extensions: knownSupportedReadWriteImageFormats }, 158 | // ]) 159 | // if (dialog.runForWindow(this.win)) { 160 | // setState(await this.buildBuffers(readFileSync(dialog.getResult()))) 161 | // } 162 | // } 163 | 164 | async function handleSave(w: Window, file: Buffer, extensions: string[]) { 165 | const dialog = FileSaveDialog.create() 166 | dialog.setOptions(FileDialog.optionShowHidden) 167 | dialog.setFilters([ 168 | { description: 'Images', extensions }, 169 | ]) 170 | if (dialog.runForWindow(w)) { 171 | // setState({ 172 | // working: 'Processing...', 173 | // }) 174 | // await sleep(20) 175 | // const result = mainSync({ 176 | // command: `convert output.miff '${basename(dialog.getResult())}'`, 177 | // inputFiles: [new File('output.miff', this.state.magicaBuffer)], 178 | // }) 179 | // if (checkError(result, this.state)) { 180 | // return 181 | // } 182 | // if (result.outputFiles.length) { 183 | writeFileSync(dialog.getResult(), file) 184 | showModal({ parent: w, title: 'File Saved', closeIn: 5000, body: 'File successfully saved at \n' + dialog.getResult() }) 185 | // } 186 | // else { 187 | // showModal({ title: 'Error', body: 'An error occurred while trying to save file \n' + dialog.getResult() + ': \n' + result.error + '\n' + result.stderr.join(', '), closeIn: 5000 , state: this.state}) 188 | // } 189 | // setState({ working: undefined }) 190 | } 191 | } 192 | 193 | -------------------------------------------------------------------------------- /camera-capture-demo/src/n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cancerberoSgx/camera-capture/b1378d4e884c13d2a9c9b6be699eafea861571ac/camera-capture-demo/src/n.png -------------------------------------------------------------------------------- /camera-capture-demo/src/showModal.ts: -------------------------------------------------------------------------------- 1 | import { View, Label, Window, Container, Button } from 'gui'; 2 | 3 | export function showModal(o: ShowModalOptions) { 4 | const p = Container.create(); 5 | p.setStyle({ flexGrow: 1, flex: 1, flexDirection: 'column' }); 6 | // if (o.state) { 7 | // p.setBackgroundColor(o.state.theme.bg); 8 | // p.setColor(o.state.theme.fg); 9 | // } 10 | const s = (o.body||o.title) 11 | const body = typeof s === 'string' ? Label.create(s) : s; 12 | body.setStyle({ flexGrow: 1, flex: 1, flexDirection: 'column' }); 13 | p.addChildView(body); 14 | const w = Window.create({ frame: true, showTrafficLights: true }); 15 | o.parent.addChildWindow(w) 16 | w.setContentView(p); 17 | w.setTitle(o.title || 'Modal'); 18 | w.setAlwaysOnTop(true); 19 | w.setContentSize({ width: 400, height: 200 }); 20 | w.setTitle(o.title || 'Modal'); 21 | const b = Button.create('Close'); 22 | p.addChildView(b); 23 | b.onClick = () => { w.close(); }; 24 | w.setVisible(true); 25 | w.center(); 26 | w.activate(); 27 | o.closeIn && setTimeout(() => w.close(), o.closeIn); 28 | } 29 | interface ShowModalOptions { 30 | body?: View | string; 31 | title: string; 32 | closeIn?: number; 33 | parent: Window 34 | } 35 | -------------------------------------------------------------------------------- /camera-capture-demo/src/state.ts: -------------------------------------------------------------------------------- 1 | import { SupportedFormats } from 'camera-capture' 2 | 3 | export interface State { 4 | mime: SupportedFormats; 5 | } 6 | 7 | let _state: State 8 | 9 | export let getProp: (nameOrPartial?: T) => State[T] = (nameOrPartial) => { 10 | if (!_state) { 11 | _state = getInitialState(); 12 | } 13 | return _state[nameOrPartial as keyof State]; 14 | } 15 | 16 | export function getState():State { 17 | return _state 18 | } 19 | 20 | export function setState(p:Partial):void { 21 | Object.assign(_state, p) 22 | } 23 | 24 | function getInitialState(): State { 25 | return { 26 | mime: 'image/jpeg' 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /camera-capture-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "lib": ["esnext", "dom"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "declaration": true, 12 | "rootDir": "." 13 | }, 14 | "include": [ 15 | "src" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /camera-capture/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | .cache 4 | .DS_Store 5 | *.tgz 6 | .nyc_output 7 | coverage 8 | /package 9 | test-browser-outdir 10 | tmp* 11 | working_tmp 12 | bkps 13 | emscripten_prefix 14 | apps/font2bitmap 15 | apps/sample-app 16 | .DS_Store -------------------------------------------------------------------------------- /camera-capture/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | buildAllProjects.sh 3 | *.log 4 | /tmp 5 | /src 6 | /.gitignore 7 | /.npmignore 8 | /dist/spec 9 | /tsconfig.json 10 | /declarations.d.ts 11 | *.tgz 12 | test 13 | typescript-ast-query-editor 14 | formatCodeSettings.json 15 | .nyc_output 16 | coverage 17 | test-browser-outdir 18 | guitarra 19 | dist/test 20 | dist/test-browser 21 | test 22 | .cache 23 | .travis.yml 24 | zangano 25 | zangano-demo 26 | astq-query* 27 | ava.config.js 28 | nyc.config.js 29 | TODO.md 30 | ts-ast 31 | .vscode 32 | guides-probes-backups 33 | test-browser 34 | .travis.yml 35 | .DS_Store 36 | /tmp* 37 | docs 38 | .cache 39 | test-browser 40 | test-browser-outdir 41 | working_tmp 42 | scripts 43 | playground 44 | bkps 45 | docs 46 | magick-wasm 47 | emscripten_prefix 48 | ava.config-js.js 49 | apps 50 | .github 51 | tmp-browser-test-testUmdWasmLocation -------------------------------------------------------------------------------- /camera-capture/COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sebastián Gurin 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 | -------------------------------------------------------------------------------- /camera-capture/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sebastián Gurin 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 | -------------------------------------------------------------------------------- /camera-capture/README.md: -------------------------------------------------------------------------------- 1 | # camera-capture 2 | 3 | Portable Camera, audio, desktop capture Node.js library. 4 | 5 | ## Contents 6 | 7 | 8 | 9 | - [What / Why ?](#what--why-) 10 | - [Install](#install) 11 | - [JavaScript API](#javascript-api) 12 | * [Managed frame read](#managed-frame-read) 13 | * [Manual frame read](#manual-frame-read) 14 | * [Recording camera video](#recording-camera-video) 15 | - [Command line](#command-line) 16 | - [Summary](#summary) 17 | * [Design summary](#design-summary) 18 | * [Status](#status) 19 | - [Reference API](#reference-api) 20 | - [TODO / Road map](#todo--road-map) 21 | * [low priority](#low-priority) 22 | 23 | 24 | 25 | ## What / Why ? 26 | 27 | After searching for an easy to use portable library to access the webcam directly from node.js I didn't found a library that works in windows, macOs and linux, without native dependencies that users need ot manually install (or even so, they won't work). 28 | 29 | This library solves the problem with an easy approach. Use headless browser to capture the video, draw in canvas and pass the image data the Node.js context as fast as possible (`age.exposeFunction()`) and with minimal processing. It uses HTMLCanvasElement getImageData when returning raw image data or HTMLCanvasElement.toBlob() when retuning encoded images such as png, jpg. In both cases using ArrayBuffer 30 | 31 | ## Install 32 | 33 | ```sh 34 | npm install camera-capture puppeteer 35 | ``` 36 | 37 | (`puppeteer` is a peer dependency you must install it by yourself) 38 | 39 | ## JavaScript API 40 | 41 | ### Managed frame read 42 | 43 | ```js 44 | import {VideoCapture} from 'camera-capture' 45 | const c = new VideoCapture() 46 | c.addFrameListener(frame => { 47 | // frame by default is unencoded raw Image Data `{width: 480, height: 320, data: UIntArray}`` 48 | // which is often what image processing / surfaces interfaces expect for fast processing. 49 | // Use `mime` option to receive it in other formats (see examples below) 50 | surface.putImageData(0,0,frame.width, frame.height, frame.data) 51 | }) 52 | // pause / resume frame emission (without tunning off the camera) 53 | setTimeout(()=>c.pause(), 1000) 54 | setTimeout(()=>c.resume(), 2000) 55 | // shutdown everything, including, camera, browser, server: 56 | setTimeout(()=>c.stop(), 3000) 57 | console.log('Capturing camera'); 58 | await c.start() // promise will be resolved only when `stop` 59 | console.log('Stopping camera capture'); 60 | ``` 61 | 62 | ### Manual frame read 63 | 64 | Instead of using start() and being notified on each frame, just call `initialize()` and read frames programmatically: 65 | 66 | ```js 67 | import {VideoCapture} from 'camera-capture' 68 | const c = new VideoCapture({ 69 | mime: 'image/png' 70 | }) 71 | await c.initialize() 72 | let f = await c.readFrame() // PNG as configured 73 | writeFileSync('tmp.png', f.data) 74 | f = await c.readFrame('image/webp') // take another shot this time as webp image 75 | writeFileSync('tmp.webp', f.data) 76 | f = await c.readFrame('image/jpeg') // jpeg 77 | writeFileSync('tmp.jpg', f.data) 78 | f = await c.readFrame('rgba') // raw image data (as default) 79 | writeFileSync('tmp-8bit-200x200.rgba', f.data) 80 | ``` 81 | 82 | ### Recording camera video 83 | 84 | The following uses DOM MediaRecorder API to record video. Notice that it all happens in the browser, on memory, so the result is a excellent quality video but it could consume lots of memory on long recordings. If that's an issue perhaps it's better to store frame by frame to hard drive and then use a video assembler like ffmpeg / imagemagick. (in the roadmap): 85 | 86 | ```ts 87 | import {VideoCapture} from 'camera-capture' 88 | const c = new VideoCapture({ port: 8082 }) 89 | await c.initialize() 90 | await c.startRecording() 91 | await sleep(500) 92 | const data = await c.stopRecording() 93 | writeFileSync('tmp6.webm', data) 94 | ``` 95 | 96 | ## Command line 97 | 98 | TODO - TBD 99 | 100 | ## Summary 101 | 102 | I didn't found any library that provides an interface to capture webcam video so I show the video and filter frame by frame in my Node.s desktop app (not based on electron - no canvas / HTML5 available - rendering on cairo/opengl surface that complies with 103 | 104 | * Don't require users to install native complex dependencies (like opencv or native applications installed) 105 | * Don't include any binary code that needs to be compiled. 106 | * works on windows, macOs, and linux 107 | * provides a stream-like API for video frames 108 | * fast so it can be used for a "real-time" video filter demo 109 | * usable without electron/canvas/html5 - imagine I'm rendering in a native surface like cairo, gtk, etc 110 | * portable - no surprises - working in latest node.js versions 111 | * Optionally the frames can be encoded as in jpg/png or even a video created . 112 | + Also provides simple filtering API. 113 | 114 | ### Design summary 115 | 116 | * Use puppeteer (which is google chrome headless browser) to capture camera video. Expose frames as fast as possible. 117 | * not focused on encoding more than the ones supported by the browser 118 | * API based on raw image data - users responsible of compose an output video with ffmpeg, imagemagick, opencv, etc. Format encoding is not the objective of this project 119 | 120 | ### Status 121 | 122 | Observed behavior: 123 | 124 | About, 30 frames per second (size 600x400, format: raw image data) 125 | 126 | * JavaScript API (managing the capturing loop) 127 | * javaScript API (manual capture) 128 | * image encoded as jpeg, png, webp 129 | * camera video recording using DOM MediaRecorder 130 | ## Reference API 131 | 132 | * [Capture options](https://github.com/cancerberoSgx/camera-capture/blob/master/docs/interfaces/_capture_.captureoptions.md) 133 | * [Capture class](https://github.com/cancerberoSgx/camera-capture/blob/master/docs/modules/_capture_.md) 134 | 135 | ## TODO / Road map 136 | 137 | ### performance 138 | - [ ] performance 2: sharing a webrtc session between node and browserthis could imply not having to read/write image data at all just consuming a video stream ? 139 | - [ ] test if toDataUrl is faster than toBlob 140 | - [ ] perhaps is faster to do the capture loop all together inside the DOM, instead calling evaluate() on each iteration? 141 | - [ ] performance 1 post frames to a node.js server , possibly using websockets 142 | 143 | ## misc 144 | 145 | - [ ] supposedly qt supports webrtc natively - oerhaos a demo connecting pptr and qt desktop app ? 146 | - [ ] probably for frames a generator / or observable is more appropriate than even listeners. 147 | - [ ] CLI 148 | - [ ] demo - stream local camera capture on a server web page. 149 | - [ ] stopVideo refactor TODOs 150 | - [ ] a free port number resolver - try until one is available (conection successful) 151 | - [ ] pause/resume / start/stop should work for recording too. 152 | - [ ] do we really need to serialize constrains ? 153 | - [ ] performance tests (fps raw image data and encoded images) 154 | - [ ] video recording formats other than webm? 155 | - [ ] video recording constraints - size - 156 | - [ ] audio recording only API 157 | - [ ] record desktop ? possible ? 158 | - [ ] desktop screenshot only API 159 | - [ ] browser screenshot only API 160 | - [ ] webcam screenshot only API 161 | - [ ] geo location (get the coords) ? (need https?) 162 | - [ ] change video size dynamically ? 163 | - [x] investigate why/how to pass the buffer / array buffer view directly without transforming it to number[] / and array buffer views 164 | - using Buffer (TextEncoder/TextDecoder to serialize the data as a single char-per-byte string (using windows-1252 encoding) and deserialize it in Node on the other side which is fast (since passing strings is much faster). 165 | - [x] check c.addFrameListener() with encoded images 166 | - [x] real world example: native app 167 | - [x] encode in browser supported formats (png, jpg) 168 | - [x] c.readFrame() users read manually instead listener - loop controlled by users. 169 | - [x] listener API managed loop 170 | - [x] API docs 171 | - [x] add api docs descriptions to class, options and 172 | - [x] record capture using dom api (output is mp4/avi video) 173 | 174 | ### low priority 175 | - [ ] research how fast/slow is painting canvas pixel by pixel from image data than showImage in node-gui 176 | - [ ] TODO: support fps control like in opencv 177 | - [ ] 178 | 179 | ## ideas 180 | - use a desktop GUI library like node-gui to render a node.js canvas - target users: people using jsdom / node canvas for testing canvas based apps headless have the possibility to render it (not just a screenshot but as actual stream of frames natively in the desktop)idea for for a project node-gui : a jsdom-node-canvas renderer: use jsdom+node-canvas to real-time render the canvas element in a view. -------------------------------------------------------------------------------- /camera-capture/ava.config-js.js: -------------------------------------------------------------------------------- 1 | export default { 2 | files: [ 3 | "dist/test/*Test.js*" 4 | ], 5 | compileEnhancements: false, 6 | serial:true, 7 | concurrency: 1, 8 | }; -------------------------------------------------------------------------------- /camera-capture/ava.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | files: [ 3 | "test/*Test.ts*" 4 | ], 5 | extensions: ['ts'], 6 | compileEnhancements: false, 7 | serial: true, 8 | concurrency: 1, 9 | // failFast:true, 10 | require: [ 11 | "ts-node/register" 12 | ] 13 | }; -------------------------------------------------------------------------------- /camera-capture/formatCodeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "insertSpaceBeforeAndAfterBinaryOperators": true, 3 | "insertSpaceAfterCommaDelimiter": true, 4 | "insertSpaceAfterSemicolonInForStatements": true, 5 | "insertSpaceAfterConstructor": false, 6 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, 7 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, 8 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, 9 | "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false, 10 | "insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, 11 | "insertSpaceAfterTypeAssertion": false, 12 | "insertSpaceBeforeFunctionParenthesis": false, 13 | "placeOpenBraceOnNewLineForFunctions": false, 14 | "placeOpenBraceOnNewLineForControlBlocks": false, 15 | "insertSpaceBeforeTypeAnnotation": false, 16 | "indentMultiLineObjectLiteralBeginningOnBlankLine": true, 17 | "indentSize": 2, 18 | "tabSize": 2, 19 | "convertTabsToSpaces": true, 20 | "quotePreference": "single", 21 | "importModuleSpecifierPreference": "relative", 22 | "importModuleSpecifierEnding": "minimal", 23 | "allowTextChangesInNewFiles": true, 24 | "trailingSemicolon": false 25 | } -------------------------------------------------------------------------------- /camera-capture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "camera-capture", 3 | "version": "0.0.11", 4 | "description": "Super portable, fast camera capture library for node.js (server). TypeScript/JavaScript easy to use APIs. Uses puppeteer headless browser to capture webcam video (audio/desktop, recording, etc) and stream back to node.js frame by frame in plain image data for optimal speed or optionally in encoded formats like jpeg, png, bmp, etc", 5 | "main": "dist/src/index.js", 6 | "types": "dist/src/index.d.ts", 7 | "scripts": { 8 | "test": "npm run build && npx ava --serial --concurrency 1 --config ava.config.js", 9 | "prepare": "npm run build", 10 | "test-js": "npm run build && npx ava --serial --concurrency 1 --config ava.config-js.js", 11 | "clean": "rm -rf tmp* dist test-browser-outdir working_tmp ../docs", 12 | "build": "npm run clean && npx tsc && npm run copy ", 13 | "copy": "mkdir -p dist/src/assets && cp src/assets/* dist/src/assets/ && cp src/index.html dist/src/index.html ", 14 | "format": "npx ts-refactor format \"src/**/*.ts*\" \"test/**/*.ts*\" ./formatCodeSettings.json --tsConfigPath ./tsconfig.json --dontAsk", 15 | "organizeImports": "npx ts-refactor organizeImports \"src/**/*.ts*\" \"test/**/*.ts*\" ./formatCodeSettings.json --tsConfigPath ./tsconfig.json --dontAsk ", 16 | "lint": "npm run organizeImports && npm run format", 17 | "docs": "npm run docs-readme-toc && npm run docs-api", 18 | "docs-api": "rm -rf ../docs && typedoc --ignoreCompilerErrors --theme markdown --out ../docs --readme none --excludeNotExported src/index.ts ", 19 | "docs-readme-toc": "npx markdown-toc README.md -i", 20 | "all": "npm run clean && npm run lint && npm run build && npm run test && npm run test-js && npm run docs", 21 | "all-publish": "npm run all && git commit -a -m 'version patch' && npm version patch && npm publish" 22 | }, 23 | "license": "MIT", 24 | "dependencies": { 25 | "misc-utils-of-mine-generic": "^0.2.34" 26 | }, 27 | "devDependencies": { 28 | "@types/file-type": "^10.9.1", 29 | "@types/node": "^12.7.8", 30 | "@types/puppeteer": "^1.20.0", 31 | "ava": "^2.4.0", 32 | "file-type": "^12.3.0", 33 | "markdown-toc": "^1.2.0", 34 | "puppeteer": "^1.20.0", 35 | "ts-node": "^8.4.1", 36 | "ts-refactor": "0.0.9", 37 | "typedoc": "^0.15.0", 38 | "typedoc-plugin-markdown": "^2.2.6", 39 | "typescript": "^3.6.3" 40 | }, 41 | "peerDependencies": { 42 | "puppeteer": "^1.20.0" 43 | }, 44 | "keywords": [ 45 | "Node.js", 46 | "camera", 47 | "webcam", 48 | "video", 49 | "capture", 50 | "record", 51 | "camera", 52 | "audio", 53 | "video record", 54 | "camera capture", 55 | "audio capture", 56 | "screenshot", 57 | "desktop" 58 | ], 59 | "author": { 60 | "name": "Sebastian Gurin", 61 | "email": "sebastigurin@gmail.com", 62 | "url": "http://sgurin.com" 63 | }, 64 | "homepage": "https://www.npmjs.com/package/camera-capture", 65 | "repository": { 66 | "type": "git", 67 | "url": "https://github.com/cancerberoSgx/camera-capture.git" 68 | }, 69 | "bugs": { 70 | "url": "https://github.com/cancerberoSgx/camera-capture" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /camera-capture/src/assets/buffer-5.4.3.min.js: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).buffer=t()}}(function(){return function(){return function t(r,e,n){function i(f,u){if(!e[f]){if(!r[f]){var s="function"==typeof require&&require;if(!u&&s)return s(f,!0);if(o)return o(f,!0);var h=new Error("Cannot find module '"+f+"'");throw h.code="MODULE_NOT_FOUND",h}var a=e[f]={exports:{}};r[f][0].call(a.exports,function(t){return i(r[f][1][t]||t)},a,a.exports,t,r,e,n)}return e[f].exports}for(var o="function"==typeof require&&require,f=0;ff)throw new RangeError('The value "'+t+'" is invalid for option "size"');var e=new Uint8Array(t);return Object.setPrototypeOf(e,r.prototype),e}function r(t,r,e){if("number"==typeof t){if("string"==typeof r)throw new TypeError('The "string" argument must be of type string. Received type number');return a(t)}return s(t,r,e)}function s(t,e,n){if("string"==typeof t)return function(t,e){"string"==typeof e&&""!==e||(e="utf8");if(!r.isEncoding(e))throw new TypeError("Unknown encoding: "+e);var n=0|l(t,e),i=u(n),o=i.write(t,e);o!==n&&(i=i.slice(0,o));return i}(t,e);if(ArrayBuffer.isView(t))return p(t);if(null==t)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if(z(t,ArrayBuffer)||t&&z(t.buffer,ArrayBuffer))return function(t,e,n){if(e<0||t.byteLength=f)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+f.toString(16)+" bytes");return 0|t}function l(t,e){if(r.isBuffer(t))return t.length;if(ArrayBuffer.isView(t)||z(t,ArrayBuffer))return t.byteLength;if("string"!=typeof t)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof t);var n=t.length,i=arguments.length>2&&!0===arguments[2];if(!i&&0===n)return 0;for(var o=!1;;)switch(e){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return P(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return j(t).length;default:if(o)return i?-1:P(t).length;e=(""+e).toLowerCase(),o=!0}}function y(t,r,e){var n=t[r];t[r]=t[e],t[e]=n}function g(t,e,n,i,o){if(0===t.length)return-1;if("string"==typeof n?(i=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),D(n=+n)&&(n=o?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(o)return-1;n=t.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof e&&(e=r.from(e,i)),r.isBuffer(e))return 0===e.length?-1:w(t,e,n,i,o);if("number"==typeof e)return e&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(t,e,n):Uint8Array.prototype.lastIndexOf.call(t,e,n):w(t,[e],n,i,o);throw new TypeError("val must be string, number or Buffer")}function w(t,r,e,n,i){var o,f=1,u=t.length,s=r.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(t.length<2||r.length<2)return-1;f=2,u/=2,s/=2,e/=2}function h(t,r){return 1===f?t[r]:t.readUInt16BE(r*f)}if(i){var a=-1;for(o=e;ou&&(e=u-s),o=e;o>=0;o--){for(var p=!0,c=0;ci&&(n=i):n=i;var o=r.length;n>o/2&&(n=o/2);for(var f=0;f>8,i=e%256,o.push(i),o.push(n);return o}(r,t.length-e),t,e,n)}function A(t,r,e){return 0===r&&e===t.length?n.fromByteArray(t):n.fromByteArray(t.slice(r,e))}function U(t,r,e){e=Math.min(t.length,e);for(var n=[],i=r;i239?4:h>223?3:h>191?2:1;if(i+p<=e)switch(p){case 1:h<128&&(a=h);break;case 2:128==(192&(o=t[i+1]))&&(s=(31&h)<<6|63&o)>127&&(a=s);break;case 3:o=t[i+1],f=t[i+2],128==(192&o)&&128==(192&f)&&(s=(15&h)<<12|(63&o)<<6|63&f)>2047&&(s<55296||s>57343)&&(a=s);break;case 4:o=t[i+1],f=t[i+2],u=t[i+3],128==(192&o)&&128==(192&f)&&128==(192&u)&&(s=(15&h)<<18|(63&o)<<12|(63&f)<<6|63&u)>65535&&s<1114112&&(a=s)}null===a?(a=65533,p=1):a>65535&&(a-=65536,n.push(a>>>10&1023|55296),a=56320|1023&a),n.push(a),i+=p}return function(t){var r=t.length;if(r<=T)return String.fromCharCode.apply(String,t);var e="",n=0;for(;nthis.length)return"";if((void 0===e||e>this.length)&&(e=this.length),e<=0)return"";if((e>>>=0)<=(r>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return L(this,r,e);case"utf8":case"utf-8":return U(this,r,e);case"ascii":return I(this,r,e);case"latin1":case"binary":return S(this,r,e);case"base64":return A(this,r,e);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return R(this,r,e);default:if(n)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),n=!0}}.apply(this,arguments)},r.prototype.toLocaleString=r.prototype.toString,r.prototype.equals=function(t){if(!r.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t||0===r.compare(this,t)},r.prototype.inspect=function(){var t="",r=e.INSPECT_MAX_BYTES;return t=this.toString("hex",0,r).replace(/(.{2})/g,"$1 ").trim(),this.length>r&&(t+=" ... "),""},o&&(r.prototype[o]=r.prototype.inspect),r.prototype.compare=function(t,e,n,i,o){if(z(t,Uint8Array)&&(t=r.from(t,t.offset,t.byteLength)),!r.isBuffer(t))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof t);if(void 0===e&&(e=0),void 0===n&&(n=t?t.length:0),void 0===i&&(i=0),void 0===o&&(o=this.length),e<0||n>t.length||i<0||o>this.length)throw new RangeError("out of range index");if(i>=o&&e>=n)return 0;if(i>=o)return-1;if(e>=n)return 1;if(this===t)return 0;for(var f=(o>>>=0)-(i>>>=0),u=(n>>>=0)-(e>>>=0),s=Math.min(f,u),h=this.slice(i,o),a=t.slice(e,n),p=0;p>>=0,isFinite(e)?(e>>>=0,void 0===n&&(n="utf8")):(n=e,e=void 0)}var i=this.length-r;if((void 0===e||e>i)&&(e=i),t.length>0&&(e<0||r<0)||r>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var o=!1;;)switch(n){case"hex":return d(this,t,r,e);case"utf8":case"utf-8":return v(this,t,r,e);case"ascii":return b(this,t,r,e);case"latin1":case"binary":return m(this,t,r,e);case"base64":return E(this,t,r,e);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return B(this,t,r,e);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},r.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var T=4096;function I(t,r,e){var n="";e=Math.min(t.length,e);for(var i=r;in)&&(e=n);for(var i="",o=r;oe)throw new RangeError("Trying to access beyond buffer length")}function O(t,e,n,i,o,f){if(!r.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>o||et.length)throw new RangeError("Index out of range")}function _(t,r,e,n,i,o){if(e+n>t.length)throw new RangeError("Index out of range");if(e<0)throw new RangeError("Index out of range")}function x(t,r,e,n,o){return r=+r,e>>>=0,o||_(t,0,e,4),i.write(t,r,e,n,23,4),e+4}function M(t,r,e,n,o){return r=+r,e>>>=0,o||_(t,0,e,8),i.write(t,r,e,n,52,8),e+8}r.prototype.slice=function(t,e){var n=this.length;(t=~~t)<0?(t+=n)<0&&(t=0):t>n&&(t=n),(e=void 0===e?n:~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),e>>=0,r>>>=0,e||C(t,r,this.length);for(var n=this[t],i=1,o=0;++o>>=0,r>>>=0,e||C(t,r,this.length);for(var n=this[t+--r],i=1;r>0&&(i*=256);)n+=this[t+--r]*i;return n},r.prototype.readUInt8=function(t,r){return t>>>=0,r||C(t,1,this.length),this[t]},r.prototype.readUInt16LE=function(t,r){return t>>>=0,r||C(t,2,this.length),this[t]|this[t+1]<<8},r.prototype.readUInt16BE=function(t,r){return t>>>=0,r||C(t,2,this.length),this[t]<<8|this[t+1]},r.prototype.readUInt32LE=function(t,r){return t>>>=0,r||C(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},r.prototype.readUInt32BE=function(t,r){return t>>>=0,r||C(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},r.prototype.readIntLE=function(t,r,e){t>>>=0,r>>>=0,e||C(t,r,this.length);for(var n=this[t],i=1,o=0;++o=(i*=128)&&(n-=Math.pow(2,8*r)),n},r.prototype.readIntBE=function(t,r,e){t>>>=0,r>>>=0,e||C(t,r,this.length);for(var n=r,i=1,o=this[t+--n];n>0&&(i*=256);)o+=this[t+--n]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*r)),o},r.prototype.readInt8=function(t,r){return t>>>=0,r||C(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},r.prototype.readInt16LE=function(t,r){t>>>=0,r||C(t,2,this.length);var e=this[t]|this[t+1]<<8;return 32768&e?4294901760|e:e},r.prototype.readInt16BE=function(t,r){t>>>=0,r||C(t,2,this.length);var e=this[t+1]|this[t]<<8;return 32768&e?4294901760|e:e},r.prototype.readInt32LE=function(t,r){return t>>>=0,r||C(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},r.prototype.readInt32BE=function(t,r){return t>>>=0,r||C(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},r.prototype.readFloatLE=function(t,r){return t>>>=0,r||C(t,4,this.length),i.read(this,t,!0,23,4)},r.prototype.readFloatBE=function(t,r){return t>>>=0,r||C(t,4,this.length),i.read(this,t,!1,23,4)},r.prototype.readDoubleLE=function(t,r){return t>>>=0,r||C(t,8,this.length),i.read(this,t,!0,52,8)},r.prototype.readDoubleBE=function(t,r){return t>>>=0,r||C(t,8,this.length),i.read(this,t,!1,52,8)},r.prototype.writeUIntLE=function(t,r,e,n){(t=+t,r>>>=0,e>>>=0,n)||O(this,t,r,e,Math.pow(2,8*e)-1,0);var i=1,o=0;for(this[r]=255&t;++o>>=0,e>>>=0,n)||O(this,t,r,e,Math.pow(2,8*e)-1,0);var i=e-1,o=1;for(this[r+i]=255&t;--i>=0&&(o*=256);)this[r+i]=t/o&255;return r+e},r.prototype.writeUInt8=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,1,255,0),this[r]=255&t,r+1},r.prototype.writeUInt16LE=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,2,65535,0),this[r]=255&t,this[r+1]=t>>>8,r+2},r.prototype.writeUInt16BE=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,2,65535,0),this[r]=t>>>8,this[r+1]=255&t,r+2},r.prototype.writeUInt32LE=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,4,4294967295,0),this[r+3]=t>>>24,this[r+2]=t>>>16,this[r+1]=t>>>8,this[r]=255&t,r+4},r.prototype.writeUInt32BE=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,4,4294967295,0),this[r]=t>>>24,this[r+1]=t>>>16,this[r+2]=t>>>8,this[r+3]=255&t,r+4},r.prototype.writeIntLE=function(t,r,e,n){if(t=+t,r>>>=0,!n){var i=Math.pow(2,8*e-1);O(this,t,r,e,i-1,-i)}var o=0,f=1,u=0;for(this[r]=255&t;++o>0)-u&255;return r+e},r.prototype.writeIntBE=function(t,r,e,n){if(t=+t,r>>>=0,!n){var i=Math.pow(2,8*e-1);O(this,t,r,e,i-1,-i)}var o=e-1,f=1,u=0;for(this[r+o]=255&t;--o>=0&&(f*=256);)t<0&&0===u&&0!==this[r+o+1]&&(u=1),this[r+o]=(t/f>>0)-u&255;return r+e},r.prototype.writeInt8=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,1,127,-128),t<0&&(t=255+t+1),this[r]=255&t,r+1},r.prototype.writeInt16LE=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,2,32767,-32768),this[r]=255&t,this[r+1]=t>>>8,r+2},r.prototype.writeInt16BE=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,2,32767,-32768),this[r]=t>>>8,this[r+1]=255&t,r+2},r.prototype.writeInt32LE=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,4,2147483647,-2147483648),this[r]=255&t,this[r+1]=t>>>8,this[r+2]=t>>>16,this[r+3]=t>>>24,r+4},r.prototype.writeInt32BE=function(t,r,e){return t=+t,r>>>=0,e||O(this,t,r,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),this[r]=t>>>24,this[r+1]=t>>>16,this[r+2]=t>>>8,this[r+3]=255&t,r+4},r.prototype.writeFloatLE=function(t,r,e){return x(this,t,r,!0,e)},r.prototype.writeFloatBE=function(t,r,e){return x(this,t,r,!1,e)},r.prototype.writeDoubleLE=function(t,r,e){return M(this,t,r,!0,e)},r.prototype.writeDoubleBE=function(t,r,e){return M(this,t,r,!1,e)},r.prototype.copy=function(t,e,n,i){if(!r.isBuffer(t))throw new TypeError("argument should be a Buffer");if(n||(n=0),i||0===i||(i=this.length),e>=t.length&&(e=t.length),e||(e=0),i>0&&i=this.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("sourceEnd out of bounds");i>this.length&&(i=this.length),t.length-e=0;--f)t[f+e]=this[f+n];else Uint8Array.prototype.set.call(t,this.subarray(n,i),e);return o},r.prototype.fill=function(t,e,n,i){if("string"==typeof t){if("string"==typeof e?(i=e,e=0,n=this.length):"string"==typeof n&&(i=n,n=this.length),void 0!==i&&"string"!=typeof i)throw new TypeError("encoding must be a string");if("string"==typeof i&&!r.isEncoding(i))throw new TypeError("Unknown encoding: "+i);if(1===t.length){var o=t.charCodeAt(0);("utf8"===i&&o<128||"latin1"===i)&&(t=o)}}else"number"==typeof t?t&=255:"boolean"==typeof t&&(t=Number(t));if(e<0||this.length>>=0,n=void 0===n?this.length:n>>>0,t||(t=0),"number"==typeof t)for(f=e;f55295&&e<57344){if(!i){if(e>56319){(r-=3)>-1&&o.push(239,191,189);continue}if(f+1===n){(r-=3)>-1&&o.push(239,191,189);continue}i=e;continue}if(e<56320){(r-=3)>-1&&o.push(239,191,189),i=e;continue}e=65536+(i-55296<<10|e-56320)}else i&&(r-=3)>-1&&o.push(239,191,189);if(i=null,e<128){if((r-=1)<0)break;o.push(e)}else if(e<2048){if((r-=2)<0)break;o.push(e>>6|192,63&e|128)}else if(e<65536){if((r-=3)<0)break;o.push(e>>12|224,e>>6&63|128,63&e|128)}else{if(!(e<1114112))throw new Error("Invalid code point");if((r-=4)<0)break;o.push(e>>18|240,e>>12&63|128,e>>6&63|128,63&e|128)}}return o}function j(t){return n.toByteArray(function(t){if((t=(t=t.split("=")[0]).trim().replace(k,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function N(t,r,e,n){for(var i=0;i=r.length||i>=t.length);++i)r[i+e]=t[i];return i}function z(t,r){return t instanceof r||null!=t&&null!=t.constructor&&null!=t.constructor.name&&t.constructor.name===r.name}function D(t){return t!=t}var F=function(){for(var t=new Array(256),r=0;r<16;++r)for(var e=16*r,n=0;n<16;++n)t[e+n]="0123456789abcdef"[r]+"0123456789abcdef"[n];return t}()}).call(this,t("buffer").Buffer)},{"base64-js":2,buffer:5,ieee754:3}],2:[function(t,r,e){"use strict";e.byteLength=function(t){var r=h(t),e=r[0],n=r[1];return 3*(e+n)/4-n},e.toByteArray=function(t){var r,e,n=h(t),f=n[0],u=n[1],s=new o(function(t,r,e){return 3*(r+e)/4-e}(0,f,u)),a=0,p=u>0?f-4:f;for(e=0;e>16&255,s[a++]=r>>8&255,s[a++]=255&r;2===u&&(r=i[t.charCodeAt(e)]<<2|i[t.charCodeAt(e+1)]>>4,s[a++]=255&r);1===u&&(r=i[t.charCodeAt(e)]<<10|i[t.charCodeAt(e+1)]<<4|i[t.charCodeAt(e+2)]>>2,s[a++]=r>>8&255,s[a++]=255&r);return s},e.fromByteArray=function(t){for(var r,e=t.length,i=e%3,o=[],f=0,u=e-i;fu?u:f+16383));1===i?(r=t[e-1],o.push(n[r>>2]+n[r<<4&63]+"==")):2===i&&(r=(t[e-2]<<8)+t[e-1],o.push(n[r>>10]+n[r>>4&63]+n[r<<2&63]+"="));return o.join("")};for(var n=[],i=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",u=0,s=f.length;u0)throw new Error("Invalid string. Length must be a multiple of 4");var e=t.indexOf("=");return-1===e&&(e=r),[e,e===r?0:4-e%4]}function a(t,r,e){for(var i,o,f=[],u=r;u>18&63]+n[o>>12&63]+n[o>>6&63]+n[63&o]);return f.join("")}i["-".charCodeAt(0)]=62,i["_".charCodeAt(0)]=63},{}],3:[function(t,r,e){e.read=function(t,r,e,n,i){var o,f,u=8*i-n-1,s=(1<>1,a=-7,p=e?i-1:0,c=e?-1:1,l=t[r+p];for(p+=c,o=l&(1<<-a)-1,l>>=-a,a+=u;a>0;o=256*o+t[r+p],p+=c,a-=8);for(f=o&(1<<-a)-1,o>>=-a,a+=n;a>0;f=256*f+t[r+p],p+=c,a-=8);if(0===o)o=1-h;else{if(o===s)return f?NaN:1/0*(l?-1:1);f+=Math.pow(2,n),o-=h}return(l?-1:1)*f*Math.pow(2,o-n)},e.write=function(t,r,e,n,i,o){var f,u,s,h=8*o-i-1,a=(1<>1,c=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,l=n?0:o-1,y=n?1:-1,g=r<0||0===r&&1/r<0?1:0;for(r=Math.abs(r),isNaN(r)||r===1/0?(u=isNaN(r)?1:0,f=a):(f=Math.floor(Math.log(r)/Math.LN2),r*(s=Math.pow(2,-f))<1&&(f--,s*=2),(r+=f+p>=1?c/s:c*Math.pow(2,1-p))*s>=2&&(f++,s/=2),f+p>=a?(u=0,f=a):f+p>=1?(u=(r*s-1)*Math.pow(2,i),f+=p):(u=r*Math.pow(2,p-1)*Math.pow(2,i),f=0));i>=8;t[e+l]=255&u,l+=y,u/=256,i-=8);for(f=f<0;t[e+l]=255&f,l+=y,f/=256,h-=8);t[e+l-y]|=128*g}},{}],4:[function(t,r,e){arguments[4][2][0].apply(e,arguments)},{dup:2}],5:[function(t,r,e){(function(r){"use strict";var n=t("base64-js"),i=t("ieee754");e.Buffer=r,e.SlowBuffer=function(t){+t!=t&&(t=0);return r.alloc(+t)},e.INSPECT_MAX_BYTES=50;var o=2147483647;function f(t){if(t>o)throw new RangeError('The value "'+t+'" is invalid for option "size"');var e=new Uint8Array(t);return e.__proto__=r.prototype,e}function r(t,r,e){if("number"==typeof t){if("string"==typeof r)throw new TypeError('The "string" argument must be of type string. Received type number');return h(t)}return u(t,r,e)}function u(t,e,n){if("string"==typeof t)return function(t,e){"string"==typeof e&&""!==e||(e="utf8");if(!r.isEncoding(e))throw new TypeError("Unknown encoding: "+e);var n=0|c(t,e),i=f(n),o=i.write(t,e);o!==n&&(i=i.slice(0,o));return i}(t,e);if(ArrayBuffer.isView(t))return a(t);if(null==t)throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if(z(t,ArrayBuffer)||t&&z(t.buffer,ArrayBuffer))return function(t,e,n){if(e<0||t.byteLength=o)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+o.toString(16)+" bytes");return 0|t}function c(t,e){if(r.isBuffer(t))return t.length;if(ArrayBuffer.isView(t)||z(t,ArrayBuffer))return t.byteLength;if("string"!=typeof t)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof t);var n=t.length,i=arguments.length>2&&!0===arguments[2];if(!i&&0===n)return 0;for(var o=!1;;)switch(e){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return P(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return j(t).length;default:if(o)return i?-1:P(t).length;e=(""+e).toLowerCase(),o=!0}}function l(t,r,e){var n=t[r];t[r]=t[e],t[e]=n}function y(t,e,n,i,o){if(0===t.length)return-1;if("string"==typeof n?(i=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),D(n=+n)&&(n=o?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(o)return-1;n=t.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof e&&(e=r.from(e,i)),r.isBuffer(e))return 0===e.length?-1:g(t,e,n,i,o);if("number"==typeof e)return e&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(t,e,n):Uint8Array.prototype.lastIndexOf.call(t,e,n):g(t,[e],n,i,o);throw new TypeError("val must be string, number or Buffer")}function g(t,r,e,n,i){var o,f=1,u=t.length,s=r.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(t.length<2||r.length<2)return-1;f=2,u/=2,s/=2,e/=2}function h(t,r){return 1===f?t[r]:t.readUInt16BE(r*f)}if(i){var a=-1;for(o=e;ou&&(e=u-s),o=e;o>=0;o--){for(var p=!0,c=0;ci&&(n=i):n=i;var o=r.length;n>o/2&&(n=o/2);for(var f=0;f>8,i=e%256,o.push(i),o.push(n);return o}(r,t.length-e),t,e,n)}function B(t,r,e){return 0===r&&e===t.length?n.fromByteArray(t):n.fromByteArray(t.slice(r,e))}function A(t,r,e){e=Math.min(t.length,e);for(var n=[],i=r;i239?4:h>223?3:h>191?2:1;if(i+p<=e)switch(p){case 1:h<128&&(a=h);break;case 2:128==(192&(o=t[i+1]))&&(s=(31&h)<<6|63&o)>127&&(a=s);break;case 3:o=t[i+1],f=t[i+2],128==(192&o)&&128==(192&f)&&(s=(15&h)<<12|(63&o)<<6|63&f)>2047&&(s<55296||s>57343)&&(a=s);break;case 4:o=t[i+1],f=t[i+2],u=t[i+3],128==(192&o)&&128==(192&f)&&128==(192&u)&&(s=(15&h)<<18|(63&o)<<12|(63&f)<<6|63&u)>65535&&s<1114112&&(a=s)}null===a?(a=65533,p=1):a>65535&&(a-=65536,n.push(a>>>10&1023|55296),a=56320|1023&a),n.push(a),i+=p}return function(t){var r=t.length;if(r<=U)return String.fromCharCode.apply(String,t);var e="",n=0;for(;nthis.length)return"";if((void 0===e||e>this.length)&&(e=this.length),e<=0)return"";if((e>>>=0)<=(r>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return S(this,r,e);case"utf8":case"utf-8":return A(this,r,e);case"ascii":return T(this,r,e);case"latin1":case"binary":return I(this,r,e);case"base64":return B(this,r,e);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return L(this,r,e);default:if(n)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),n=!0}}.apply(this,arguments)},r.prototype.toLocaleString=r.prototype.toString,r.prototype.equals=function(t){if(!r.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t||0===r.compare(this,t)},r.prototype.inspect=function(){var t="",r=e.INSPECT_MAX_BYTES;return t=this.toString("hex",0,r).replace(/(.{2})/g,"$1 ").trim(),this.length>r&&(t+=" ... "),""},r.prototype.compare=function(t,e,n,i,o){if(z(t,Uint8Array)&&(t=r.from(t,t.offset,t.byteLength)),!r.isBuffer(t))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof t);if(void 0===e&&(e=0),void 0===n&&(n=t?t.length:0),void 0===i&&(i=0),void 0===o&&(o=this.length),e<0||n>t.length||i<0||o>this.length)throw new RangeError("out of range index");if(i>=o&&e>=n)return 0;if(i>=o)return-1;if(e>=n)return 1;if(this===t)return 0;for(var f=(o>>>=0)-(i>>>=0),u=(n>>>=0)-(e>>>=0),s=Math.min(f,u),h=this.slice(i,o),a=t.slice(e,n),p=0;p>>=0,isFinite(e)?(e>>>=0,void 0===n&&(n="utf8")):(n=e,e=void 0)}var i=this.length-r;if((void 0===e||e>i)&&(e=i),t.length>0&&(e<0||r<0)||r>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var o=!1;;)switch(n){case"hex":return w(this,t,r,e);case"utf8":case"utf-8":return d(this,t,r,e);case"ascii":return v(this,t,r,e);case"latin1":case"binary":return b(this,t,r,e);case"base64":return m(this,t,r,e);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return E(this,t,r,e);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},r.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var U=4096;function T(t,r,e){var n="";e=Math.min(t.length,e);for(var i=r;in)&&(e=n);for(var i="",o=r;oe)throw new RangeError("Trying to access beyond buffer length")}function C(t,e,n,i,o,f){if(!r.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>o||et.length)throw new RangeError("Index out of range")}function O(t,r,e,n,i,o){if(e+n>t.length)throw new RangeError("Index out of range");if(e<0)throw new RangeError("Index out of range")}function _(t,r,e,n,o){return r=+r,e>>>=0,o||O(t,0,e,4),i.write(t,r,e,n,23,4),e+4}function x(t,r,e,n,o){return r=+r,e>>>=0,o||O(t,0,e,8),i.write(t,r,e,n,52,8),e+8}r.prototype.slice=function(t,e){var n=this.length;(t=~~t)<0?(t+=n)<0&&(t=0):t>n&&(t=n),(e=void 0===e?n:~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),e>>=0,r>>>=0,e||R(t,r,this.length);for(var n=this[t],i=1,o=0;++o>>=0,r>>>=0,e||R(t,r,this.length);for(var n=this[t+--r],i=1;r>0&&(i*=256);)n+=this[t+--r]*i;return n},r.prototype.readUInt8=function(t,r){return t>>>=0,r||R(t,1,this.length),this[t]},r.prototype.readUInt16LE=function(t,r){return t>>>=0,r||R(t,2,this.length),this[t]|this[t+1]<<8},r.prototype.readUInt16BE=function(t,r){return t>>>=0,r||R(t,2,this.length),this[t]<<8|this[t+1]},r.prototype.readUInt32LE=function(t,r){return t>>>=0,r||R(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},r.prototype.readUInt32BE=function(t,r){return t>>>=0,r||R(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},r.prototype.readIntLE=function(t,r,e){t>>>=0,r>>>=0,e||R(t,r,this.length);for(var n=this[t],i=1,o=0;++o=(i*=128)&&(n-=Math.pow(2,8*r)),n},r.prototype.readIntBE=function(t,r,e){t>>>=0,r>>>=0,e||R(t,r,this.length);for(var n=r,i=1,o=this[t+--n];n>0&&(i*=256);)o+=this[t+--n]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*r)),o},r.prototype.readInt8=function(t,r){return t>>>=0,r||R(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},r.prototype.readInt16LE=function(t,r){t>>>=0,r||R(t,2,this.length);var e=this[t]|this[t+1]<<8;return 32768&e?4294901760|e:e},r.prototype.readInt16BE=function(t,r){t>>>=0,r||R(t,2,this.length);var e=this[t+1]|this[t]<<8;return 32768&e?4294901760|e:e},r.prototype.readInt32LE=function(t,r){return t>>>=0,r||R(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},r.prototype.readInt32BE=function(t,r){return t>>>=0,r||R(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},r.prototype.readFloatLE=function(t,r){return t>>>=0,r||R(t,4,this.length),i.read(this,t,!0,23,4)},r.prototype.readFloatBE=function(t,r){return t>>>=0,r||R(t,4,this.length),i.read(this,t,!1,23,4)},r.prototype.readDoubleLE=function(t,r){return t>>>=0,r||R(t,8,this.length),i.read(this,t,!0,52,8)},r.prototype.readDoubleBE=function(t,r){return t>>>=0,r||R(t,8,this.length),i.read(this,t,!1,52,8)},r.prototype.writeUIntLE=function(t,r,e,n){(t=+t,r>>>=0,e>>>=0,n)||C(this,t,r,e,Math.pow(2,8*e)-1,0);var i=1,o=0;for(this[r]=255&t;++o>>=0,e>>>=0,n)||C(this,t,r,e,Math.pow(2,8*e)-1,0);var i=e-1,o=1;for(this[r+i]=255&t;--i>=0&&(o*=256);)this[r+i]=t/o&255;return r+e},r.prototype.writeUInt8=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,1,255,0),this[r]=255&t,r+1},r.prototype.writeUInt16LE=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,2,65535,0),this[r]=255&t,this[r+1]=t>>>8,r+2},r.prototype.writeUInt16BE=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,2,65535,0),this[r]=t>>>8,this[r+1]=255&t,r+2},r.prototype.writeUInt32LE=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,4,4294967295,0),this[r+3]=t>>>24,this[r+2]=t>>>16,this[r+1]=t>>>8,this[r]=255&t,r+4},r.prototype.writeUInt32BE=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,4,4294967295,0),this[r]=t>>>24,this[r+1]=t>>>16,this[r+2]=t>>>8,this[r+3]=255&t,r+4},r.prototype.writeIntLE=function(t,r,e,n){if(t=+t,r>>>=0,!n){var i=Math.pow(2,8*e-1);C(this,t,r,e,i-1,-i)}var o=0,f=1,u=0;for(this[r]=255&t;++o>0)-u&255;return r+e},r.prototype.writeIntBE=function(t,r,e,n){if(t=+t,r>>>=0,!n){var i=Math.pow(2,8*e-1);C(this,t,r,e,i-1,-i)}var o=e-1,f=1,u=0;for(this[r+o]=255&t;--o>=0&&(f*=256);)t<0&&0===u&&0!==this[r+o+1]&&(u=1),this[r+o]=(t/f>>0)-u&255;return r+e},r.prototype.writeInt8=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,1,127,-128),t<0&&(t=255+t+1),this[r]=255&t,r+1},r.prototype.writeInt16LE=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,2,32767,-32768),this[r]=255&t,this[r+1]=t>>>8,r+2},r.prototype.writeInt16BE=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,2,32767,-32768),this[r]=t>>>8,this[r+1]=255&t,r+2},r.prototype.writeInt32LE=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,4,2147483647,-2147483648),this[r]=255&t,this[r+1]=t>>>8,this[r+2]=t>>>16,this[r+3]=t>>>24,r+4},r.prototype.writeInt32BE=function(t,r,e){return t=+t,r>>>=0,e||C(this,t,r,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),this[r]=t>>>24,this[r+1]=t>>>16,this[r+2]=t>>>8,this[r+3]=255&t,r+4},r.prototype.writeFloatLE=function(t,r,e){return _(this,t,r,!0,e)},r.prototype.writeFloatBE=function(t,r,e){return _(this,t,r,!1,e)},r.prototype.writeDoubleLE=function(t,r,e){return x(this,t,r,!0,e)},r.prototype.writeDoubleBE=function(t,r,e){return x(this,t,r,!1,e)},r.prototype.copy=function(t,e,n,i){if(!r.isBuffer(t))throw new TypeError("argument should be a Buffer");if(n||(n=0),i||0===i||(i=this.length),e>=t.length&&(e=t.length),e||(e=0),i>0&&i=this.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("sourceEnd out of bounds");i>this.length&&(i=this.length),t.length-e=0;--f)t[f+e]=this[f+n];else Uint8Array.prototype.set.call(t,this.subarray(n,i),e);return o},r.prototype.fill=function(t,e,n,i){if("string"==typeof t){if("string"==typeof e?(i=e,e=0,n=this.length):"string"==typeof n&&(i=n,n=this.length),void 0!==i&&"string"!=typeof i)throw new TypeError("encoding must be a string");if("string"==typeof i&&!r.isEncoding(i))throw new TypeError("Unknown encoding: "+i);if(1===t.length){var o=t.charCodeAt(0);("utf8"===i&&o<128||"latin1"===i)&&(t=o)}}else"number"==typeof t&&(t&=255);if(e<0||this.length>>=0,n=void 0===n?this.length:n>>>0,t||(t=0),"number"==typeof t)for(f=e;f55295&&e<57344){if(!i){if(e>56319){(r-=3)>-1&&o.push(239,191,189);continue}if(f+1===n){(r-=3)>-1&&o.push(239,191,189);continue}i=e;continue}if(e<56320){(r-=3)>-1&&o.push(239,191,189),i=e;continue}e=65536+(i-55296<<10|e-56320)}else i&&(r-=3)>-1&&o.push(239,191,189);if(i=null,e<128){if((r-=1)<0)break;o.push(e)}else if(e<2048){if((r-=2)<0)break;o.push(e>>6|192,63&e|128)}else if(e<65536){if((r-=3)<0)break;o.push(e>>12|224,e>>6&63|128,63&e|128)}else{if(!(e<1114112))throw new Error("Invalid code point");if((r-=4)<0)break;o.push(e>>18|240,e>>12&63|128,e>>6&63|128,63&e|128)}}return o}function j(t){return n.toByteArray(function(t){if((t=(t=t.split("=")[0]).trim().replace(M,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function N(t,r,e,n){for(var i=0;i=r.length||i>=t.length);++i)r[i+e]=t[i];return i}function z(t,r){return t instanceof r||null!=t&&null!=t.constructor&&null!=t.constructor.name&&t.constructor.name===r.name}function D(t){return t!=t}}).call(this,t("buffer").Buffer)},{"base64-js":4,buffer:5,ieee754:6}],6:[function(t,r,e){arguments[4][3][0].apply(e,arguments)},{dup:3}]},{},[1])(1)}); -------------------------------------------------------------------------------- /camera-capture/src/browser.ts: -------------------------------------------------------------------------------- 1 | // These utilities are serialized (function.prototype.toString()) and evaluated in the browser's context so they must remain independent 2 | 3 | /** 4 | * Self contained function to transform a [Blob] into [ArrayBuffer]. Notice that this function is meant to be serialized and evaluated in a browser context that's why its dependencies must be controlled. 5 | */ 6 | export function blobToArrayBuffer(blob: Blob): Promise { 7 | return new Promise((resolve, reject) => { 8 | const reader = new FileReader() 9 | function onLoadEnd(e: any) { 10 | reader.removeEventListener('loadend', onLoadEnd, false) 11 | if (e.error) { 12 | reject(e.error) 13 | } else if (reader.result) { 14 | resolve(reader.result as ArrayBuffer) 15 | } else { 16 | reject(new Error('Expected FileReader result')) 17 | } 18 | } 19 | reader.addEventListener('loadend', onLoadEnd, false) 20 | reader.readAsArrayBuffer(blob) 21 | }) 22 | } 23 | 24 | 25 | /** 26 | * Reads given [HTMLCanvasElement] image encoded in given format and quality and return its content as [ArrayBuffer]. Depends on [blobToArrayBufferFn] which must be given or assumed to be global. Notice that this function is meant to be serialized and evaluated in a browser context that's why its dependencies must be controlled. 27 | * @param mime A DOMString indicating the image format. The default type is image/png. 28 | * @param quality A Number between 0 and 1 indicating image quality if the requested type is image/jpeg. If this argument is anything else, the default value for image quality is used. Other arguments are ignored. 29 | */ 30 | export function canvasToArrayBuffer(canvas: HTMLCanvasElement, mime: string = 'image/png', quality = 1, blobToArrayBufferFn: typeof blobToArrayBuffer = (window as any).blobToArrayBuffer): Promise { 31 | blobToArrayBufferFn = typeof blobToArrayBufferFn === 'function' ? blobToArrayBufferFn : (window as any).blobToArrayBuffer 32 | return new Promise((resolve, reject) => canvas.toBlob(async blob => { 33 | if (blob) { 34 | resolve(await blobToArrayBufferFn(blob)) 35 | } 36 | else { 37 | reject(new Error('Expected toBlob() to be defined')); 38 | } 39 | }, mime, quality)) 40 | } 41 | 42 | /** 43 | * Uses [MediaRecorder] to start recording current captured video. 44 | * Notice that this function is meant to be serialized and evaluated in a browser context that's why its dependencies must be controlled. 45 | */ 46 | export function startRecording(options: StartRecordingOptions = { video: document.querySelector('video')!, mimeType: 'video/webm;codecs=vp8', width: 480, height: 320 }) { 47 | return new Promise(resolve => { 48 | options.video = options.video || document.querySelector('video')! 49 | options.mimeType = options.mimeType || 'video/webm;codecs=vp8' 50 | options.width = options.width || 480 51 | options.height = options.height || 320 52 | const mediaSource = new MediaSource() 53 | let sourceBuffer: SourceBuffer 54 | mediaSource.addEventListener('sourceopen', () => { 55 | sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"') 56 | }, false) 57 | ; (window as any).recordedBlobs = [] as Blob[] 58 | if (!MediaRecorder.isTypeSupported(options.mimeType)) { 59 | console.error(`${options.mimeType} is not Supported`) 60 | options = { ...options, mimeType: 'video/webm' } 61 | if (!MediaRecorder.isTypeSupported(options.mimeType!)) { 62 | console.error(`${options.mimeType} is not Supported`) 63 | options = { ...options, mimeType: '' } 64 | } 65 | } 66 | try { 67 | const mediaRecorder = new MediaRecorder(options.video.srcObject as MediaStream, options) 68 | mediaRecorder.onstop = (event: any) => { 69 | console.log('Recorder stopped: ', event) 70 | } 71 | mediaRecorder.ondataavailable = (event: any) => { 72 | if (event.data && event.data.size > 0) { 73 | (window as any).recordedBlobs.push(event.data) 74 | } 75 | } 76 | mediaRecorder.start(10) // collect 10ms of data 77 | resolve() 78 | } catch (error) { 79 | console.error('Exception while creating MediaRecorder:', error, `Exception while creating MediaRecorder: ${JSON.stringify(error)}`) 80 | return 81 | } 82 | }) 83 | } 84 | 85 | export interface StartRecordingOptions { 86 | video: HTMLVideoElement 87 | mimeType?: string 88 | width?: number 89 | height?: number 90 | } 91 | 92 | // DOM MediaRecorder missing types 93 | import { TODO } from 'misc-utils-of-mine-generic' 94 | 95 | export type MediaRecorderOptions = TODO 96 | 97 | export type RecordingState = TODO 98 | 99 | export type EventHandler = TODO 100 | 101 | export type OnDataAvailableListener = (e: { 102 | data: Blob; 103 | }) => void 104 | 105 | export declare class MediaRecorder { 106 | constructor(stream: MediaStream, options: MediaRecorderOptions); 107 | readonly stream: MediaStream; 108 | readonly mimeType: string; 109 | readonly state: RecordingState; 110 | onstart: EventHandler; 111 | onstop: EventHandler; 112 | ondataavailable: OnDataAvailableListener; 113 | onpause: EventHandler; 114 | onresume: EventHandler; 115 | onerror: EventHandler; 116 | readonly videoBitsPerSecond: number; 117 | readonly audioBitsPerSecond: number; 118 | start(timeslice: number): void; 119 | stop(): void; 120 | pause(): void; 121 | resume(): void; 122 | requestData(): void; 123 | static isTypeSupported(type: string): boolean; 124 | } 125 | -------------------------------------------------------------------------------- /camera-capture/src/capture.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import { checkThrow, serial, sleep } from 'misc-utils-of-mine-generic' 3 | import { join } from 'path' 4 | import { blobToArrayBuffer, canvasToArrayBuffer, startRecording } from './browser' 5 | import { CaptureBase } from './captureBase' 6 | import { CaptureOptions, ImageData, Listener, SupportedFormats } from './types' 7 | 8 | export class VideoCapture extends CaptureBase { 9 | 10 | protected capturing = false 11 | protected initialized = false 12 | protected lastFrame?: ImageData 13 | protected listeners: Listener[] = [] 14 | /** counts [shots] */ 15 | protected counter = 0 16 | protected recording = false 17 | 18 | constructor(protected o: CaptureOptions = {}) { 19 | super(o) 20 | this.captureLoop = this.captureLoop.bind(this) 21 | this.o.width = this.o.width || 480 22 | this.o.height = this.o.height || 320 23 | } 24 | 25 | /** 26 | * Will be notified on managed frame shoot started by [start] method. Notice that this is optional and frames can be arbitrarily read using [readFrame] 27 | */ 28 | addFrameListener(listener: Listener): void { 29 | this.listeners.push(listener) 30 | } 31 | 32 | /** 33 | * Turns off the camera. frame read loop triggered by [start] won't be cleared but listeners won't be called while camera is off. 34 | * 35 | * The camera can be turned on again by calling [startCamera] 36 | */ 37 | async stopCamera() { 38 | checkThrow(this.server && this.browser && this.page, 'Expected started before calling stop()') 39 | this.initialized = false 40 | this.capturing = false 41 | if (this.recording) { 42 | await this.stopRecording(true) 43 | } 44 | await this.page!.evaluate(() => { 45 | const video = document.querySelector('video')!; 46 | (video.srcObject as MediaStream)!.getTracks().forEach(t => t.stop()) 47 | }) 48 | } 49 | 50 | /** 51 | * turns off everything, first recording , turns of camera, frame listeners and at last the server and browser. Everything can be restarted using [initialize] or alternatively [start] to restart frame listener notifications too 52 | */ 53 | async stop() { 54 | checkThrow(this.server && this.browser, 'Expected started before calling stop()') 55 | await this.stopCamera() 56 | await super.stop() 57 | await sleep(300) 58 | } 59 | 60 | /** 61 | * Won't turn off the camera but frame listeners won't be notified which will result on low cpu usage. Use it to switch between managed and manual frame read with [readFrame]. You can unpause calling [resume]. 62 | */ 63 | async pause() { 64 | checkThrow(this.server && this.browser, 'Expected started before calling stop()') 65 | this.capturing = false 66 | } 67 | 68 | isPaused() { 69 | return this.capturing 70 | } 71 | 72 | isStopped() { 73 | return this.initialized 74 | } 75 | 76 | isRecording() { 77 | return this.recording 78 | } 79 | 80 | /** 81 | * Resumes frame listener notification. See [pause]. 82 | */ 83 | async resume() { 84 | checkThrow(this.server && this.browser, 'Expected started before calling stop()') 85 | this.capturing = true 86 | } 87 | 88 | /** 89 | * Starts capturing camera video. If not calling yet it will call [initialize] and after camera is turned on it will start the capture loop, this is frame listeners notification. The loop, by default will be as fast as possible consequently with high cpu overhead. Use [pause] and[resume] to control it. Alternatively, if you just want to read frames arbitrarily y your self, just call [initialize] instead this method and use [readFrame]. 90 | */ 91 | async start() { 92 | if (this.capturing) { 93 | throw new Error('Already capturing') 94 | } 95 | await this.initialize() 96 | this.capturing = true 97 | this.counter = 0 98 | await this.captureLoop() 99 | } 100 | 101 | /** 102 | * starts servers, browser, install scripts and global functions used , and start up the video and canvas elements. 103 | * ,media streams / canvas / video in the DOM. 104 | * 105 | * After it resolves the camera should be turned on , and methods like [readFrame] and [startRecording] will be ready to be called.. 106 | */ 107 | async initialize() { 108 | if (this.initialized) { 109 | return 110 | } 111 | await super.initialize() 112 | await this.page!.addScriptTag({ content: readFileSync(join(__dirname, 'assets', 'buffer-5.4.3.min.js')).toString() }) 113 | await this.page!.evaluate((canvasToArrayBufferS, startRecordingS, blobToArrayBufferS) => { 114 | const d = document.createElement('div') 115 | d.innerHTML = `` 116 | document.body.append(d); 117 | (window as any).blobToArrayBuffer = eval(`(${blobToArrayBufferS})`); 118 | (window as any).canvasToArrayBuffer = eval(`(${canvasToArrayBufferS})`); 119 | (window as any).startRecording = eval(`(${startRecordingS})`) 120 | }, canvasToArrayBuffer.toString(), startRecording.toString(), blobToArrayBuffer.toString()) 121 | await this.startCamera() 122 | this.initialized = true 123 | } 124 | 125 | /** 126 | * Just turn on the camera using given constrains and merging them with defaults and the ones given in constructor. 127 | * 128 | * After it resolves the camera should be turned on and methods like [readFrame] and [startRecording] will be ready to be called.. 129 | */ 130 | async startCamera(o: MediaStreamConstraints = {}) { 131 | const constraints = { 132 | ...{ 133 | audio: false, 134 | video: true 135 | }, 136 | ...this.o.constrains, ...o 137 | } 138 | await this.page!.evaluate((width, height, constraints) => { 139 | return new Promise((resolve, reject) => { 140 | const video = document.querySelector('video')! 141 | const canvas = document.querySelector('canvas')! 142 | canvas.width = width 143 | canvas.height = height 144 | const parsedConstraints = JSON.parse(constraints) as MediaStreamConstraints 145 | navigator.mediaDevices.getUserMedia(parsedConstraints) 146 | .then(stream => { 147 | video.onerror = reject 148 | video.srcObject = stream 149 | video.onplay = (() => setTimeout(() => resolve(), 200)) // TODO: remove listener ? 150 | }) 151 | .catch(error => { 152 | console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name) 153 | reject(error) 154 | }) 155 | }) 156 | }, this.o.width || 480, this.o.height || 320, JSON.stringify(constraints)) 157 | } 158 | 159 | /** 160 | * Uses [MediaRecorder] to start recording current captured video. Notice that this happens 100% on memory so take a look at RAM and the time it takes [stopRecording] to encode the video file. 161 | */ 162 | async startRecording(recordOptions = { mimeType: 'video/webm;codecs=vp8', width: 480, height: 320, }) { 163 | this.recording = true 164 | await this.page!.evaluate(async (recordOptionsS) => { 165 | const options = JSON.parse(recordOptionsS) 166 | await (window as any).startRecording({ ...options, video: document.querySelector('video')! }) 167 | }, JSON.stringify(recordOptions)) 168 | } 169 | 170 | /** 171 | * Stop video recording (see [startRecording]) and resolves with a encoded video file 'video/webm' which dimensions correspond to the original video constraints. 172 | * @param discard if true it won't build the video and resolve with undefined. 173 | */ 174 | async stopRecording(discard = false) { 175 | if (!this.isRecording()) { 176 | return 177 | } 178 | this.recording = false 179 | const data = await this.page!.evaluate((discard) => { 180 | return new Promise(resolve => { 181 | //TODO: move to browser and use blobToArrayBuffer 182 | // TODO: returns Promise 183 | // TODO: use buffer here too to pass data 184 | if (discard) { 185 | (window as any).recordedBlobs = [] 186 | resolve() 187 | } else { 188 | const blob = new Blob((window as any).recordedBlobs, { type: 'video/webm' }) 189 | const fr = new FileReader() 190 | fr.readAsArrayBuffer(blob) 191 | fr.onloadend = () => { 192 | const data = fr.result as ArrayBuffer 193 | resolve(Array.from(new Uint8ClampedArray(data))); 194 | (window as any).recordedBlobs = [] 195 | } 196 | } 197 | }) 198 | }, discard) 199 | return data ? new Uint8ClampedArray(data) : undefined 200 | } 201 | 202 | /** 203 | * Main public method to capture the current frame on the video camera. [initialized] must be called first. Then this method can be called at will, optionally providing desired image output format. 204 | * 205 | * It will work even if [isPaused] since is independent of managed frame listening. 206 | * 207 | * Tip: right now, 'image/jpeg' seems to be faster then the rest, even rgba, on a x2.5 ratio (Reading jpg 30 fps on a canvas size 480, 320).. Nevertheless this could change/improve in the future. 208 | */ 209 | async readFrame(mime: SupportedFormats = this.o.mime || 'rgba', quality = 1) { 210 | if (this.initialized) { 211 | //TODO. perhaps is faster to do the capture loop all together inside the DOM, instead calling evaluate() on each iteration? - if so we must call node.js function cause cannot return. 212 | const data = await this.page!.evaluate(async (mime: SupportedFormats = 'rgba', width: number, height: number, quality: number) => { 213 | const video = document.querySelector('video')! 214 | const canvas = document.querySelector('canvas')! 215 | canvas.getContext('2d')!.drawImage(video, 0, 0, canvas.width, canvas.height) 216 | if (mime === 'rgba') { 217 | const data = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height) 218 | return { width: data.width, height: data.height, data: (window as any).buffer.Buffer.from(data.data).toString('binary') as string } 219 | } else { 220 | const data = await (window as any).canvasToArrayBuffer(canvas, mime, quality, (window as any).blobToArrayBuffer) 221 | if (data) { 222 | return { width, height, data: (window as any).buffer.Buffer.from(data).toString('binary') as string } 223 | } else { 224 | // console.error('123'); 225 | 226 | throw new Error('Should not happen 1234') 227 | } 228 | } 229 | }, mime, this.o.width!, this.o.height!, quality) 230 | const imageData = { 231 | width: data.width, 232 | height: data.height, 233 | data: Buffer.from(data.data, 'binary') 234 | } 235 | await this.notifyListeners(imageData as any) 236 | return imageData 237 | } else { 238 | throw new Error('Expected to be initialized') 239 | } 240 | } 241 | 242 | protected async notifyListeners(d: ImageData) { 243 | await serial(this.listeners.map(l => async () => { 244 | await l(d) 245 | })) 246 | } 247 | 248 | protected async captureLoop() { 249 | if (!this.initialized || this.o.shots && this.counter >= this.o.shots) { 250 | return 251 | } 252 | if (this.capturing) { 253 | await this.readFrame() 254 | this.counter++ 255 | await sleep(0) 256 | } else { 257 | await sleep(100) 258 | } 259 | await this.captureLoop() 260 | } 261 | 262 | 263 | } 264 | 265 | -------------------------------------------------------------------------------- /camera-capture/src/captureBase.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'fs' 2 | import { Server } from 'http' 3 | import { mergeRecursive, notSameNotFalsy, sleep } from 'misc-utils-of-mine-generic' 4 | import { tmpdir } from 'os' 5 | import { join } from 'path' 6 | import puppeteer from 'puppeteer' 7 | import { staticServer } from './staticServer' 8 | import { CaptureBaseOptions } from './types' 9 | 10 | export class CaptureBase { 11 | 12 | protected server?: Server 13 | protected browser?: puppeteer.Browser 14 | protected page?: puppeteer.Page 15 | 16 | constructor(protected o: CaptureBaseOptions = {}) { 17 | } 18 | 19 | async stop() { 20 | await sleep(500) 21 | await this.page!.close() 22 | await sleep(500) 23 | await this.browser!.close() 24 | await sleep(500) 25 | await this.server!.close() 26 | await sleep(500) 27 | } 28 | 29 | async initialize() { 30 | const dir = this.o.mkdirServed === true ? join(tmpdir(), 'camera-capture') : typeof this.o.mkdirServed === 'string' ? this.o.mkdirServed : __dirname 31 | if (this.o.mkdirServed && !existsSync(join(dir, 'index.html'))) { 32 | mkdirSync(dir, { recursive: true }) 33 | writeFileSync(join(dir, 'index.html'), readFileSync(realpathSync(join(__dirname, 'index.html')))) 34 | } 35 | try { 36 | this.server = await staticServer(dir, this.o.port || 8080) 37 | this.o.debug && console.log('Serving index.html on port ' + (this.o.port || 8080) + '. Folder: "' + dir + '"') 38 | } catch (error) { 39 | console.error('Error while opening server on port ' + (this.o.port || 8080) + '. Folder: "' + dir + '": ', error, error.stack) 40 | } 41 | const pOptions = mergeRecursive({ 42 | ...{}, 43 | ...this.o.puppeteerOptions 44 | }, 45 | { 46 | headless: true, 47 | args: ['--disable-web-security', '--allow-file-access', '--use-fake-ui-for-media-stream', ...this.o.puppeteerOptions && this.o.puppeteerOptions.args || []].filter(notSameNotFalsy) 48 | }) 49 | this.o.debug && console.log(`Puppeteer options: ${JSON.stringify(pOptions)}`) 50 | this.browser = await puppeteer.launch(pOptions) 51 | this.page = await this.browser.newPage() 52 | this.page.on('console', e => { 53 | if (e.type() === 'error') { 54 | console.error('error: ' + JSON.stringify(e.location()) + '\n' + e.text().split('\n').join('\n')) 55 | } 56 | console.log('log: ' + JSON.stringify(e.location()) + '\n' + e.text()) 57 | }) 58 | this.o.debug && console.log(`Opening address '${`http://127.0.0.1:${this.o.port || 8080}/index.html`}'`) 59 | await this.page.goto(`http://127.0.0.1:${this.o.port || 8080}/index.html`) 60 | await sleep(500) 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /camera-capture/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | video-capture 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /camera-capture/src/index.ts: -------------------------------------------------------------------------------- 1 | import { flatInstallArrayPrototype } from 'misc-utils-of-mine-generic' 2 | flatInstallArrayPrototype() 3 | 4 | export * from './capture' 5 | export * from './types' 6 | -------------------------------------------------------------------------------- /camera-capture/src/staticServer.ts: -------------------------------------------------------------------------------- 1 | import { createReadStream } from "fs" 2 | import { createServer, Server } from "http" 3 | import { Fn } from 'misc-utils-of-mine-generic' 4 | import { join } from 'path' 5 | 6 | export function staticServer(basePath: string, port = 9999, onFound: Fn = () => { }, onNotFound: Fn = () => { }): Promise { 7 | return new Promise(async (resolve) => { 8 | const server = createServer((req, res) => { 9 | var url = resolveUrl(req.url) 10 | onFound && onFound(url) 11 | var stream = createReadStream(join(basePath, url || '')) 12 | stream.on('error', function() { 13 | onNotFound && onNotFound(url) 14 | res.writeHead(404) 15 | res.end() 16 | }) 17 | stream.pipe(res) 18 | }).listen(port) 19 | server.on('listening', () => { 20 | resolve(server) 21 | }) 22 | }) 23 | function resolveUrl(url = '') { 24 | var i = url.indexOf('?') 25 | if (i != -1) { 26 | url = url.substr(0, i) 27 | } 28 | i = url.indexOf('#') 29 | if (i != -1) { 30 | url = url.substr(0, i) 31 | } 32 | return url 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /camera-capture/src/types.ts: -------------------------------------------------------------------------------- 1 | import { LaunchOptions } from 'puppeteer' 2 | 3 | export interface CaptureBaseOptions { 4 | /** 5 | * Image-capture needs to serve a static HTML so the headless browser capture media from there. If given port number will be used for the server. Default is 8080. 6 | */ 7 | port?: number 8 | puppeteerOptions?: LaunchOptions 9 | /** 10 | * If given, a folder with that path will be created (or if true is given at $HOME/.camera-capture/static) and a index.html file will be copied there so it can be served as static web page for the headless browser to use. This is not often necessary, but particularly when packaging the client package as a desktop application with asar or yackage . 11 | */ 12 | mkdirServed?: string | boolean 13 | debug?: boolean; 14 | } 15 | 16 | export type SupportedFormats = 'image/png' | 'image/jpeg' | 'image/webp' | 'rgba' 17 | 18 | export interface CaptureOptions extends CaptureBaseOptions { 19 | constrains?: MediaStreamConstraints; 20 | /** 21 | * TODO. Make sure that at least given number of milliseconds pass between frames 22 | */ 23 | interval?: number; 24 | /** 25 | * Takes given number of shots and stops. 26 | */ 27 | shots?: number; 28 | /** 29 | * Frames width in pixels. Default is 480. 30 | */ 31 | width?: number; 32 | /** 33 | * Frames height in pixels. Default is 320. 34 | */ 35 | height?: number; 36 | /** 37 | * If given it will make sure frames speed is no greater than given number of frames per second. 38 | */ 39 | fps?: number; 40 | /** 41 | * Output frames image format. Accepted values: `image/png`, `image/jpeg`, `image/webp`, `rgba`. By default is `rgba` which is raw data image 8-bit depth RGBA - as in HTML's ImageData object. 42 | */ 43 | mime?: SupportedFormats; 44 | } 45 | 46 | export type Listener = (data: ImageData) => void | Promise 47 | 48 | export interface ImageData { 49 | width: number; 50 | height: number; 51 | data: Uint8ClampedArray; 52 | } 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /camera-capture/test/assets/lenna.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cancerberoSgx/camera-capture/b1378d4e884c13d2a9c9b6be699eafea861571ac/camera-capture/test/assets/lenna.jpg -------------------------------------------------------------------------------- /camera-capture/test/captureTest.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import fileType, { FileTypeResult } from 'file-type' 3 | import { readFileSync, writeFileSync } from 'fs' 4 | import { unique } from 'misc-utils-of-mine-generic' 5 | import { VideoCapture } from '../src/capture' 6 | 7 | test.serial.cb('addFrameListener single ', t => { 8 | const c = new VideoCapture({ port: 8082, width: 480, height: 360 }) 9 | c.addFrameListener(async frame => { 10 | t.deepEqual([frame.width, frame.height, frame.data.length], [480, 360, 691200]) 11 | await c.stop() 12 | t.end() 13 | }) 14 | c.start() 15 | }) 16 | 17 | test.serial.cb('addFrameListener multiple ', t => { 18 | let i = 0 19 | let t0 = Infinity 20 | const N = 5 21 | const c = new VideoCapture({ 22 | width: 200, height: 200, port: 8084 23 | }) 24 | c.addFrameListener(async frame => { 25 | i++ 26 | t.deepEqual([frame.width, frame.height, frame.data.length], [200, 200, 160000]) 27 | if (i > N) { 28 | await c.stop() 29 | t.end() 30 | } 31 | }) 32 | c.initialize().then(() => { 33 | c.start() 34 | }) 35 | }) 36 | 37 | test.serial('addFrameListener multi encoded', async t => { 38 | const c = new VideoCapture({ port: 8083, width: 480, height: 360, mime: 'image/jpeg', shots: 3 }) 39 | const frames: ([FileTypeResult | undefined, number, number])[] = [] 40 | c.addFrameListener(frame => { 41 | writeFileSync(unique('tmp') + '.jpg', frame.data) 42 | frames.push([fileType(frame.data), frame.width, frame.height]) 43 | }) 44 | await c.start() 45 | t.deepEqual(frames, [ 46 | [{ ext: 'jpg', mime: 'image/jpeg' }, 480, 360], 47 | [{ ext: 'jpg', mime: 'image/jpeg' }, 480, 360], 48 | [{ ext: 'jpg', mime: 'image/jpeg' }, 480, 360] 49 | ]) 50 | await c.stop() 51 | }) 52 | 53 | test.serial('users requesting frames instead notifications', async t => { 54 | const c = new VideoCapture({ 55 | width: 100, height: 100, port: 8085 56 | }) 57 | await c.initialize() 58 | const f = await c.readFrame() 59 | const f2 = await c.readFrame() 60 | t.deepEqual([f.width, f.height, f.data.length], [100, 100, 40000]) 61 | t.deepEqual([f2.width, f2.height, f2.data.length], [100, 100, 40000]) 62 | t.true(f !== f2) 63 | await c.stop() 64 | }) 65 | 66 | test.serial('encoded frames - default format given in options', async t => { 67 | const c = new VideoCapture({ 68 | width: 480, height: 320, port: 8086, mime: 'image/png' 69 | }) 70 | await c.initialize() 71 | const f = await c.readFrame() 72 | t.deepEqual(fileType(Buffer.from(f.data)), { ext: 'png', mime: 'image/png' }) 73 | writeFileSync('tmp.png', f.data) 74 | t.deepEqual(fileType(readFileSync('tmp.png')), { ext: 'png', mime: 'image/png' }) 75 | 76 | const f2 = await c.readFrame('image/jpeg') 77 | t.deepEqual(fileType(Buffer.from(f2.data)), { ext: 'jpg', mime: 'image/jpeg' }) 78 | writeFileSync('tmp.jpg', f2.data) 79 | t.deepEqual(fileType(readFileSync('tmp.jpg')), { ext: 'jpg', mime: 'image/jpeg' }) 80 | 81 | const f3 = await c.readFrame('image/webp') 82 | t.deepEqual(fileType(f3.data.buffer), { ext: 'webp', mime: 'image/webp' }) 83 | writeFileSync('tmp.webp', f3.data) 84 | t.deepEqual(fileType(readFileSync('tmp.webp')), { ext: 'webp', mime: 'image/webp' }) 85 | 86 | await c.stop() 87 | }) 88 | 89 | test.todo('pause, resume, stop') 90 | -------------------------------------------------------------------------------- /camera-capture/test/performanceProbes.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs' 2 | import { sleep } from 'misc-utils-of-mine-generic' 3 | import { VideoCapture } from '../src/capture' 4 | 5 | async function rgba480x320() { 6 | const c = new VideoCapture({ port: 8012, width: 480, height: 320, mime: 'rgba', constrains: { video: { width: 480, height: 320 } } }) 7 | let counter = 0 8 | c.addFrameListener(f => { 9 | counter++ 10 | }) 11 | const t = setInterval(() => { 12 | console.log(`rgba480x320 ${counter++} FPS`) 13 | counter = 0 14 | }, 1000) 15 | setTimeout(async () => { 16 | clearInterval(t) 17 | await sleep(1200) 18 | c.stop() 19 | }, 15000) 20 | await c.start() 21 | } 22 | // rgba480x320() 23 | 24 | async function jpg480x320() { 25 | const c = new VideoCapture({ port: 8013, width: 480, height: 320, mime: 'image/jpeg', constrains: { video: { width: 480, height: 320 } } }) 26 | let counter = 0 27 | c.addFrameListener(f => { 28 | counter++ 29 | }) 30 | const t = setInterval(() => { 31 | console.log(`jpg480x320 ${counter++} FPS`) 32 | counter = 0 33 | }, 1000) 34 | setTimeout(async () => { 35 | clearInterval(t) 36 | writeFileSync('tmp_jpg480x320.jpg', (await c.readFrame()).data) 37 | await sleep(1200) 38 | await c.stop() 39 | }, 5000) 40 | await c.start() 41 | } 42 | 43 | 44 | 45 | (async () => { 46 | // await rgba480x320() 47 | await jpg480x320() 48 | })() 49 | 50 | 51 | // async function perfCheat1() { 52 | // class C extends CaptureBase { 53 | // // hack = new SharedArrayBuffer(200)// {data: {}} 54 | // async initialize() { 55 | // // this.hack. 56 | // await super.initialize() 57 | // // console.log(this.hack); 58 | // await this.page!.exposeFunction('hack', () => { 59 | 60 | // }) 61 | // // await this.page!.exposeFunction('hack', ()=>this.hack) 62 | 63 | // await this.page!.evaluate(() => { 64 | // (window as any).arr = new Uint8ClampedArray(1024 * 1024); 65 | // }) 66 | 67 | // console.time('a') 68 | // const test1 = new Uint8ClampedArray(await this.page!.evaluate(() => { 69 | // return Array.from((window as any).arr) as number[] 70 | // })) 71 | // console.timeEnd('a') 72 | // console.log(test1.length / 1024 / 1024); 73 | 74 | 75 | // console.time('b') 76 | // const test2 = new TextEncoder().encode(await this.page!.evaluate(() => { 77 | // // new TextEncoder().encode() 78 | // return new TextDecoder().decode((window as any).arr) 79 | // // return Array.from((window as any).arr) as number[] 80 | // })) 81 | // console.timeEnd('b') 82 | // console.log(test2.length / 1024 / 1024); 83 | 84 | 85 | // // const d = new TextDecoder('s', {encoding:'windows-1252'}) 86 | // // d.encoding='windows-1252' 87 | // // await this.page!.evaluate((s)=>{ 88 | // // (window as any).fileContents3 = new Uint8ClampedArray(new TextEncoder().encode(s).buffer) 89 | // // }, new TextDecoder().decode(new Uint8ClampedArray(readFileSync('test/assets/lenna.jpg').buffer))) 90 | 91 | // // await this.page!.evaluate((s)=>{ 92 | // // try { 93 | // // // eval(s) 94 | // // //@ts-ignore 95 | // // console.log(window.Buffer, window.require) 96 | 97 | // // } catch (error) { 98 | // // console.error(error) 99 | // // } 100 | // // }, readFileSync('test/assets/buffer-5.4.3.min.js').toString()) 101 | // await this.page!.addScriptTag({content: readFileSync('src/assets/buffer-5.4.3.min.js').toString() }) 102 | 103 | 104 | // // load 'buffer' library. It will be available at window.buffer.Buffer. 105 | // // await this.page!.evaluate((s) => eval(s), readFileSync('test/assets/buffer-5.4.3.min.js').toString()) 106 | 107 | // // store an image as a Buffer in window.fileContents3 global variable (simulates the image array buffer view extracted from canvas) 108 | // await this.page!.evaluate((s) => 109 | // // @ts-ignore 110 | // window.fileContents3 = window.buffer.Buffer.from(s, 'binary') 111 | // , readFileSync('test/assets/lenna.jpg').toString('binary')) 112 | 113 | // console.time('c') 114 | // // get the encoded image as Node.js Buffer 115 | // const fileContents = Buffer.from(await this.page!.evaluate(() => 116 | // // @ts-ignore 117 | // window.fileContents3.toString('binary') 118 | // ), 'binary') 119 | // console.timeEnd('c') 120 | 121 | // writeFileSync('tmp_l1.jpg', fileContents) 122 | 123 | // // console.log(test3.length/1024/1024); 124 | // // writeFileSync('tmp_l.jpg',Buffer.from(test3) ) 125 | // // // writeFileSync('tmp_l2.jpg', readFileSync('test/assets/lenna.jpg' ).toString('binary'), {encoding: 'binary'}) 126 | // // // writeFileSync('tmp_l4.jpg',new Uint8ClampedArray(test3)) 127 | // // // writeFileSync('tmp_l5.jpg',new Uint8ClampedArray(test3.buffer)) 128 | // } 129 | // } 130 | // const c = new C({ port: 8014 }) 131 | // await c.initialize() 132 | // } 133 | // perfCheat1() 134 | -------------------------------------------------------------------------------- /camera-capture/test/recordTest.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import fileType from 'file-type' 3 | import { sleep } from 'misc-utils-of-mine-generic' 4 | import { VideoCapture } from '../src/capture' 5 | 6 | test('simple recording', async t => { 7 | const c = new VideoCapture({ port: 8082 }) 8 | await c.initialize() 9 | await c.startRecording() 10 | await sleep(500) 11 | const data = await c.stopRecording() 12 | t.deepEqual(fileType(data!.buffer), { ext: 'webm', mime: 'video/webm' }) 13 | }) 14 | -------------------------------------------------------------------------------- /camera-capture/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "lib": ["esnext", "dom"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "declaration": true, 12 | "rootDir": "." 13 | }, 14 | "include": [ 15 | "src", "test" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | [camera-capture](README.md) 2 | 3 | # camera-capture 4 | 5 | ## Index 6 | 7 | ### External modules 8 | 9 | * ["browser"](modules/_browser_.md) 10 | * ["capture"](modules/_capture_.md) 11 | * ["captureBase"](modules/_capturebase_.md) 12 | * ["index"](modules/_index_.md) 13 | * ["staticServer"](modules/_staticserver_.md) 14 | * ["types"](modules/_types_.md) 15 | -------------------------------------------------------------------------------- /docs/classes/_browser_.mediarecorder.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["browser"](../modules/_browser_.md) › [MediaRecorder](_browser_.mediarecorder.md) 2 | 3 | # Class: MediaRecorder 4 | 5 | ## Hierarchy 6 | 7 | * **MediaRecorder** 8 | 9 | ## Index 10 | 11 | ### Constructors 12 | 13 | * [constructor](_browser_.mediarecorder.md#constructor) 14 | 15 | ### Properties 16 | 17 | * [audioBitsPerSecond](_browser_.mediarecorder.md#audiobitspersecond) 18 | * [mimeType](_browser_.mediarecorder.md#mimetype) 19 | * [ondataavailable](_browser_.mediarecorder.md#ondataavailable) 20 | * [onerror](_browser_.mediarecorder.md#onerror) 21 | * [onpause](_browser_.mediarecorder.md#onpause) 22 | * [onresume](_browser_.mediarecorder.md#onresume) 23 | * [onstart](_browser_.mediarecorder.md#onstart) 24 | * [onstop](_browser_.mediarecorder.md#onstop) 25 | * [state](_browser_.mediarecorder.md#state) 26 | * [stream](_browser_.mediarecorder.md#stream) 27 | * [videoBitsPerSecond](_browser_.mediarecorder.md#videobitspersecond) 28 | 29 | ### Methods 30 | 31 | * [pause](_browser_.mediarecorder.md#pause) 32 | * [requestData](_browser_.mediarecorder.md#requestdata) 33 | * [resume](_browser_.mediarecorder.md#resume) 34 | * [start](_browser_.mediarecorder.md#start) 35 | * [stop](_browser_.mediarecorder.md#stop) 36 | * [isTypeSupported](_browser_.mediarecorder.md#static-istypesupported) 37 | 38 | ## Constructors 39 | 40 | ### constructor 41 | 42 | \+ **new MediaRecorder**(`stream`: MediaStream, `options`: [MediaRecorderOptions](../modules/_browser_.md#mediarecorderoptions)): *[MediaRecorder](_browser_.mediarecorder.md)* 43 | 44 | *Defined in [browser.ts:105](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L105)* 45 | 46 | **Parameters:** 47 | 48 | Name | Type | 49 | ------ | ------ | 50 | `stream` | MediaStream | 51 | `options` | [MediaRecorderOptions](../modules/_browser_.md#mediarecorderoptions) | 52 | 53 | **Returns:** *[MediaRecorder](_browser_.mediarecorder.md)* 54 | 55 | ## Properties 56 | 57 | ### audioBitsPerSecond 58 | 59 | • **audioBitsPerSecond**: *number* 60 | 61 | *Defined in [browser.ts:117](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L117)* 62 | 63 | ___ 64 | 65 | ### mimeType 66 | 67 | • **mimeType**: *string* 68 | 69 | *Defined in [browser.ts:108](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L108)* 70 | 71 | ___ 72 | 73 | ### ondataavailable 74 | 75 | • **ondataavailable**: *[OnDataAvailableListener](../modules/_browser_.md#ondataavailablelistener)* 76 | 77 | *Defined in [browser.ts:112](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L112)* 78 | 79 | ___ 80 | 81 | ### onerror 82 | 83 | • **onerror**: *[EventHandler](../modules/_browser_.md#eventhandler)* 84 | 85 | *Defined in [browser.ts:115](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L115)* 86 | 87 | ___ 88 | 89 | ### onpause 90 | 91 | • **onpause**: *[EventHandler](../modules/_browser_.md#eventhandler)* 92 | 93 | *Defined in [browser.ts:113](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L113)* 94 | 95 | ___ 96 | 97 | ### onresume 98 | 99 | • **onresume**: *[EventHandler](../modules/_browser_.md#eventhandler)* 100 | 101 | *Defined in [browser.ts:114](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L114)* 102 | 103 | ___ 104 | 105 | ### onstart 106 | 107 | • **onstart**: *[EventHandler](../modules/_browser_.md#eventhandler)* 108 | 109 | *Defined in [browser.ts:110](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L110)* 110 | 111 | ___ 112 | 113 | ### onstop 114 | 115 | • **onstop**: *[EventHandler](../modules/_browser_.md#eventhandler)* 116 | 117 | *Defined in [browser.ts:111](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L111)* 118 | 119 | ___ 120 | 121 | ### state 122 | 123 | • **state**: *[RecordingState](../modules/_browser_.md#recordingstate)* 124 | 125 | *Defined in [browser.ts:109](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L109)* 126 | 127 | ___ 128 | 129 | ### stream 130 | 131 | • **stream**: *MediaStream* 132 | 133 | *Defined in [browser.ts:107](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L107)* 134 | 135 | ___ 136 | 137 | ### videoBitsPerSecond 138 | 139 | • **videoBitsPerSecond**: *number* 140 | 141 | *Defined in [browser.ts:116](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L116)* 142 | 143 | ## Methods 144 | 145 | ### pause 146 | 147 | ▸ **pause**(): *void* 148 | 149 | *Defined in [browser.ts:120](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L120)* 150 | 151 | **Returns:** *void* 152 | 153 | ___ 154 | 155 | ### requestData 156 | 157 | ▸ **requestData**(): *void* 158 | 159 | *Defined in [browser.ts:122](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L122)* 160 | 161 | **Returns:** *void* 162 | 163 | ___ 164 | 165 | ### resume 166 | 167 | ▸ **resume**(): *void* 168 | 169 | *Defined in [browser.ts:121](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L121)* 170 | 171 | **Returns:** *void* 172 | 173 | ___ 174 | 175 | ### start 176 | 177 | ▸ **start**(`timeslice`: number): *void* 178 | 179 | *Defined in [browser.ts:118](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L118)* 180 | 181 | **Parameters:** 182 | 183 | Name | Type | 184 | ------ | ------ | 185 | `timeslice` | number | 186 | 187 | **Returns:** *void* 188 | 189 | ___ 190 | 191 | ### stop 192 | 193 | ▸ **stop**(): *void* 194 | 195 | *Defined in [browser.ts:119](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L119)* 196 | 197 | **Returns:** *void* 198 | 199 | ___ 200 | 201 | ### `Static` isTypeSupported 202 | 203 | ▸ **isTypeSupported**(`type`: string): *boolean* 204 | 205 | *Defined in [browser.ts:123](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L123)* 206 | 207 | **Parameters:** 208 | 209 | Name | Type | 210 | ------ | ------ | 211 | `type` | string | 212 | 213 | **Returns:** *boolean* 214 | -------------------------------------------------------------------------------- /docs/classes/_capture_.videocapture.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["capture"](../modules/_capture_.md) › [VideoCapture](_capture_.videocapture.md) 2 | 3 | # Class: VideoCapture 4 | 5 | ## Hierarchy 6 | 7 | * [CaptureBase](_capturebase_.capturebase.md) 8 | 9 | ↳ **VideoCapture** 10 | 11 | ## Index 12 | 13 | ### Constructors 14 | 15 | * [constructor](_capture_.videocapture.md#constructor) 16 | 17 | ### Properties 18 | 19 | * [browser](_capture_.videocapture.md#protected-optional-browser) 20 | * [capturing](_capture_.videocapture.md#protected-capturing) 21 | * [counter](_capture_.videocapture.md#protected-counter) 22 | * [initialized](_capture_.videocapture.md#protected-initialized) 23 | * [lastFrame](_capture_.videocapture.md#protected-optional-lastframe) 24 | * [listeners](_capture_.videocapture.md#protected-listeners) 25 | * [o](_capture_.videocapture.md#protected-o) 26 | * [page](_capture_.videocapture.md#protected-optional-page) 27 | * [recording](_capture_.videocapture.md#protected-recording) 28 | * [server](_capture_.videocapture.md#protected-optional-server) 29 | 30 | ### Methods 31 | 32 | * [addFrameListener](_capture_.videocapture.md#addframelistener) 33 | * [captureLoop](_capture_.videocapture.md#protected-captureloop) 34 | * [initialize](_capture_.videocapture.md#initialize) 35 | * [isPaused](_capture_.videocapture.md#ispaused) 36 | * [isRecording](_capture_.videocapture.md#isrecording) 37 | * [isStopped](_capture_.videocapture.md#isstopped) 38 | * [notifyListeners](_capture_.videocapture.md#protected-notifylisteners) 39 | * [pause](_capture_.videocapture.md#pause) 40 | * [readFrame](_capture_.videocapture.md#readframe) 41 | * [resume](_capture_.videocapture.md#resume) 42 | * [start](_capture_.videocapture.md#start) 43 | * [startCamera](_capture_.videocapture.md#startcamera) 44 | * [startRecording](_capture_.videocapture.md#startrecording) 45 | * [stop](_capture_.videocapture.md#stop) 46 | * [stopCamera](_capture_.videocapture.md#stopcamera) 47 | * [stopRecording](_capture_.videocapture.md#stoprecording) 48 | 49 | ## Constructors 50 | 51 | ### constructor 52 | 53 | \+ **new VideoCapture**(`o`: [CaptureOptions](../interfaces/_types_.captureoptions.md)): *[VideoCapture](_capture_.videocapture.md)* 54 | 55 | *Overrides [CaptureBase](_capturebase_.capturebase.md).[constructor](_capturebase_.capturebase.md#constructor)* 56 | 57 | *Defined in [capture.ts:16](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L16)* 58 | 59 | **Parameters:** 60 | 61 | Name | Type | Default | 62 | ------ | ------ | ------ | 63 | `o` | [CaptureOptions](../interfaces/_types_.captureoptions.md) | {} | 64 | 65 | **Returns:** *[VideoCapture](_capture_.videocapture.md)* 66 | 67 | ## Properties 68 | 69 | ### `Protected` `Optional` browser 70 | 71 | • **browser**? : *puppeteer.Browser* 72 | 73 | *Inherited from [CaptureBase](_capturebase_.capturebase.md).[browser](_capturebase_.capturebase.md#protected-optional-browser)* 74 | 75 | *Defined in [captureBase.ts:13](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L13)* 76 | 77 | ___ 78 | 79 | ### `Protected` capturing 80 | 81 | • **capturing**: *boolean* = false 82 | 83 | *Defined in [capture.ts:10](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L10)* 84 | 85 | ___ 86 | 87 | ### `Protected` counter 88 | 89 | • **counter**: *number* = 0 90 | 91 | *Defined in [capture.ts:15](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L15)* 92 | 93 | counts [shots] 94 | 95 | ___ 96 | 97 | ### `Protected` initialized 98 | 99 | • **initialized**: *boolean* = false 100 | 101 | *Defined in [capture.ts:11](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L11)* 102 | 103 | ___ 104 | 105 | ### `Protected` `Optional` lastFrame 106 | 107 | • **lastFrame**? : *[ImageData](../interfaces/_types_.imagedata.md)* 108 | 109 | *Defined in [capture.ts:12](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L12)* 110 | 111 | ___ 112 | 113 | ### `Protected` listeners 114 | 115 | • **listeners**: *[Listener](../modules/_types_.md#listener)[]* = [] 116 | 117 | *Defined in [capture.ts:13](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L13)* 118 | 119 | ___ 120 | 121 | ### `Protected` o 122 | 123 | • **o**: *[CaptureOptions](../interfaces/_types_.captureoptions.md)* 124 | 125 | *Overrides [CaptureBase](_capturebase_.capturebase.md).[o](_capturebase_.capturebase.md#protected-o)* 126 | 127 | *Defined in [capture.ts:18](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L18)* 128 | 129 | ___ 130 | 131 | ### `Protected` `Optional` page 132 | 133 | • **page**? : *puppeteer.Page* 134 | 135 | *Inherited from [CaptureBase](_capturebase_.capturebase.md).[page](_capturebase_.capturebase.md#protected-optional-page)* 136 | 137 | *Defined in [captureBase.ts:14](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L14)* 138 | 139 | ___ 140 | 141 | ### `Protected` recording 142 | 143 | • **recording**: *boolean* = false 144 | 145 | *Defined in [capture.ts:16](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L16)* 146 | 147 | ___ 148 | 149 | ### `Protected` `Optional` server 150 | 151 | • **server**? : *Server* 152 | 153 | *Inherited from [CaptureBase](_capturebase_.capturebase.md).[server](_capturebase_.capturebase.md#protected-optional-server)* 154 | 155 | *Defined in [captureBase.ts:12](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L12)* 156 | 157 | ## Methods 158 | 159 | ### addFrameListener 160 | 161 | ▸ **addFrameListener**(`listener`: [Listener](../modules/_types_.md#listener)): *void* 162 | 163 | *Defined in [capture.ts:28](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L28)* 164 | 165 | Will be notified on managed frame shoot started by [start] method. Notice that this is optional and frames can be arbitrarily read using [readFrame] 166 | 167 | **Parameters:** 168 | 169 | Name | Type | 170 | ------ | ------ | 171 | `listener` | [Listener](../modules/_types_.md#listener) | 172 | 173 | **Returns:** *void* 174 | 175 | ___ 176 | 177 | ### `Protected` captureLoop 178 | 179 | ▸ **captureLoop**(): *Promise‹void›* 180 | 181 | *Defined in [capture.ts:248](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L248)* 182 | 183 | **Returns:** *Promise‹void›* 184 | 185 | ___ 186 | 187 | ### initialize 188 | 189 | ▸ **initialize**(): *Promise‹void›* 190 | 191 | *Overrides [CaptureBase](_capturebase_.capturebase.md).[initialize](_capturebase_.capturebase.md#initialize)* 192 | 193 | *Defined in [capture.ts:107](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L107)* 194 | 195 | starts servers, browser, install scripts and global functions used , and start up the video and canvas elements. 196 | ,media streams / canvas / video in the DOM. 197 | 198 | After it resolves the camera should be turned on , and methods like [readFrame] and [startRecording] will be ready to be called.. 199 | 200 | **Returns:** *Promise‹void›* 201 | 202 | ___ 203 | 204 | ### isPaused 205 | 206 | ▸ **isPaused**(): *boolean* 207 | 208 | *Defined in [capture.ts:68](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L68)* 209 | 210 | **Returns:** *boolean* 211 | 212 | ___ 213 | 214 | ### isRecording 215 | 216 | ▸ **isRecording**(): *boolean* 217 | 218 | *Defined in [capture.ts:76](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L76)* 219 | 220 | **Returns:** *boolean* 221 | 222 | ___ 223 | 224 | ### isStopped 225 | 226 | ▸ **isStopped**(): *boolean* 227 | 228 | *Defined in [capture.ts:72](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L72)* 229 | 230 | **Returns:** *boolean* 231 | 232 | ___ 233 | 234 | ### `Protected` notifyListeners 235 | 236 | ▸ **notifyListeners**(`d`: [ImageData](../interfaces/_types_.imagedata.md)): *Promise‹void›* 237 | 238 | *Defined in [capture.ts:242](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L242)* 239 | 240 | **Parameters:** 241 | 242 | Name | Type | 243 | ------ | ------ | 244 | `d` | [ImageData](../interfaces/_types_.imagedata.md) | 245 | 246 | **Returns:** *Promise‹void›* 247 | 248 | ___ 249 | 250 | ### pause 251 | 252 | ▸ **pause**(): *Promise‹void›* 253 | 254 | *Defined in [capture.ts:63](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L63)* 255 | 256 | Won't turn off the camera but frame listeners won't be notified which will result on low cpu usage. Use it to switch between managed and manual frame read with [readFrame]. You can unpause calling [resume]. 257 | 258 | **Returns:** *Promise‹void›* 259 | 260 | ___ 261 | 262 | ### readFrame 263 | 264 | ▸ **readFrame**(`mime`: [SupportedFormats](../modules/_types_.md#supportedformats), `quality`: number): *Promise‹object›* 265 | 266 | *Defined in [capture.ts:209](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L209)* 267 | 268 | Main public method to capture the current frame on the video camera. [initialized] must be called first. Then this method can be called at will, optionally providing desired image output format. 269 | 270 | It will work even if [isPaused] since is independent of managed frame listening. 271 | 272 | Tip: right now, 'image/jpeg' seems to be faster then the rest, even rgba, on a x2.5 ratio (Reading jpg 30 fps on a canvas size 480, 320).. Nevertheless this could change/improve in the future. 273 | 274 | **Parameters:** 275 | 276 | Name | Type | Default | 277 | ------ | ------ | ------ | 278 | `mime` | [SupportedFormats](../modules/_types_.md#supportedformats) | this.o.mime || 'rgba' | 279 | `quality` | number | 1 | 280 | 281 | **Returns:** *Promise‹object›* 282 | 283 | ___ 284 | 285 | ### resume 286 | 287 | ▸ **resume**(): *Promise‹void›* 288 | 289 | *Defined in [capture.ts:83](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L83)* 290 | 291 | Resumes frame listener notification. See [pause]. 292 | 293 | **Returns:** *Promise‹void›* 294 | 295 | ___ 296 | 297 | ### start 298 | 299 | ▸ **start**(): *Promise‹void›* 300 | 301 | *Defined in [capture.ts:91](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L91)* 302 | 303 | Starts capturing camera video. If not calling yet it will call [initialize] and after camera is turned on it will start the capture loop, this is frame listeners notification. The loop, by default will be as fast as possible consequently with high cpu overhead. Use [pause] and[resume] to control it. Alternatively, if you just want to read frames arbitrarily y your self, just call [initialize] instead this method and use [readFrame]. 304 | 305 | **Returns:** *Promise‹void›* 306 | 307 | ___ 308 | 309 | ### startCamera 310 | 311 | ▸ **startCamera**(`o`: MediaStreamConstraints): *Promise‹void›* 312 | 313 | *Defined in [capture.ts:130](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L130)* 314 | 315 | Just turn on the camera using given constrains and merging them with defaults and the ones given in constructor. 316 | 317 | After it resolves the camera should be turned on and methods like [readFrame] and [startRecording] will be ready to be called.. 318 | 319 | **Parameters:** 320 | 321 | Name | Type | Default | 322 | ------ | ------ | ------ | 323 | `o` | MediaStreamConstraints | {} | 324 | 325 | **Returns:** *Promise‹void›* 326 | 327 | ___ 328 | 329 | ### startRecording 330 | 331 | ▸ **startRecording**(`recordOptions`: object): *Promise‹void›* 332 | 333 | *Defined in [capture.ts:162](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L162)* 334 | 335 | Uses [MediaRecorder] to start recording current captured video. Notice that this happens 100% on memory so take a look at RAM and the time it takes [stopRecording] to encode the video file. 336 | 337 | **Parameters:** 338 | 339 | Name | Type | Default | 340 | ------ | ------ | ------ | 341 | `recordOptions` | object | { mimeType: 'video/webm;codecs=vp8', width: 480, height: 320, } | 342 | 343 | **Returns:** *Promise‹void›* 344 | 345 | ___ 346 | 347 | ### stop 348 | 349 | ▸ **stop**(): *Promise‹void›* 350 | 351 | *Overrides [CaptureBase](_capturebase_.capturebase.md).[stop](_capturebase_.capturebase.md#stop)* 352 | 353 | *Defined in [capture.ts:53](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L53)* 354 | 355 | turns off everything, first recording , turns of camera, frame listeners and at last the server and browser. Everything can be restarted using [initialize] or alternatively [start] to restart frame listener notifications too 356 | 357 | **Returns:** *Promise‹void›* 358 | 359 | ___ 360 | 361 | ### stopCamera 362 | 363 | ▸ **stopCamera**(): *Promise‹void›* 364 | 365 | *Defined in [capture.ts:37](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L37)* 366 | 367 | Turns off the camera. frame read loop triggered by [start] won't be cleared but listeners won't be called while camera is off. 368 | 369 | The camera can be turned on again by calling [startCamera] 370 | 371 | **Returns:** *Promise‹void›* 372 | 373 | ___ 374 | 375 | ### stopRecording 376 | 377 | ▸ **stopRecording**(`discard`: boolean): *Promise‹undefined | Uint8ClampedArray›* 378 | 379 | *Defined in [capture.ts:174](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/capture.ts#L174)* 380 | 381 | Stop video recording (see [startRecording]) and resolves with a encoded video file 'video/webm' which dimensions correspond to the original video constraints. 382 | 383 | **Parameters:** 384 | 385 | Name | Type | Default | Description | 386 | ------ | ------ | ------ | ------ | 387 | `discard` | boolean | false | if true it won't build the video and resolve with undefined. | 388 | 389 | **Returns:** *Promise‹undefined | Uint8ClampedArray›* 390 | -------------------------------------------------------------------------------- /docs/classes/_capturebase_.capturebase.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["captureBase"](../modules/_capturebase_.md) › [CaptureBase](_capturebase_.capturebase.md) 2 | 3 | # Class: CaptureBase 4 | 5 | ## Hierarchy 6 | 7 | * **CaptureBase** 8 | 9 | ↳ [VideoCapture](_capture_.videocapture.md) 10 | 11 | ## Index 12 | 13 | ### Constructors 14 | 15 | * [constructor](_capturebase_.capturebase.md#constructor) 16 | 17 | ### Properties 18 | 19 | * [browser](_capturebase_.capturebase.md#protected-optional-browser) 20 | * [o](_capturebase_.capturebase.md#protected-o) 21 | * [page](_capturebase_.capturebase.md#protected-optional-page) 22 | * [server](_capturebase_.capturebase.md#protected-optional-server) 23 | 24 | ### Methods 25 | 26 | * [initialize](_capturebase_.capturebase.md#initialize) 27 | * [stop](_capturebase_.capturebase.md#stop) 28 | 29 | ## Constructors 30 | 31 | ### constructor 32 | 33 | \+ **new CaptureBase**(`o`: [CaptureBaseOptions](../interfaces/_types_.capturebaseoptions.md)): *[CaptureBase](_capturebase_.capturebase.md)* 34 | 35 | *Defined in [captureBase.ts:14](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L14)* 36 | 37 | **Parameters:** 38 | 39 | Name | Type | Default | 40 | ------ | ------ | ------ | 41 | `o` | [CaptureBaseOptions](../interfaces/_types_.capturebaseoptions.md) | {} | 42 | 43 | **Returns:** *[CaptureBase](_capturebase_.capturebase.md)* 44 | 45 | ## Properties 46 | 47 | ### `Protected` `Optional` browser 48 | 49 | • **browser**? : *puppeteer.Browser* 50 | 51 | *Defined in [captureBase.ts:13](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L13)* 52 | 53 | ___ 54 | 55 | ### `Protected` o 56 | 57 | • **o**: *[CaptureBaseOptions](../interfaces/_types_.capturebaseoptions.md)* 58 | 59 | *Defined in [captureBase.ts:16](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L16)* 60 | 61 | ___ 62 | 63 | ### `Protected` `Optional` page 64 | 65 | • **page**? : *puppeteer.Page* 66 | 67 | *Defined in [captureBase.ts:14](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L14)* 68 | 69 | ___ 70 | 71 | ### `Protected` `Optional` server 72 | 73 | • **server**? : *Server* 74 | 75 | *Defined in [captureBase.ts:12](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L12)* 76 | 77 | ## Methods 78 | 79 | ### initialize 80 | 81 | ▸ **initialize**(): *Promise‹void›* 82 | 83 | *Defined in [captureBase.ts:29](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L29)* 84 | 85 | **Returns:** *Promise‹void›* 86 | 87 | ___ 88 | 89 | ### stop 90 | 91 | ▸ **stop**(): *Promise‹void›* 92 | 93 | *Defined in [captureBase.ts:19](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/captureBase.ts#L19)* 94 | 95 | **Returns:** *Promise‹void›* 96 | -------------------------------------------------------------------------------- /docs/interfaces/_browser_.startrecordingoptions.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["browser"](../modules/_browser_.md) › [StartRecordingOptions](_browser_.startrecordingoptions.md) 2 | 3 | # Interface: StartRecordingOptions 4 | 5 | ## Hierarchy 6 | 7 | * **StartRecordingOptions** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [height](_browser_.startrecordingoptions.md#optional-height) 14 | * [mimeType](_browser_.startrecordingoptions.md#optional-mimetype) 15 | * [video](_browser_.startrecordingoptions.md#video) 16 | * [width](_browser_.startrecordingoptions.md#optional-width) 17 | 18 | ## Properties 19 | 20 | ### `Optional` height 21 | 22 | • **height**? : *undefined | number* 23 | 24 | *Defined in [browser.ts:89](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L89)* 25 | 26 | ___ 27 | 28 | ### `Optional` mimeType 29 | 30 | • **mimeType**? : *undefined | string* 31 | 32 | *Defined in [browser.ts:87](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L87)* 33 | 34 | ___ 35 | 36 | ### video 37 | 38 | • **video**: *HTMLVideoElement* 39 | 40 | *Defined in [browser.ts:86](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L86)* 41 | 42 | ___ 43 | 44 | ### `Optional` width 45 | 46 | • **width**? : *undefined | number* 47 | 48 | *Defined in [browser.ts:88](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L88)* 49 | -------------------------------------------------------------------------------- /docs/interfaces/_types_.capturebaseoptions.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["types"](../modules/_types_.md) › [CaptureBaseOptions](_types_.capturebaseoptions.md) 2 | 3 | # Interface: CaptureBaseOptions 4 | 5 | ## Hierarchy 6 | 7 | * **CaptureBaseOptions** 8 | 9 | ↳ [CaptureOptions](_types_.captureoptions.md) 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [debug](_types_.capturebaseoptions.md#optional-debug) 16 | * [mkdirServed](_types_.capturebaseoptions.md#optional-mkdirserved) 17 | * [port](_types_.capturebaseoptions.md#optional-port) 18 | * [puppeteerOptions](_types_.capturebaseoptions.md#optional-puppeteeroptions) 19 | 20 | ## Properties 21 | 22 | ### `Optional` debug 23 | 24 | • **debug**? : *undefined | false | true* 25 | 26 | *Defined in [types.ts:13](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L13)* 27 | 28 | ___ 29 | 30 | ### `Optional` mkdirServed 31 | 32 | • **mkdirServed**? : *string | boolean* 33 | 34 | *Defined in [types.ts:12](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L12)* 35 | 36 | If given, a folder with that path will be created (or if true is given at $HOME/.camera-capture/static) and a index.html file will be copied there so it can be served as static web page for the headless browser to use. This is not often necessary, but particularly when packaging the client package as a desktop application with asar or yackage . 37 | 38 | ___ 39 | 40 | ### `Optional` port 41 | 42 | • **port**? : *undefined | number* 43 | 44 | *Defined in [types.ts:7](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L7)* 45 | 46 | Image-capture needs to serve a static HTML so the headless browser capture media from there. If given port number will be used for the server. Default is 8080. 47 | 48 | ___ 49 | 50 | ### `Optional` puppeteerOptions 51 | 52 | • **puppeteerOptions**? : *LaunchOptions* 53 | 54 | *Defined in [types.ts:8](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L8)* 55 | -------------------------------------------------------------------------------- /docs/interfaces/_types_.captureoptions.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["types"](../modules/_types_.md) › [CaptureOptions](_types_.captureoptions.md) 2 | 3 | # Interface: CaptureOptions 4 | 5 | ## Hierarchy 6 | 7 | * [CaptureBaseOptions](_types_.capturebaseoptions.md) 8 | 9 | ↳ **CaptureOptions** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [constrains](_types_.captureoptions.md#optional-constrains) 16 | * [debug](_types_.captureoptions.md#optional-debug) 17 | * [fps](_types_.captureoptions.md#optional-fps) 18 | * [height](_types_.captureoptions.md#optional-height) 19 | * [interval](_types_.captureoptions.md#optional-interval) 20 | * [mime](_types_.captureoptions.md#optional-mime) 21 | * [mkdirServed](_types_.captureoptions.md#optional-mkdirserved) 22 | * [port](_types_.captureoptions.md#optional-port) 23 | * [puppeteerOptions](_types_.captureoptions.md#optional-puppeteeroptions) 24 | * [shots](_types_.captureoptions.md#optional-shots) 25 | * [width](_types_.captureoptions.md#optional-width) 26 | 27 | ## Properties 28 | 29 | ### `Optional` constrains 30 | 31 | • **constrains**? : *MediaStreamConstraints* 32 | 33 | *Defined in [types.ts:19](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L19)* 34 | 35 | ___ 36 | 37 | ### `Optional` debug 38 | 39 | • **debug**? : *undefined | false | true* 40 | 41 | *Inherited from [CaptureBaseOptions](_types_.capturebaseoptions.md).[debug](_types_.capturebaseoptions.md#optional-debug)* 42 | 43 | *Defined in [types.ts:13](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L13)* 44 | 45 | ___ 46 | 47 | ### `Optional` fps 48 | 49 | • **fps**? : *undefined | number* 50 | 51 | *Defined in [types.ts:39](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L39)* 52 | 53 | If given it will make sure frames speed is no greater than given number of frames per second. 54 | 55 | ___ 56 | 57 | ### `Optional` height 58 | 59 | • **height**? : *undefined | number* 60 | 61 | *Defined in [types.ts:35](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L35)* 62 | 63 | Frames height in pixels. Default is 320. 64 | 65 | ___ 66 | 67 | ### `Optional` interval 68 | 69 | • **interval**? : *undefined | number* 70 | 71 | *Defined in [types.ts:23](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L23)* 72 | 73 | TODO. Make sure that at least given number of milliseconds pass between frames 74 | 75 | ___ 76 | 77 | ### `Optional` mime 78 | 79 | • **mime**? : *[SupportedFormats](../modules/_types_.md#supportedformats)* 80 | 81 | *Defined in [types.ts:43](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L43)* 82 | 83 | Output frames image format. Accepted values: `image/png`, `image/jpeg`, `image/webp`, `rgba`. By default is `rgba` which is raw data image 8-bit depth RGBA - as in HTML's ImageData object. 84 | 85 | ___ 86 | 87 | ### `Optional` mkdirServed 88 | 89 | • **mkdirServed**? : *string | boolean* 90 | 91 | *Inherited from [CaptureBaseOptions](_types_.capturebaseoptions.md).[mkdirServed](_types_.capturebaseoptions.md#optional-mkdirserved)* 92 | 93 | *Defined in [types.ts:12](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L12)* 94 | 95 | If given, a folder with that path will be created (or if true is given at $HOME/.camera-capture/static) and a index.html file will be copied there so it can be served as static web page for the headless browser to use. This is not often necessary, but particularly when packaging the client package as a desktop application with asar or yackage . 96 | 97 | ___ 98 | 99 | ### `Optional` port 100 | 101 | • **port**? : *undefined | number* 102 | 103 | *Inherited from [CaptureBaseOptions](_types_.capturebaseoptions.md).[port](_types_.capturebaseoptions.md#optional-port)* 104 | 105 | *Defined in [types.ts:7](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L7)* 106 | 107 | Image-capture needs to serve a static HTML so the headless browser capture media from there. If given port number will be used for the server. Default is 8080. 108 | 109 | ___ 110 | 111 | ### `Optional` puppeteerOptions 112 | 113 | • **puppeteerOptions**? : *LaunchOptions* 114 | 115 | *Inherited from [CaptureBaseOptions](_types_.capturebaseoptions.md).[puppeteerOptions](_types_.capturebaseoptions.md#optional-puppeteeroptions)* 116 | 117 | *Defined in [types.ts:8](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L8)* 118 | 119 | ___ 120 | 121 | ### `Optional` shots 122 | 123 | • **shots**? : *undefined | number* 124 | 125 | *Defined in [types.ts:27](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L27)* 126 | 127 | Takes given number of shots and stops. 128 | 129 | ___ 130 | 131 | ### `Optional` width 132 | 133 | • **width**? : *undefined | number* 134 | 135 | *Defined in [types.ts:31](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L31)* 136 | 137 | Frames width in pixels. Default is 480. 138 | -------------------------------------------------------------------------------- /docs/interfaces/_types_.imagedata.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["types"](../modules/_types_.md) › [ImageData](_types_.imagedata.md) 2 | 3 | # Interface: ImageData 4 | 5 | ## Hierarchy 6 | 7 | * **ImageData** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [data](_types_.imagedata.md#data) 14 | * [height](_types_.imagedata.md#height) 15 | * [width](_types_.imagedata.md#width) 16 | 17 | ## Properties 18 | 19 | ### data 20 | 21 | • **data**: *Uint8ClampedArray* 22 | 23 | *Defined in [types.ts:51](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L51)* 24 | 25 | ___ 26 | 27 | ### height 28 | 29 | • **height**: *number* 30 | 31 | *Defined in [types.ts:50](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L50)* 32 | 33 | ___ 34 | 35 | ### width 36 | 37 | • **width**: *number* 38 | 39 | *Defined in [types.ts:49](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L49)* 40 | -------------------------------------------------------------------------------- /docs/modules/_browser_.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["browser"](_browser_.md) 2 | 3 | # External module: "browser" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [MediaRecorder](../classes/_browser_.mediarecorder.md) 10 | 11 | ### Interfaces 12 | 13 | * [StartRecordingOptions](../interfaces/_browser_.startrecordingoptions.md) 14 | 15 | ### Type aliases 16 | 17 | * [EventHandler](_browser_.md#eventhandler) 18 | * [MediaRecorderOptions](_browser_.md#mediarecorderoptions) 19 | * [OnDataAvailableListener](_browser_.md#ondataavailablelistener) 20 | * [RecordingState](_browser_.md#recordingstate) 21 | 22 | ### Functions 23 | 24 | * [blobToArrayBuffer](_browser_.md#blobtoarraybuffer) 25 | * [canvasToArrayBuffer](_browser_.md#canvastoarraybuffer) 26 | * [startRecording](_browser_.md#startrecording) 27 | 28 | ## Type aliases 29 | 30 | ### EventHandler 31 | 32 | Ƭ **EventHandler**: *TODO* 33 | 34 | *Defined in [browser.ts:99](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L99)* 35 | 36 | ___ 37 | 38 | ### MediaRecorderOptions 39 | 40 | Ƭ **MediaRecorderOptions**: *TODO* 41 | 42 | *Defined in [browser.ts:95](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L95)* 43 | 44 | ___ 45 | 46 | ### OnDataAvailableListener 47 | 48 | Ƭ **OnDataAvailableListener**: *function* 49 | 50 | *Defined in [browser.ts:101](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L101)* 51 | 52 | #### Type declaration: 53 | 54 | ▸ (`e`: object): *void* 55 | 56 | **Parameters:** 57 | 58 | Name | Type | 59 | ------ | ------ | 60 | `e` | object | 61 | 62 | ___ 63 | 64 | ### RecordingState 65 | 66 | Ƭ **RecordingState**: *TODO* 67 | 68 | *Defined in [browser.ts:97](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L97)* 69 | 70 | ## Functions 71 | 72 | ### blobToArrayBuffer 73 | 74 | ▸ **blobToArrayBuffer**(`blob`: Blob): *Promise‹ArrayBuffer›* 75 | 76 | *Defined in [browser.ts:6](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L6)* 77 | 78 | Self contained function to transform a [Blob] into [ArrayBuffer]. Notice that this function is meant to be serialized and evaluated in a browser context that's why its dependencies must be controlled. 79 | 80 | **Parameters:** 81 | 82 | Name | Type | 83 | ------ | ------ | 84 | `blob` | Blob | 85 | 86 | **Returns:** *Promise‹ArrayBuffer›* 87 | 88 | ___ 89 | 90 | ### canvasToArrayBuffer 91 | 92 | ▸ **canvasToArrayBuffer**(`canvas`: HTMLCanvasElement, `mime`: string, `quality`: number, `blobToArrayBufferFn`: [blobToArrayBuffer](_browser_.md#blobtoarraybuffer)): *Promise‹ArrayBuffer›* 93 | 94 | *Defined in [browser.ts:30](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L30)* 95 | 96 | Reads given [HTMLCanvasElement] image encoded in given format and quality and return its content as [ArrayBuffer]. Depends on [blobToArrayBufferFn] which must be given or assumed to be global. Notice that this function is meant to be serialized and evaluated in a browser context that's why its dependencies must be controlled. 97 | 98 | **Parameters:** 99 | 100 | Name | Type | Default | Description | 101 | ------ | ------ | ------ | ------ | 102 | `canvas` | HTMLCanvasElement | - | - | 103 | `mime` | string | "image/png" | A DOMString indicating the image format. The default type is image/png. | 104 | `quality` | number | 1 | A Number between 0 and 1 indicating image quality if the requested type is image/jpeg. If this argument is anything else, the default value for image quality is used. Other arguments are ignored. | 105 | `blobToArrayBufferFn` | [blobToArrayBuffer](_browser_.md#blobtoarraybuffer) | (window as any).blobToArrayBuffer | - | 106 | 107 | **Returns:** *Promise‹ArrayBuffer›* 108 | 109 | ___ 110 | 111 | ### startRecording 112 | 113 | ▸ **startRecording**(`options`: [StartRecordingOptions](../interfaces/_browser_.startrecordingoptions.md)): *Promise‹unknown›* 114 | 115 | *Defined in [browser.ts:46](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/browser.ts#L46)* 116 | 117 | Uses [MediaRecorder] to start recording current captured video. 118 | Notice that this function is meant to be serialized and evaluated in a browser context that's why its dependencies must be controlled. 119 | 120 | **Parameters:** 121 | 122 | Name | Type | Default | 123 | ------ | ------ | ------ | 124 | `options` | [StartRecordingOptions](../interfaces/_browser_.startrecordingoptions.md) | { video: document.querySelector('video')!, mimeType: 'video/webm;codecs=vp8', width: 480, height: 320 } | 125 | 126 | **Returns:** *Promise‹unknown›* 127 | -------------------------------------------------------------------------------- /docs/modules/_capture_.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["capture"](_capture_.md) 2 | 3 | # External module: "capture" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [VideoCapture](../classes/_capture_.videocapture.md) 10 | -------------------------------------------------------------------------------- /docs/modules/_capturebase_.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["captureBase"](_capturebase_.md) 2 | 3 | # External module: "captureBase" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [CaptureBase](../classes/_capturebase_.capturebase.md) 10 | -------------------------------------------------------------------------------- /docs/modules/_index_.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["index"](_index_.md) 2 | 3 | # External module: "index" 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/modules/_staticserver_.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["staticServer"](_staticserver_.md) 2 | 3 | # External module: "staticServer" 4 | 5 | ## Index 6 | 7 | ### Functions 8 | 9 | * [staticServer](_staticserver_.md#staticserver) 10 | 11 | ## Functions 12 | 13 | ### staticServer 14 | 15 | ▸ **staticServer**(`basePath`: string, `port`: number, `onFound`: Fn, `onNotFound`: Fn): *Promise‹Server›* 16 | 17 | *Defined in [staticServer.ts:6](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/staticServer.ts#L6)* 18 | 19 | **Parameters:** 20 | 21 | Name | Type | Default | 22 | ------ | ------ | ------ | 23 | `basePath` | string | - | 24 | `port` | number | 9999 | 25 | `onFound` | Fn | () => { } | 26 | `onNotFound` | Fn | () => { } | 27 | 28 | **Returns:** *Promise‹Server›* 29 | -------------------------------------------------------------------------------- /docs/modules/_types_.md: -------------------------------------------------------------------------------- 1 | [camera-capture](../README.md) › ["types"](_types_.md) 2 | 3 | # External module: "types" 4 | 5 | ## Index 6 | 7 | ### Interfaces 8 | 9 | * [CaptureBaseOptions](../interfaces/_types_.capturebaseoptions.md) 10 | * [CaptureOptions](../interfaces/_types_.captureoptions.md) 11 | * [ImageData](../interfaces/_types_.imagedata.md) 12 | 13 | ### Type aliases 14 | 15 | * [Listener](_types_.md#listener) 16 | * [SupportedFormats](_types_.md#supportedformats) 17 | 18 | ## Type aliases 19 | 20 | ### Listener 21 | 22 | Ƭ **Listener**: *function* 23 | 24 | *Defined in [types.ts:46](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L46)* 25 | 26 | #### Type declaration: 27 | 28 | ▸ (`data`: [ImageData](../interfaces/_types_.imagedata.md)): *void | Promise‹void›* 29 | 30 | **Parameters:** 31 | 32 | Name | Type | 33 | ------ | ------ | 34 | `data` | [ImageData](../interfaces/_types_.imagedata.md) | 35 | 36 | ___ 37 | 38 | ### SupportedFormats 39 | 40 | Ƭ **SupportedFormats**: *"image/png" | "image/jpeg" | "image/webp" | "rgba"* 41 | 42 | *Defined in [types.ts:16](https://github.com/cancerberoSgx/camera-capture/blob/31f9c91/camera-capture/src/types.ts#L16)* 43 | -------------------------------------------------------------------------------- /saudade/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .cache 4 | docs 5 | .DS_Store 6 | *.tgz 7 | .nyc_output 8 | coverage 9 | typescript-ast-query-editor/src/queryAst/tsLibraries 10 | log2.txt 11 | /package 12 | .vscode 13 | test-browser-outdir 14 | playground 15 | /tmp* 16 | bkps 17 | working_tmp -------------------------------------------------------------------------------- /saudade/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *.log 3 | /tmp 4 | /src 5 | /.gitignore 6 | /.npmignore 7 | /dist/spec 8 | /tsconfig.json 9 | /declarations.d.ts 10 | *.tgz 11 | test 12 | typescript-ast-query-editor 13 | formatCodeSettings.json 14 | .nyc_output 15 | coverage 16 | test-browser-outdir 17 | guitarra 18 | dist/test 19 | dist/test-browser 20 | test 21 | .cache 22 | .travis.yml 23 | zangano 24 | zangano-demo 25 | astq-query* 26 | ava.config.js 27 | nyc.config.js 28 | TODO.md 29 | ts-ast 30 | .vscode 31 | guides-probes-backups 32 | test-browser 33 | .travis.yml 34 | .DS_Store 35 | /tmp* 36 | docs 37 | .cache 38 | test-browser 39 | test-browser-outdir 40 | working_tmp 41 | scripts 42 | playground 43 | bkps 44 | docs 45 | magick-wasm 46 | emscripten_prefix 47 | ava.config-js.js 48 | apps 49 | static 50 | bkps -------------------------------------------------------------------------------- /saudade/COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sebastián Gurin 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 | -------------------------------------------------------------------------------- /saudade/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sebastián Gurin 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 | -------------------------------------------------------------------------------- /saudade/README.md: -------------------------------------------------------------------------------- 1 | Command line interface for [camera-capture](https://www.npmjs.com/package/camera-capture) 2 | 3 | ## install 4 | 5 | npm install -g saudade 6 | 7 | ## Usage 8 | 9 | YBD 10 | 11 | ## We want: 12 | 13 | ## TODO 14 | 15 | - [ ] --action frames . naming patterns like --mode frames --output "tmp" 16 | 17 | 18 | Notes 19 | 20 | for f in lenna* ; do cat $f | convert - miff:- ; done | convert - -rotate 33 miff:- | convert - -scale 133 miff:- | convert - tmp2.gif -------------------------------------------------------------------------------- /saudade/ava.config-js.js: -------------------------------------------------------------------------------- 1 | export default { 2 | files: [ 3 | "dist/test/*Test.js*" 4 | ], 5 | compileEnhancements: false, 6 | serial:true, 7 | concurrency: 1, 8 | }; -------------------------------------------------------------------------------- /saudade/ava.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | files: [ 3 | "test/*Test.ts*" 4 | ], 5 | extensions: ['ts'], 6 | compileEnhancements: false, 7 | serial: true, 8 | concurrency: 1, 9 | // failFast:true, 10 | require: [ 11 | "ts-node/register" 12 | ] 13 | }; -------------------------------------------------------------------------------- /saudade/bin/saudade.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist/src/cliMain') 4 | -------------------------------------------------------------------------------- /saudade/formatCodeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "insertSpaceBeforeAndAfterBinaryOperators": true, 3 | "insertSpaceAfterCommaDelimiter": true, 4 | "insertSpaceAfterSemicolonInForStatements": true, 5 | "insertSpaceAfterConstructor": false, 6 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, 7 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, 8 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, 9 | "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false, 10 | "insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, 11 | "insertSpaceAfterTypeAssertion": false, 12 | "insertSpaceBeforeFunctionParenthesis": false, 13 | "placeOpenBraceOnNewLineForFunctions": false, 14 | "placeOpenBraceOnNewLineForControlBlocks": false, 15 | "insertSpaceBeforeTypeAnnotation": false, 16 | "indentMultiLineObjectLiteralBeginningOnBlankLine": true, 17 | "indentSize": 2, 18 | "tabSize": 2, 19 | "convertTabsToSpaces": true, 20 | "quotePreference": "single", 21 | "importModuleSpecifierPreference": "relative", 22 | "importModuleSpecifierEnding": "minimal", 23 | "allowTextChangesInNewFiles": true, 24 | "trailingSemicolon": false 25 | } -------------------------------------------------------------------------------- /saudade/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "saudade", 3 | "version": "0.0.1", 4 | "description": "Command line interface for camera-capture", 5 | "main": "dist/src/index.js", 6 | "types": "dist/src/index.d.ts", 7 | "bin": "bin/saudade.js", 8 | "scripts": { 9 | "clean": "rm -rf dist docs", 10 | "test": "npm run build && npx ava --serial --concurrency 1 --config ava.config.js", 11 | "test-js": "npm run build && npx ava --serial --concurrency 1 --config ava.config-js.js", 12 | "format": "npx ts-refactor format \"src/**/*.ts*\" \"test/**/*.ts*\" ./formatCodeSettings.json --tsConfigPath ./tsconfig.json --dontAsk", 13 | "organizeImports": "npx ts-refactor organizeImports \"src/**/*.ts*\" \"test/**/*.ts*\" ./formatCodeSettings.json --tsConfigPath ./tsconfig.json --dontAsk ", 14 | "lint": " npm run organizeImports && npm run format", 15 | "prepare": " npm run build ", 16 | "build": "npm run clean && npx tsc", 17 | "all": "npm run clean && npm run lint && npm run build && npm test && npm run test-js", 18 | "all-publish": "npm run all && git commit -a -m 'version patch' && npm version patch && npm publish" 19 | }, 20 | "keywords": [ 21 | "typescript", 22 | "camera", 23 | "audio", 24 | "video record", 25 | "camera capture", 26 | "audio capture", 27 | "capture", 28 | "node.js", 29 | "Command line" 30 | ], 31 | "author": "Sebastián Gurin", 32 | "license": "MIT", 33 | "devDependencies": { 34 | "@types/node": "^12.7.8", 35 | "@types/puppeteer": "^1.20.0", 36 | "@types/shelljs": "^0.8.5", 37 | "ava": "^2.4.0", 38 | "shelljs": "^0.8.3", 39 | "ts-node": "^8.4.1", 40 | "ts-refactor": "0.0.9", 41 | "typescript": "^3.6.3" 42 | }, 43 | "dependencies": { 44 | "camera-capture": "file:../camera-capture", 45 | "minimist": "^1.2.0", 46 | "misc-utils-of-mine-generic": "^0.2.34", 47 | "puppeteer": "^1.20.0" 48 | }, 49 | "repository": { 50 | "type": "git", 51 | "url": "https://github.com/cancerberoSgx/camera-capture.git" 52 | }, 53 | "bugs": { 54 | "url": "https://github.com/cancerberoSgx/camera-capture" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /saudade/src/cli.ts: -------------------------------------------------------------------------------- 1 | import {CaptureOptions, VideoCapture} from 'camera-capture' 2 | import {promises, mkdirSync, createWriteStream, writeFileSync} from 'fs' 3 | import { withoutExtension } from 'misc-utils-of-mine-generic' 4 | import { dirname, join, basename } from 'path' 5 | 6 | export interface Options extends CaptureOptions{ 7 | help?: boolean 8 | action: 'frames' | 'video' |'audio'|'none' 9 | output?: string 10 | headless?: boolean 11 | timeout?: number 12 | } 13 | 14 | export async function cli(o: Options) { 15 | o.debug && console.log(`CLI Options: ${JSON.stringify({ ...o, input: null })}`) 16 | if (o.help) { 17 | printHelpAndExit(0) 18 | } 19 | if (!o.action) { 20 | fail('Option --action option is mandatory') 21 | } 22 | if(o.action==='frames'){ 23 | const c = new VideoCapture(o) 24 | // let output = o.output ? 25 | let counter = 0 26 | if(o.output){ 27 | mkdirSync(dirname(o.output)||o.output, {recursive: true}) 28 | } 29 | c.addFrameListener(frame => { 30 | if(o.output){ 31 | const f = join( dirname(o.output!)||o.output!, withoutExtension(basename(o.output!)))+(counter+'').padStart(5, '0')+'.png' 32 | if(typeof o.shots!=='undefined' && counter++ > o.shots) { 33 | c.stop() 34 | return 35 | } 36 | writeFileSync(f, frame.data) 37 | } 38 | else{ 39 | console.error('--output mandatory (TODO)'); 40 | c.stop() 41 | return 42 | // writable.write(frame.data as any ) 43 | // writable.write('\0') 44 | // writable.destroy() 45 | } 46 | counter++ 47 | // console.log(counter, ); 48 | 49 | // let output: Writable = process.stdout 50 | // const writable = !o.output ? process.stdout : createWriteStream(f) 51 | // else { 52 | 53 | // } 54 | }) 55 | await c.start() 56 | } 57 | } 58 | 59 | export function fail(msg: string) { 60 | console.error(msg) 61 | printHelpAndExit(1) 62 | } 63 | 64 | function printHelpAndExit(code: number) { 65 | console.log(` 66 | Usage: 67 | 68 | TODO 69 | `) 70 | process.exit(code) 71 | } 72 | -------------------------------------------------------------------------------- /saudade/src/cliMain.ts: -------------------------------------------------------------------------------- 1 | import { cli } from './cli' 2 | 3 | const options = require('minimist')(process.argv.slice(2)) 4 | cli(options) 5 | -------------------------------------------------------------------------------- /saudade/test/helpTest.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { config, exec } from 'shelljs' 3 | 4 | config.silent = true 5 | 6 | test('--help', async t => { 7 | const p = exec('node bin/saudade --help') 8 | t.deepEqual(p.code, 0) 9 | t.true(p.stdout.includes('Usage:')) 10 | t.deepEqual(p.stderr.trim(), '') 11 | }) 12 | 13 | test('--invalid', async t => { 14 | const p = exec('node bin/saudade --invalid') 15 | t.notDeepEqual(p.code, 0) 16 | t.true(p.stdout.includes('Usage:')) 17 | t.true(p.stderr.includes('is mandatory')) 18 | }) 19 | -------------------------------------------------------------------------------- /saudade/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "lib": ["esnext", "dom"], 6 | "strict": true, 7 | "sourceMap": true, 8 | "esModuleInterop": true, 9 | "outDir": "./dist", 10 | "rootDir": ".", 11 | "declaration": true, 12 | }, 13 | "include": ["src", "test"] 14 | } 15 | --------------------------------------------------------------------------------