Comparing version
117
index.js
@@ -0,38 +1,63 @@ | ||
'use strict'; | ||
const os = require('os'); | ||
const util = require('util'); | ||
const path = require('path'); | ||
const execa = require('execa'); | ||
const tmp = require('tmp'); | ||
// TODO: log in production with proces.env.DEBUG_APERTURE | ||
function log(...msgs) { | ||
if (process.env.DEBUG) { | ||
console.log(...msgs); | ||
const debuglog = util.debuglog('aperture'); | ||
const isYosemiteOrHigher = process.platform === 'darwin' && Number(os.release().split('.')[0]) >= 14; | ||
class Aperture { | ||
constructor() { | ||
if (!isYosemiteOrHigher) { | ||
throw new Error('Requires macOS 10.10 or higher'); | ||
} | ||
} | ||
} | ||
class Aperture { | ||
getAudioSources() { | ||
return execa(path.join(__dirname, 'swift', 'main'), ['list-audio-devices']).then(result => { | ||
return JSON.parse(result.stdout); | ||
}); | ||
return execa.stdout(path.join(__dirname, 'swift/main'), ['list-audio-devices']).then(JSON.parse); | ||
} | ||
// resolves if the recording started successfully | ||
// rejects if the recording didn't started after 5 seconds or if some error | ||
// occurs during the recording session | ||
startRecording({ | ||
fps = 30, | ||
cropArea = 'none', // can be 'none' or {x, y, width, height} – TODO: document this | ||
cropArea = 'none', | ||
showCursor = true, | ||
highlightClicks = false, | ||
displayId = 'main', | ||
audioSourceId = 'none' // one of the `id`s from getAudioSources() | ||
audioSourceId = 'none' | ||
} = {}) { | ||
return new Promise((resolve, reject) => { | ||
if (this.recorder !== undefined) { | ||
reject(new Error('Call `.stopRecording()` first')); | ||
return; | ||
} | ||
if (highlightClicks === true) { | ||
showCursor = true; | ||
} | ||
this.tmpPath = tmp.tmpNameSync({postfix: '.mp4'}); | ||
if (typeof cropArea === 'object') { // TODO validate this | ||
if (typeof cropArea === 'object') { | ||
if (typeof cropArea.x !== 'number' || | ||
typeof cropArea.y !== 'number' || | ||
typeof cropArea.width !== 'number' || | ||
typeof cropArea.height !== 'number') { | ||
reject(new Error('Invalid `cropArea` option object')); | ||
return; | ||
} | ||
cropArea = `${cropArea.x}:${cropArea.y}:${cropArea.width}:${cropArea.height}`; | ||
} | ||
const recorderOpts = [this.tmpPath, fps, cropArea, showCursor, highlightClicks, displayId, audioSourceId]; | ||
const recorderOpts = [ | ||
this.tmpPath, | ||
fps, | ||
cropArea, | ||
showCursor, | ||
highlightClicks, | ||
displayId, | ||
audioSourceId | ||
]; | ||
@@ -42,16 +67,25 @@ this.recorder = execa(path.join(__dirname, 'swift', 'main'), recorderOpts); | ||
const timeout = setTimeout(() => { | ||
const err = new Error('unnable to start the recorder after 5 seconds'); | ||
// `.stopRecording()` was called already | ||
if (this.recorder === undefined) { | ||
return; | ||
} | ||
const err = new Error('Could not start recording within 5 seconds'); | ||
err.code = 'RECORDER_TIMEOUT'; | ||
this.recorder.kill(); | ||
delete this.recorder; | ||
reject(err); | ||
}, 5000); | ||
this.recorder.catch(err => { | ||
clearTimeout(timeout); | ||
delete this.recorder; | ||
reject(err.stderr ? new Error(err.stderr) : err); | ||
}); | ||
this.recorder.stdout.setEncoding('utf8'); | ||
this.recorder.stdout.on('data', data => { | ||
data = data.toString(); | ||
debuglog(data); | ||
log(data); | ||
if (data.replace(/\n|\s/gm, '') === 'R') { | ||
if (data.trim() === 'R') { | ||
// `R` is printed by Swift when the recording **actually** starts | ||
@@ -62,17 +96,2 @@ clearTimeout(timeout); | ||
}); | ||
this.recorder.on('error', reject); // TODO handle this; | ||
this.recorder.on('exit', code => { | ||
clearTimeout(timeout); | ||
let err; | ||
if (code === 0) { | ||
return; // we're good | ||
} else if (code === 1) { | ||
err = new Error('malformed arguments'); // TODO | ||
} else if (code === 2) { | ||
err = new Error('invalid coordinates'); // TODO | ||
} else { | ||
err = new Error('unknown error'); // TODO | ||
} | ||
reject(err); | ||
}); | ||
}); | ||
@@ -84,17 +103,11 @@ } | ||
if (this.recorder === undefined) { | ||
reject('call `startRecording` first'); | ||
reject(new Error('Call `.startRecording()` first')); | ||
return; | ||
} | ||
this.recorder.on('exit', code => { | ||
// at this point the movie file has been fully written to the file system | ||
if (code === 0) { | ||
delete this.recorder; | ||
resolve(this.tmpPath); | ||
// TODO: this file is deleted when the program exits | ||
// maybe we should add a note about this on the docs or implement a workaround | ||
delete this.tmpPath; | ||
} else { | ||
reject(code); // TODO | ||
} | ||
this.recorder.then(() => { | ||
delete this.recorder; | ||
resolve(this.tmpPath); | ||
}).catch(err => { | ||
reject(err.stderr ? new Error(err.stderr) : err); | ||
}); | ||
@@ -101,0 +114,0 @@ |
{ | ||
"name": "aperture", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "Record the screen on macOS", | ||
@@ -8,9 +8,23 @@ "license": "MIT", | ||
"author": "Matheus Fernandes <npm@matheus.top> (https://matheus.top)", | ||
"maintainers": [ | ||
{ | ||
"name": "Sindre Sorhus", | ||
"email": "sindresorhus@gmail.com", | ||
"url": "sindresorhus.com" | ||
} | ||
], | ||
"engines": { | ||
"node": ">=6" | ||
}, | ||
"scripts": { | ||
"test": "xo", | ||
"build": "cd swift && xcodebuild && mv build/release/aperture main && rm -r build", | ||
"build": "cd swift && xcodebuild -derivedDataPath $(mktemp -d) -scheme aperture", | ||
"prepublish": "npm run build" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"swift/main" | ||
], | ||
"dependencies": { | ||
"execa": "^0.5.0", | ||
"execa": "^0.6.0", | ||
"tmp": "0.0.31" | ||
@@ -21,6 +35,2 @@ }, | ||
}, | ||
"files": [ | ||
"index.js", | ||
"swift/main" | ||
], | ||
"xo": { | ||
@@ -27,0 +37,0 @@ "space": true, |
<p> | ||
<h1 align="center">aperture</h1> | ||
<h3 align="center">A library for screen recording </h3> | ||
<h1 align="center">Aperture</h1> | ||
<h4 align="center">Record the screen on macOS</h4> | ||
<p align="center"><a href="https://github.com/sindresorhus/xo"><img src="https://img.shields.io/badge/code_style-XO-5ed9c7.svg" alt="XO code style"></a></p> | ||
</p> | ||
## Install | ||
``` | ||
npm install aperture | ||
$ npm install aperture | ||
``` | ||
*Requires macOS 10.10 or later.* | ||
## Usage | ||
```javascript | ||
```js | ||
const aperture = require('aperture')(); | ||
const cropArea = {x: 100, y: 100, width: 500, height: 500}; | ||
const options = { | ||
fps: 30, | ||
cropArea: { | ||
x: 100, | ||
y: 100, | ||
width: 500, | ||
height: 500 | ||
} | ||
}; | ||
aperture.startRecording({fps: 30, cropArea}) | ||
.then(filePath => setTimeout(stopRecording, 3000)); | ||
aperture.startRecording(options).then(filePath => { | ||
setTimeout(() => { | ||
aperture.stopRecording().then(console.log); | ||
//=> '/var/folders/r9/65knbqts47x3yg055cd739qh0000gn/T/tmp-15694AAzbYX1vzi2X.mp4' | ||
}, 3000); | ||
}); | ||
``` | ||
function stopRecording() { | ||
aperture.stopRecording() | ||
.then(console.log); //=> /var/folders/r9/65knbqts47x3yg055cd739qh0000gn/T/tmp-15694AAzbYX1vzi2X.mp4 | ||
} | ||
``` | ||
## API | ||
@@ -36,7 +47,9 @@ | ||
Returns a promise for the path to the screen recording file. | ||
Returns a `Promise` for the path to the screen recording file. | ||
Fullfills when the recording starts or rejects if the recording didn't start after 5 seconds. | ||
### instance.stopRecording() | ||
Returns a promise for the path to the screen recording file. | ||
Returns a `Promise` for the path to the screen recording file. | ||
@@ -86,2 +99,4 @@ ### instance.getAudioSources() | ||
Enabling this will also enable the `showCursor` option. | ||
##### displayId | ||
@@ -104,4 +119,3 @@ | ||
`aperture` was built to fulfill the needs of [Kap](https://github.com/wulkano/kap), providing a JavaScript interface to the **best** available method for recording the screen. | ||
That's why it's currently a wrapper for a [Swift script](https://github.com/wulkano/aperture/blob/master/swift/aperture/main.swift) that records the screen using the [AVFoundation framework](https://developer.apple.com/av-foundation/). | ||
Aperture was built to fulfill the needs of [Kap](https://github.com/wulkano/kap), providing a JavaScript interface to the **best** available method for recording the screen. That's why it's currently a wrapper for a [Swift script](https://github.com/wulkano/aperture/blob/master/swift/aperture/main.swift) that records the screen using the [AVFoundation framework](https://developer.apple.com/av-foundation/). | ||
@@ -120,8 +134,10 @@ #### But you can use `ffmpeg -f avfoundation...` | ||
## Linux and Windows | ||
We want to bring `aperture` to Linux and Windows, but we don't have time and resources for such tasks (we're Mac users), so **any help is more than welcome**. We just want to enforce two things: **performance** and **quality** – it doesn't matter how (`ffmpeg`, custom built native lib, etc) they are achieved. | ||
We want to bring Aperture to Linux and Windows, but we don't have time or resources for such tasks (we're Mac users), so **any help is more than welcome**. We just want to enforce two things: **performance** and **quality** – it doesn't matter how (`ffmpeg`, custom built native lib, etc) they are achieved. | ||
## Upcoming | ||
`aperture` is in its early days. We're working on adding more features, such as *export to GIF*, compression options, support for multiple displays, support for audio and much more. Check out our [`aperture`](https://github.com/wulkano/kap/issues?q=is%3Aissue+is%3Aopen+label%3Aaperture) issues on **Kap** to learn more. | ||
Aperture is in its early days. We're working on adding more features, such as *export to GIF*, compression options, support for multiple displays, support for audio, and much more. Check out our [Aperture](https://github.com/wulkano/kap/issues?q=is%3Aissue+is%3Aopen+label%3Aaperture) issues on **Kap** to learn more. |
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
8608333
9.99%98
12.64%140
12.9%1
-50%2
-33.33%+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated