Comparing version
240
index.d.ts
@@ -1,156 +0,152 @@ | ||
declare namespace aperture { | ||
type Screen = { | ||
id: number; | ||
name: string; | ||
}; | ||
export type Screen = { | ||
id: number; | ||
name: string; | ||
}; | ||
type AudioDevice = { | ||
id: string; | ||
name: string; | ||
}; | ||
export type AudioDevice = { | ||
id: string; | ||
name: string; | ||
}; | ||
type VideoCodec = 'h264' | 'hevc' | 'proRes422' | 'proRes4444'; | ||
export type VideoCodec = 'h264' | 'hevc' | 'proRes422' | 'proRes4444'; | ||
type RecordingOptions = { | ||
/** | ||
Number of frames per seconds. | ||
*/ | ||
readonly fps?: number; | ||
export type RecordingOptions = { | ||
/** | ||
Number of frames per seconds. | ||
*/ | ||
readonly fps?: number; | ||
/** | ||
Record only an area of the screen. | ||
*/ | ||
readonly cropArea?: { | ||
x: number; | ||
y: number; | ||
width: number; | ||
height: number; | ||
}; | ||
/** | ||
Record only an area of the screen. | ||
*/ | ||
readonly cropArea?: { | ||
x: number; | ||
y: number; | ||
width: number; | ||
height: number; | ||
}; | ||
/** | ||
Show the cursor in the screen recording. | ||
*/ | ||
readonly showCursor?: boolean; | ||
/** | ||
Show the cursor in the screen recording. | ||
*/ | ||
readonly showCursor?: boolean; | ||
/** | ||
Highlight cursor clicks in the screen recording. | ||
/** | ||
Highlight cursor clicks in the screen recording. | ||
Enabling this will also enable the `showCursor` option. | ||
*/ | ||
readonly highlightClicks?: boolean; | ||
Enabling this will also enable the `showCursor` option. | ||
*/ | ||
readonly highlightClicks?: boolean; | ||
/** | ||
Screen to record. | ||
/** | ||
Screen to record. | ||
Defaults to primary screen. | ||
*/ | ||
readonly screenId?: number; | ||
Defaults to primary screen. | ||
*/ | ||
readonly screenId?: number; | ||
/** | ||
Audio device to include in the screen recording. | ||
/** | ||
Audio device to include in the screen recording. | ||
Should be one of the `id`'s from `aperture.audioDevices()`. | ||
*/ | ||
readonly audioDeviceId?: string; | ||
Should be one of the `id`'s from `audioDevices()`. | ||
*/ | ||
readonly audioDeviceId?: string; | ||
/** | ||
Video codec to use. | ||
/** | ||
Video codec to use. | ||
A computer with Intel 6th generation processor or newer is strongly recommended for the `hevc` codec, as otherwise it will use software encoding, which only produces 3 FPS fullscreen recording. | ||
A computer with Intel 6th generation processor or newer is strongly recommended for the `hevc` codec, as otherwise it will use software encoding, which only produces 3 FPS fullscreen recording. | ||
The `proRes422` and `proRes4444` codecs are uncompressed data. They will create huge files. | ||
*/ | ||
readonly videoCodec?: VideoCodec; | ||
}; | ||
The `proRes422` and `proRes4444` codecs are uncompressed data. They will create huge files. | ||
*/ | ||
readonly videoCodec?: VideoCodec; | ||
}; | ||
interface Recorder { | ||
/** | ||
Returns a `Promise` that fullfills when the recording starts or rejects if the recording didn't start after 5 seconds. | ||
*/ | ||
startRecording: (options?: RecordingOptions) => Promise<void>; | ||
export type Recorder = { | ||
/** | ||
Returns a `Promise` that fullfills when the recording starts or rejects if the recording didn't start after 5 seconds. | ||
*/ | ||
startRecording: (options?: RecordingOptions) => Promise<void>; | ||
/** | ||
`Promise` that fullfills with the path to the screen recording file when it's ready. This will never reject. | ||
/** | ||
`Promise` that fullfills with the path to the screen recording file when it's ready. This will never reject. | ||
Only available while a recording is happening, `undefined` otherwise. | ||
Only available while a recording is happening, `undefined` otherwise. | ||
Usually, this resolves around 1 second before the recording starts, but that's not guaranteed. | ||
*/ | ||
isFileReady: Promise<string> | undefined; | ||
Usually, this resolves around 1 second before the recording starts, but that's not guaranteed. | ||
*/ | ||
isFileReady: Promise<string> | undefined; | ||
/** | ||
Pauses the recording. To resume, call `recorder.resume()`. | ||
/** | ||
Pauses the recording. To resume, call `recorder.resume()`. | ||
Returns a `Promise` that fullfills when the recording has been paused. | ||
*/ | ||
pause: () => Promise<void>; | ||
Returns a `Promise` that fullfills when the recording has been paused. | ||
*/ | ||
pause: () => Promise<void>; | ||
/** | ||
Resumes the recording if it's been paused. | ||
/** | ||
Resumes the recording if it's been paused. | ||
Returns a `Promise` that fullfills when the recording has been resumed. | ||
*/ | ||
resume: () => Promise<void>; | ||
Returns a `Promise` that fullfills when the recording has been resumed. | ||
*/ | ||
resume: () => Promise<void>; | ||
/** | ||
Returns a `Promise` that resolves with a boolean indicating whether or not the recording is currently paused. | ||
*/ | ||
isPaused: () => Promise<boolean>; | ||
/** | ||
Returns a `Promise` that resolves with a boolean indicating whether or not the recording is currently paused. | ||
*/ | ||
isPaused: () => Promise<boolean>; | ||
/** | ||
Returns a `Promise` for the path to the screen recording file. | ||
*/ | ||
stopRecording: () => Promise<string>; | ||
} | ||
} | ||
declare const aperture: (() => aperture.Recorder) & { | ||
/** | ||
Get a list of available video codecs. | ||
Returns a `Promise` for the path to the screen recording file. | ||
*/ | ||
stopRecording: () => Promise<string>; | ||
}; | ||
The key is the `videoCodec` option name and the value is the codec name. | ||
/** | ||
Get a list of available video codecs. | ||
It only returns `hevc` if your computer supports HEVC hardware encoding. | ||
The key is the `videoCodec` option name and the value is the codec name. | ||
@example | ||
``` | ||
Map { | ||
'h264' => 'H264', | ||
'hevc' => 'HEVC', | ||
'proRes422' => 'Apple ProRes 422', | ||
'proRes4444' => 'Apple ProRes 4444' | ||
} | ||
``` | ||
*/ | ||
videoCodecs: Map<aperture.VideoCodec, string>; | ||
It only returns `hevc` if your computer supports HEVC hardware encoding. | ||
/** | ||
Get a list of screens. | ||
@example | ||
``` | ||
Map { | ||
'h264' => 'H264', | ||
'hevc' => 'HEVC', | ||
'proRes422' => 'Apple ProRes 422', | ||
'proRes4444' => 'Apple ProRes 4444' | ||
} | ||
``` | ||
*/ | ||
export function videoCodecs(): Map<VideoCodec, string>; | ||
The first screen is the primary screen. | ||
/** | ||
Get a list of screens. | ||
@example | ||
``` | ||
[{ | ||
id: 69732482, | ||
name: 'Color LCD' | ||
}] | ||
``` | ||
*/ | ||
screens: () => Promise<aperture.Screen[]>; | ||
The first screen is the primary screen. | ||
/** | ||
Get a list of audio devices. | ||
@example | ||
``` | ||
[{ | ||
id: 69732482, | ||
name: 'Color LCD' | ||
}] | ||
``` | ||
*/ | ||
export function screens(): Promise<Screen[]>; | ||
@example | ||
``` | ||
[{ | ||
id: 'AppleHDAEngineInput:1B,0,1,0:1', | ||
name: 'Built-in Microphone' | ||
}] | ||
``` | ||
*/ | ||
audioDevices: () => Promise<aperture.AudioDevice[]>; | ||
}; | ||
/** | ||
Get a list of audio devices. | ||
export = aperture; | ||
@example | ||
``` | ||
[{ | ||
id: 'AppleHDAEngineInput:1B,0,1,0:1', | ||
name: 'Built-in Microphone' | ||
}] | ||
``` | ||
*/ | ||
export function audioDevices(): Promise<AudioDevice[]>; | ||
export const recorder: Recorder; |
149
index.js
@@ -1,11 +0,11 @@ | ||
'use strict'; | ||
const os = require('os'); | ||
const {debuglog} = require('util'); | ||
const path = require('path'); | ||
const execa = require('execa'); | ||
const tempy = require('tempy'); | ||
const macosVersion = require('macos-version'); | ||
const fileUrl = require('file-url'); | ||
const electronUtil = require('electron-util/node'); | ||
const delay = require('delay'); | ||
import os from 'node:os'; | ||
import {debuglog} from 'node:util'; | ||
import path from 'node:path'; | ||
import url from 'node:url'; | ||
import {execa} from 'execa'; | ||
import {temporaryFile} from 'tempy'; | ||
import {assertMacOSVersionGreaterThanOrEqualTo} from 'macos-version'; | ||
import fileUrl from 'file-url'; | ||
import {fixPathForAsarUnpack} from 'electron-util/node'; | ||
import delay from 'delay'; | ||
@@ -15,4 +15,5 @@ const log = debuglog('aperture'); | ||
const dirname_ = path.dirname(url.fileURLToPath(import.meta.url)); | ||
// Workaround for https://github.com/electron/electron/issues/9459 | ||
const BIN = path.join(electronUtil.fixPathForAsarUnpack(__dirname), 'aperture'); | ||
const BINARY = path.join(fixPathForAsarUnpack(dirname_), 'aperture'); | ||
@@ -23,3 +24,4 @@ const supportsHevcHardwareEncoding = (() => { | ||
// All Apple silicon Macs support HEVC hardware encoding. | ||
if (cpuModel.startsWith('Apple ')) { // Source string example: `'Apple M1'` | ||
if (cpuModel.startsWith('Apple ')) { | ||
// Source string example: `'Apple M1'` | ||
return true; | ||
@@ -39,5 +41,5 @@ } | ||
class Aperture { | ||
class Recorder { | ||
constructor() { | ||
macosVersion.assertGreaterThanOrEqualTo('10.13'); | ||
assertMacOSVersionGreaterThanOrEqualTo('10.13'); | ||
} | ||
@@ -52,3 +54,3 @@ | ||
audioDeviceId = undefined, | ||
videoCodec = 'h264' | ||
videoCodec = 'h264', | ||
} = {}) { | ||
@@ -63,3 +65,3 @@ this.processId = getRandomId(); | ||
this.tmpPath = tempy.file({extension: 'mp4'}); | ||
this.tmpPath = temporaryFile({extension: 'mp4'}); | ||
@@ -70,9 +72,8 @@ if (highlightClicks === true) { | ||
if (typeof cropArea === 'object' && | ||
( | ||
typeof cropArea.x !== 'number' || | ||
typeof cropArea.y !== 'number' || | ||
typeof cropArea.width !== 'number' || | ||
typeof cropArea.height !== 'number' | ||
) | ||
if ( | ||
typeof cropArea === 'object' | ||
&& (typeof cropArea.x !== 'number' | ||
|| typeof cropArea.y !== 'number' | ||
|| typeof cropArea.width !== 'number' | ||
|| typeof cropArea.height !== 'number') | ||
) { | ||
@@ -89,3 +90,3 @@ reject(new Error('Invalid `cropArea` option object')); | ||
screenId, | ||
audioDeviceId | ||
audioDeviceId, | ||
}; | ||
@@ -96,3 +97,3 @@ | ||
[cropArea.x, cropArea.y], | ||
[cropArea.width, cropArea.height] | ||
[cropArea.width, cropArea.height], | ||
]; | ||
@@ -106,3 +107,3 @@ } | ||
['proRes422', 'apcn'], | ||
['proRes4444', 'ap4h'] | ||
['proRes4444', 'ap4h'], | ||
]); | ||
@@ -149,10 +150,8 @@ | ||
this.recorder = execa( | ||
BIN, [ | ||
'record', | ||
'--process-id', | ||
this.processId, | ||
JSON.stringify(recorderOptions) | ||
] | ||
); | ||
this.recorder = execa(BINARY, [ | ||
'record', | ||
'--process-id', | ||
this.processId, | ||
JSON.stringify(recorderOptions), | ||
]); | ||
@@ -171,12 +170,10 @@ this.recorder.catch(error => { | ||
async waitForEvent(name, parse) { | ||
const {stdout} = await execa( | ||
BIN, [ | ||
'events', | ||
'listen', | ||
'--process-id', | ||
this.processId, | ||
'--exit', | ||
name | ||
] | ||
); | ||
const {stdout} = await execa(BINARY, [ | ||
'events', | ||
'listen', | ||
'--process-id', | ||
this.processId, | ||
'--exit', | ||
name, | ||
]); | ||
@@ -189,11 +186,9 @@ if (parse) { | ||
async sendEvent(name, parse) { | ||
const {stdout} = await execa( | ||
BIN, [ | ||
'events', | ||
'send', | ||
'--process-id', | ||
this.processId, | ||
name | ||
] | ||
); | ||
const {stdout} = await execa(BINARY, [ | ||
'events', | ||
'send', | ||
'--process-id', | ||
this.processId, | ||
name, | ||
]); | ||
@@ -243,39 +238,35 @@ if (parse) { | ||
module.exports = () => new Aperture(); | ||
export const recorder = new Recorder(); | ||
module.exports.screens = async () => { | ||
const {stderr} = await execa(BIN, ['list', 'screens']); | ||
const removeWarnings = string => string.split('\n').filter(line => !line.includes('] WARNING:')).join('\n'); | ||
export const screens = async () => { | ||
const {stderr} = await execa(BINARY, ['list', 'screens']); | ||
try { | ||
return JSON.parse(stderr); | ||
} catch { | ||
return stderr; | ||
return JSON.parse(removeWarnings(stderr)); | ||
} catch (error) { | ||
throw new Error(stderr, {cause: error}); | ||
} | ||
}; | ||
module.exports.audioDevices = async () => { | ||
const {stderr} = await execa(BIN, ['list', 'audio-devices']); | ||
export const audioDevices = async () => { | ||
const {stderr} = await execa(BINARY, ['list', 'audio-devices']); | ||
try { | ||
return JSON.parse(stderr); | ||
} catch { | ||
return stderr; | ||
return JSON.parse(removeWarnings(stderr)); | ||
} catch (error) { | ||
throw new Error(stderr, {cause: error}); | ||
} | ||
}; | ||
Object.defineProperty(module.exports, 'videoCodecs', { | ||
get() { | ||
const codecs = new Map([ | ||
['h264', 'H264'], | ||
['hevc', 'HEVC'], | ||
['proRes422', 'Apple ProRes 422'], | ||
['proRes4444', 'Apple ProRes 4444'] | ||
]); | ||
export const videoCodecs = new Map([ | ||
['h264', 'H264'], | ||
['hevc', 'HEVC'], | ||
['proRes422', 'Apple ProRes 422'], | ||
['proRes4444', 'Apple ProRes 4444'], | ||
]); | ||
if (!supportsHevcHardwareEncoding) { | ||
codecs.delete('hevc'); | ||
} | ||
return codecs; | ||
} | ||
}); | ||
if (!supportsHevcHardwareEncoding) { | ||
videoCodecs.delete('hevc'); | ||
} |
{ | ||
"name": "aperture", | ||
"version": "6.1.3", | ||
"version": "7.0.0", | ||
"description": "Record the screen on macOS", | ||
"license": "MIT", | ||
"repository": "wulkano/aperture-node", | ||
"type": "module", | ||
"exports": { | ||
"types": "./index.d.ts", | ||
"default": "./index.js" | ||
}, | ||
"sideEffects": false, | ||
"engines": { | ||
"node": ">=10" | ||
"node": ">=18" | ||
}, | ||
@@ -21,16 +27,16 @@ "scripts": { | ||
"dependencies": { | ||
"delay": "^5.0.0", | ||
"electron-util": "^0.14.2", | ||
"execa": "^5.0.0", | ||
"file-url": "^3.0.0", | ||
"macos-version": "^5.2.1", | ||
"tempy": "^1.0.0" | ||
"delay": "^6.0.0", | ||
"electron-util": "^0.18.1", | ||
"execa": "^8.0.1", | ||
"file-url": "^4.0.0", | ||
"macos-version": "^6.0.0", | ||
"tempy": "^3.1.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^2.4.0", | ||
"file-type": "^12.0.0", | ||
"read-chunk": "^3.2.0", | ||
"tsd": "^0.14.0", | ||
"xo": "^0.38.2" | ||
"ava": "^6.1.2", | ||
"file-type": "^19.0.0", | ||
"read-chunk": "^4.0.3", | ||
"tsd": "^0.30.7", | ||
"xo": "^0.58.0" | ||
} | ||
} |
@@ -16,4 +16,4 @@ # aperture-node | ||
```js | ||
const delay = require('delay'); | ||
const aperture = require('aperture')(); | ||
import {setTimeout} from 'node:timers/promises'; | ||
import {recorder} from 'aperture'; | ||
@@ -26,19 +26,19 @@ const options = { | ||
width: 500, | ||
height: 500 | ||
} | ||
height: 500, | ||
}, | ||
}; | ||
(async () => { | ||
await aperture.startRecording(options); | ||
await delay(3000); | ||
console.log(await aperture.stopRecording()); | ||
//=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/cdf4f7df426c97880f8c10a1600879f7.mp4' | ||
})(); | ||
await recorder.startRecording(options); | ||
await setTimeout(3000); | ||
console.log(await recorder.stopRecording()); | ||
//=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/cdf4f7df426c97880f8c10a1600879f7.mp4' | ||
``` | ||
See [`example.js`](example.js) if you want to quickly try it out. *(The example requires Node.js 8+)* | ||
See [`example.js`](example.js) if you want to quickly try it out. _(The example requires Node.js 18+)_ | ||
## API | ||
#### aperture.screens() -> `Promise<Object[]>` | ||
#### screens() -> `Promise<Object[]>` | ||
@@ -50,9 +50,11 @@ Get a list of screens. The first screen is the primary screen. | ||
```js | ||
[{ | ||
id: 69732482, | ||
name: 'Color LCD' | ||
}] | ||
[ | ||
{ | ||
id: 69732482, | ||
name: 'Color LCD', | ||
}, | ||
]; | ||
``` | ||
#### aperture.audioDevices() -> `Promise<Object[]>` | ||
#### audioDevices() -> `Promise<Object[]>` | ||
@@ -64,9 +66,11 @@ Get a list of audio devices. | ||
```js | ||
[{ | ||
id: 'AppleHDAEngineInput:1B,0,1,0:1', | ||
name: 'Built-in Microphone' | ||
}] | ||
[ | ||
{ | ||
id: 'AppleHDAEngineInput:1B,0,1,0:1', | ||
name: 'Built-in Microphone', | ||
}, | ||
]; | ||
``` | ||
#### aperture.videoCodecs -> `Map` | ||
#### videoCodecs -> `Map` | ||
@@ -86,3 +90,3 @@ Get a list of available video codecs. The key is the `videoCodec` option name and the value is the codec name. It only returns `hevc` if your computer supports HEVC hardware encoding. | ||
#### recorder = `aperture()` | ||
#### recorder | ||
@@ -158,3 +162,3 @@ #### recorder.startRecording([options?](#options)) | ||
Type: `number`\ | ||
Default: `aperture.screens()[0]` *(Primary screen)* | ||
Default: `aperture.screens()[0]` _(Primary screen)_ | ||
@@ -161,0 +165,0 @@ Screen to record. |
Sorry, the diff of this file is not supported yet
197
2.07%Yes
NaN3483484
-19.4%330
-3.79%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated