Skip to content

Commit

Permalink
Always log FFmpeg errors, snapshot improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Sunoo committed Mar 6, 2021
1 parent a5029e6 commit 9073cf5
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 249 deletions.
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"displayName": "Homebridge Camera FFmpeg",
"name": "homebridge-camera-ffmpeg",
"version": "3.1.1",
"version": "3.1.2",
"description": "Homebridge Plugin Providing FFmpeg-based Camera Support",
"main": "dist/index.js",
"license": "ISC",
Expand Down Expand Up @@ -63,9 +63,9 @@
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",
"eslint": "^7.21.0",
"homebridge": "^1.3.1",
"homebridge": "^1.3.2",
"rimraf": "^3.0.2",
"typescript": "^4.2.2"
"typescript": "^4.2.3"
},
"dependencies": {
"ffmpeg-for-homebridge": "^0.0.9",
Expand Down
119 changes: 67 additions & 52 deletions src/ffmpeg.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { ChildProcess, spawn } from 'child_process';
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
import { StreamRequestCallback } from 'homebridge';
import readline from 'readline';
import { Writable } from 'stream';
import { Logger } from './logger';
import { StreamingDelegate } from './streamingDelegate';

interface FfmpegProgress {
frame?: number;
fps?: number;
stream_0_0_q?: number;
bitrate?: number;
total_size?: number;
out_time_us?: number;
dup_frames?: number;
drop_frames?: number;
speed?: number;
}
type FfmpegProgress = {
frame: number;
fps: number;
stream_q: number;
bitrate: number;
total_size: number;
out_time_us: number;
out_time: string;
dup_frames: number;
drop_frames: number;
speed: number;
progress: string;
};

export class FfmpegProcess {
private readonly process: ChildProcess;
private readonly process: ChildProcessWithoutNullStreams;

constructor(cameraName: string, sessionId: string, videoProcessor: string, ffmpegArgs: string, log: Logger,
debug = false, delegate: StreamingDelegate, callback?: StreamRequestCallback) {
Expand All @@ -27,38 +30,40 @@ export class FfmpegProcess {
const startTime = Date.now();
this.process = spawn(videoProcessor, ffmpegArgs.split(/\s+/), { env: process.env });

this.process.stdout?.on('data', () => {
//const progress = this.parseProgress(data);
if (!started) {
started = true;
if (callback) {
callback();
}
const runtime = (Date.now() - startTime) / 1000;
const message = 'Getting the first frames took ' + runtime + ' seconds.';
if (runtime < 5) {
log.debug(message, cameraName, debug);
} else if (runtime < 22) {
log.warn(message, cameraName);
} else {
log.error(message, cameraName);
this.process.stdout.on('data', (data) => {
const progress = this.parseProgress(data);
if (progress) {
if (!started && progress.frame > 0) {
started = true;
const runtime = (Date.now() - startTime) / 1000;
const message = 'Getting the first frames took ' + runtime + ' seconds.';
if (runtime < 5) {
log.debug(message, cameraName, debug);
} else if (runtime < 22) {
log.warn(message, cameraName);
} else {
log.error(message, cameraName);
}
}
}
});
this.process.stdin?.on('error', (error: Error) => {
if (!error.message.includes('EPIPE')) {
log.error(error.message, cameraName);
const stderr = readline.createInterface({
input: this.process.stderr,
terminal: false
});
stderr.on('line', (line: string) => {
if (callback) {
callback();
callback = undefined;
}
if (line.match(/\[(panic|fatal|error)\]/)) {
log.error(line, cameraName);
} else if (debug) {
log.debug(line, cameraName, true);
}
});
if (debug) {
this.process.stderr?.on('data', (data) => {
data.toString().split('\n').forEach((line: string) => {
log.debug(line, cameraName, true);
});
});
}
this.process.on('error', (error: Error) => {
log.error('Failed to start stream: ' + error.message, cameraName);
log.error('FFmpeg process creation failed: ' + error.message, cameraName);
if (callback) {
callback(new Error('FFmpeg process creation failed'));
}
Expand Down Expand Up @@ -89,19 +94,29 @@ export class FfmpegProcess {
const input = data.toString();

if (input.indexOf('frame=') == 0) {
const progress: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
input.split('\n').forEach((line) => {
const split = line.split('=', 2);

const key = split[0];
const value = parseFloat(split[1]);

if (!isNaN(value)) {
progress[key] = value;
}
});
try {
const progress = new Map<string, string>();
input.split(/\r?\n/).forEach((line) => {
const split = line.split('=', 2);
progress.set(split[0], split[1]);
});

return progress;
return {
frame: parseInt(progress.get('frame')!),
fps: parseFloat(progress.get('fps')!),
stream_q: parseFloat(progress.get('stream_0_0_q')!),
bitrate: parseFloat(progress.get('bitrate')!),
total_size: parseInt(progress.get('total_size')!),
out_time_us: parseInt(progress.get('out_time_us')!),
out_time: progress.get('out_time')!.trim(),
dup_frames: parseInt(progress.get('dup_frames')!),
drop_frames: parseInt(progress.get('drop_frames')!),
speed: parseFloat(progress.get('speed')!),
progress: progress.get('progress')!.trim()
};
} catch {
return undefined;
}
} else {
return undefined;
}
Expand All @@ -111,7 +126,7 @@ export class FfmpegProcess {
this.process.kill('SIGKILL');
}

public getStdin(): Writable | null {
public getStdin(): Writable {
return this.process.stdin;
}
}
11 changes: 2 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
this.api = api;
this.config = config as FfmpegPlatformConfig;

if (__dirname.includes('hoobs')) {
this.log.warn('This plugin has not been tested under HOOBS, it is highly recommended that ' +
'you switch to Homebridge: https://git.io/Jtxb0');
}

this.config.cameras?.forEach((cameraConfig: CameraConfig) => {
let error = false;

Expand Down Expand Up @@ -220,9 +215,8 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
doorbellTrigger.updateCharacteristic(hap.Characteristic.On, true);
let timeoutConfig = this.cameraConfigs.get(accessory.UUID)?.motionTimeout;
timeoutConfig = timeoutConfig && timeoutConfig > 0 ? timeoutConfig : 1;
const log = this.log;
const timer = setTimeout(() => {
log.debug('Doorbell handler timeout.', accessory.displayName);
this.log.debug('Doorbell handler timeout.', accessory.displayName);
this.doorbellTimers.delete(accessory.UUID);
doorbellTrigger.updateCharacteristic(hap.Characteristic.On, false);
}, timeoutConfig * 1000);
Expand Down Expand Up @@ -273,9 +267,8 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
timeoutConfig = minimumTimeout;
}
if (timeoutConfig > 0) {
const log = this.log;
const timer = setTimeout(() => {
log.debug('Motion handler timeout.', accessory.displayName);
this.log.debug('Motion handler timeout.', accessory.displayName);
this.motionTimers.delete(accessory.UUID);
motionSensor.updateCharacteristic(hap.Characteristic.MotionDetected, false);
if (motionTrigger) {
Expand Down
Loading

0 comments on commit 9073cf5

Please sign in to comment.