62 lines
1.9 KiB
JavaScript
62 lines
1.9 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
import path from 'node:path';
|
|
|
|
export class PortalCamera {
|
|
constructor({
|
|
helper = path.resolve(new URL('./py/portal_camera.py', import.meta.url).pathname),
|
|
python = 'python3',
|
|
spawnImpl = spawn,
|
|
tmpDir = '/tmp/jarvis-webcam',
|
|
timeoutMs = 8000,
|
|
accessTimeoutMs = 120_000,
|
|
} = {}) {
|
|
this.helper = helper;
|
|
this.python = python;
|
|
this.spawnImpl = spawnImpl;
|
|
this.tmpDir = tmpDir;
|
|
this.timeoutMs = timeoutMs;
|
|
this.accessTimeoutMs = accessTimeoutMs;
|
|
}
|
|
|
|
access({ device = '' } = {}) {
|
|
return this._run(['access'], this.accessTimeoutMs, device);
|
|
}
|
|
|
|
capture(output, { device = '' } = {}) {
|
|
return this._run(['capture', output], this.timeoutMs, device);
|
|
}
|
|
|
|
_run(args, timeoutMs, device) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = this.spawnImpl(this.python, [this.helper, ...args], {
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
env: { ...process.env, JARVIS_WEBCAM_DEVICE: device || '' },
|
|
});
|
|
let out = '';
|
|
let err = '';
|
|
let settled = false;
|
|
const timer = setTimeout(() => {
|
|
if (settled) return;
|
|
settled = true;
|
|
child.kill('SIGTERM');
|
|
reject(new Error('webcam helper timed out'));
|
|
}, timeoutMs);
|
|
const done = (fn) => (value) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timer);
|
|
fn(value);
|
|
};
|
|
child.stdout.on('data', (chunk) => { out += chunk; });
|
|
child.stderr.on('data', (chunk) => { err += chunk; });
|
|
child.on('error', done(reject));
|
|
child.on('close', (code) => {
|
|
let payload = {};
|
|
try { payload = JSON.parse(String(out).trim().split('\n').pop() || '{}'); } catch {}
|
|
if (code === 0 && payload.ok) done(resolve)(payload);
|
|
else done(reject)(new Error(payload.error || err.trim() || `webcam helper exited ${code}`));
|
|
});
|
|
});
|
|
}
|
|
}
|