UNPKG

@viguza/homebridge-ezviz

Version:

A short description about what your plugin does.

163 lines 6.05 kB
import execa from 'execa'; import pathToFfmpeg from 'ffmpeg-for-homebridge'; export async function doesFfmpegSupportCodec(codec, ffmpegPath) { if (!codec) { return false; } if (codec === 'copy') { return true; } const output = await execa(ffmpegPath, ['-codecs']); return output.stdout.includes(codec); } export async function getCodecsOutput(ffmpegPath) { const output = await execa(ffmpegPath, ['-codecs']); return output.stdout; } export async function getDefaultEncoder(ffmpegPath) { const output = await execa(ffmpegPath, ['-codecs']); const validEncoders = ['h264_omx', 'h264_videotoolbox']; validEncoders.forEach((encoder) => { if (output.stdout.includes(encoder)) { return encoder; } }); return 'libx264'; } export async function isFfmpegInstalled(ffmpegPath) { try { await execa(ffmpegPath, ['-codecs']); return true; } catch (_) { return false; } } export async function getSnapshot(url, customFfmpeg) { const command = ['-i', url, '-vframes', '1', '-f', 'mjpeg', '-']; const videoProcessor = customFfmpeg || pathToFfmpeg || 'ffmpeg'; const ff = await execa(videoProcessor, command, { env: process.env, encoding: null }); return ff.stdout; } export class FfmpegProcess { ff; killTimeout; startTimeout; constructor(title, command, log, callback, delegate, sessionId, ffmpegDebugOutput, customFfmpeg) { let started = false; let startedCallback = false; const controller = delegate.controller; const cmdOutput = `${title} command: ffmpeg ${command.join(' ')}`; if (ffmpegDebugOutput) { log.info(cmdOutput); } else { log.debug(cmdOutput); } const videoProcessor = customFfmpeg || pathToFfmpeg || 'ffmpeg'; let lastOutput = ''; try { this.ff = execa(videoProcessor, command, { env: process.env }); this.startTimeout = setTimeout(() => { if (!started && callback && !startedCallback) { started = true; startedCallback = true; log.debug(`${title}: Stream start timeout reached, calling callback`); callback(); } }, 3000); this.ff.stderr?.on('data', (data) => { const output = String(data); lastOutput = `${title}: ${output}`; if (ffmpegDebugOutput) { log.info(lastOutput); } else { log.debug(lastOutput); } if (!started && (output.includes('frame=') || output.includes('fps=') || output.includes('size='))) { started = true; if (callback && !startedCallback) { startedCallback = true; if (this.startTimeout) { clearTimeout(this.startTimeout); this.startTimeout = undefined; } callback(); } } }); this.ff.stdout?.on('data', (data) => { if (ffmpegDebugOutput) { log.debug(`${title} stdout: ${String(data)}`); } }); this.ff.on('exit', (code, signal) => { if (this.killTimeout) { clearTimeout(this.killTimeout); this.killTimeout = undefined; } if (this.startTimeout) { clearTimeout(this.startTimeout); this.startTimeout = undefined; } if (code !== null && code !== 0 && !signal) { const lines = lastOutput.split('\n'); let output = ''; if (lines.length > 1) { output = lines[lines.length - 2]; if (!output.includes('Exiting normally') && !output.includes('SIGTERM')) { log.error(`${title} exited with error: ${output}`); } } if (!started && callback && !startedCallback) { startedCallback = true; callback(new Error(output || 'FFmpeg process failed to start')); } } delegate.stopStream(sessionId); controller?.forceStopStreamingSession(sessionId); }); this.ff.on('error', (error) => { log.error(`${title} process error: ${error.message}`); if (callback && !startedCallback) { startedCallback = true; callback(new Error(`FFmpeg process error: ${error.message}`)); } delegate.stopStream(sessionId); controller?.forceStopStreamingSession(sessionId); }); } catch (error) { log.error(`[${title}] Failed to start stream: ` + error); if (callback && !startedCallback) { startedCallback = true; callback(new Error('ffmpeg process creation failed!')); delegate.stopStream(sessionId); } } } stop() { if (!this.ff) { return; } try { this.ff.stdin?.end(); this.ff.kill('SIGTERM'); // Set a timeout to force kill if process doesn't terminate gracefully this.killTimeout = setTimeout(() => { this.ff?.kill('SIGKILL'); }, 5000); } catch (error) { // Process might already be dead, ignore the error } } getStdin() { return this.ff?.stdin; } getStdout() { return this.ff?.stdout; } } //# sourceMappingURL=ffmpeg.js.map