Changed output to processor to break dependancy on HTTP

This commit is contained in:
Mark van Renswoude 2016-07-26 20:25:18 +02:00
parent 1b29f0bac1
commit 8d3ce97474
11 changed files with 372 additions and 187 deletions

View File

@ -0,0 +1,71 @@
var util = require('util');
var http = require('http');
var BaseProcessor = require('./baseprocessor');
function BaseHTTPStreamProcessor(camId, cam, now)
{
BaseProcessor.apply(this, arguments);
}
util.inherits(BaseHTTPStreamProcessor, BaseProcessor);
BaseHTTPStreamProcessor.prototype.run = function()
{
var self = this;
var timer = null;
var req = null;
var doCleanup = function()
{
if (timer !== null)
{
clearTimeout(timer);
timer = null;
}
self.cleanup();
self.doEnd();
};
req = http.request(self.cam.options.url, function(res)
{
self.doStart();
timer = setTimeout(function()
{
req.abort();
doCleanup();
}, self.cam.options.time || 10000);
res.on('end', function()
{
doCleanup();
});
res.pipe(self.getStream());
});
req.on('error', function(e)
{
console.log(e);
doCleanup();
});
req.end();
};
BaseHTTPStreamProcessor.prototype.getStream = function()
{
}
BaseHTTPStreamProcessor.prototype.cleanup = function()
{
}
module.exports = BaseHTTPStreamProcessor;

42
baseprocessor.js Normal file
View File

@ -0,0 +1,42 @@
var events = require('events');
var util = require('util');
function BaseProcessor(camId, cam, now)
{
this.camId = camId;
this.cam = cam;
this.now = now;
}
/*
Descendants are required to call the following functions:
doStart
Call when the processing starts. This may be delayed slightly, for example,
right after the stream is connected or when the first chunk of data arrives.
doEnd
Call when the processor is done.
*/
util.inherits(BaseProcessor, events.EventEmitter);
BaseProcessor.prototype.run = function()
{
}
BaseProcessor.prototype.doStart = function()
{
this.emit('start');
}
BaseProcessor.prototype.doEnd = function()
{
this.emit('end');
}
module.exports = BaseProcessor;

View File

@ -76,7 +76,7 @@ module.exports =
{
if (cams.hasOwnProperty(camId))
{
require('./output-' + cams[camId].output);
require('./processor-' + cams[camId].processor);
}
}
},
@ -84,71 +84,38 @@ module.exports =
start: function(camId, cam, now)
{
var timer = null;
var req = null;
var output = new (require('./output-' + cam.output))(camId, cam.outputOptions, now);
var duringDone = false;
var queueAfter = false;
var processor = new (require('./processor-' + cam.processor))(camId, cam, now);
var duringCommandsDone = false;
var queueAfterCommand = false;
runCommands(cam.before, 'before', function()
{
var cleanup = function()
{
if (timer !== null)
{
clearTimeout(timer);
timer = null;
}
if (output !== null)
{
output.cleanup();
output = null;
}
// If the stream is cut short, or the time is configured
// to be less than the duration of the 'during' commands,
// queue it up.
if (duringDone)
runCommands(cam.after, 'after', function() { });
else
queueAfter = true;
};
req = http.request(cam.url, function(res)
processor.on('start', function()
{
runCommands(cam.during, 'during', function()
{
if (queueAfter)
// Check if the processor has already finished and the
// 'after' commands should be run immediately.
if (queueAfterCommand)
runCommands(cam.after, 'after', function() { });
else
duringDone = true;
duringCommandsDone = true;
});
timer = setTimeout(function()
{
req.abort();
cleanup();
}, cam.time || config.defaultTime || 10000);
res.on('end', function()
{
cleanup();
});
res.pipe(output.getStream());
});
req.on('error', function(e)
processor.on('end', function()
{
console.log(e);
cleanup();
// If the stream is cut short, or the time is configured
// to be less than the duration of the 'during' commands,
// queue it up.
if (duringCommandsDone)
runCommands(cam.after, 'after', function() { });
else
queueAfterCommand = true;
});
req.end();
processor.run();
});
}
};

View File

@ -2,23 +2,23 @@ var foscam = require('./config-foscam');
var config = {};
config.port = 5705;
config.defaultTime = 5000;
config.cams =
{
frontdoor:
{
// You can specify any URL string here, the Foscam helper just makes it easier
// for compatible models. If you add your own config helper, please publish!
url: foscam.mjpegStream('10.138.1.10', 'viewer', 'verysecure'),
time: 5000,
output: 'ffmpeg',
outputOptions:
processor: 'ffmpeg',
options:
{
// You can specify any URL string here, the Foscam helper just makes it easier
// for compatible models. If you add your own config helper, please publish!
url: foscam.mjpegStream('10.138.1.10', 'viewer', 'verysecure'),
time: 5000,
inputFormat: 'mjpeg',
outputFormat: 'avi',
videoCodec: 'libx264',
filename: '[/srv/www/publiccam/]YYYY-MM-DD HH.mm.ss[/<camId>.avi]'
}
@ -27,16 +27,16 @@ config.cams =
backdoor:
{
// You can use username:password@ in the URL to log in with basic
// authentication. Note that some cams, like Foscam, use Digest
// authentication which is not supported. For Foscam you can provide
// the login in the parameters instead.
url: 'http://viewer:verysecure@10.138.1.11/video.cgi',
time: 5000,
output: 'mjpeg-split',
outputOptions:
processor: 'mjpeg-split',
options:
{
// You can use username:password@ in the URL to log in with basic
// authentication. Note that some cams, like Foscam, use Digest
// authentication which is not supported. For Foscam you can provide
// the login in the parameters instead.
url: 'http://viewer:verysecure@10.138.1.11/video.cgi',
time: 5000,
filename: '[/srv/www/publiccam/]YYYY-MM-DD HH.mm.ss[/<camId> <frame>.avi]'
}
}

View File

@ -1,45 +0,0 @@
var util = require('util');
var stream = require('stream');
var FfmpegCommand = require('fluent-ffmpeg');
var helpers = require('./helpers');
function OutputFFMPEG(camId, options, now)
{
this.output = new stream.PassThrough();
var command = new FfmpegCommand();
command
.input(this.output)
.inputFormat(options.inputFormat);
if (options.inputFormat === 'mjpeg')
command.inputOption('-use_wallclock_as_timestamps 1');
command
.output(helpers.createVariableFilename(options.filename, now,
{
camId: camId
}))
.videoCodec(options.videoCodec)
.outputFormat(options.outputFormat)
.run();
}
OutputFFMPEG.prototype.getStream = function()
{
return this.output;
};
OutputFFMPEG.prototype.cleanup = function()
{
if (this.output !== null)
{
this.output.end();
this.output = null;
}
}
module.exports = OutputFFMPEG;

View File

@ -1,44 +0,0 @@
var MjpegConsumer = require('mjpeg-consumer');
var FileOnWrite = require('file-on-write');
var helpers = require('./helpers');
function OutputMJPEGSplit(camId, options, now)
{
var frameCounter = 0;
this.output = new FileOnWrite({
filename: function(data)
{
frameCounter++;
return helpers.createVariableFilename(options.filename, now,
{
camId: camId,
frame: frameCounter
});
}
});
this.consumer = new MjpegConsumer();
this.consumer.pipe(this.output);
}
OutputMJPEGSplit.prototype.getStream = function()
{
return this.consumer;
};
OutputMJPEGSplit.prototype.cleanup = function()
{
if (this.consumer !== null)
{
this.consumer.end();
this.consumer = null;
}
}
module.exports = OutputMJPEGSplit;

View File

@ -1,30 +0,0 @@
var fs = require('fs');
var helpers = require('./helpers');
function OutputRaw(camId, options, now)
{
this.output = fs.createWriteStream(helpers.createVariableFilename(options.filename, now,
{
camId: camId
}));
}
OutputRaw.prototype.getStream = function()
{
return this.output;
};
OutputRaw.prototype.cleanup = function()
{
if (this.output !== null)
{
this.output.end();
this.output = null;
}
}
module.exports = OutputRaw;

55
processor-ffmpeg.js Normal file
View File

@ -0,0 +1,55 @@
var fs = require('fs');
var util = require('util');
var stream = require('stream');
var FfmpegCommand = require('fluent-ffmpeg');
var helpers = require('./helpers');
var BaseProcessor = require('./baseprocessor');
function FFMPEGProcessor()
{
BaseProcessor.apply(this, arguments);
}
util.inherits(FFMPEGProcessor, BaseProcessor);
FFMPEGProcessor.prototype.run = function()
{
var self = this;
var command = new FfmpegCommand();
command
.input(this.cam.options.input)
.inputOptions(['-t ' + this.cam.options.time]);
if (typeof(this.cam.options.inputFormat) !== 'undefined')
{
command.inputFormat(this.cam.options.inputFormat);
if (this.cam.options.inputFormat === 'mjpeg')
command.inputOption('-use_wallclock_as_timestamps 1');
}
command
.output(helpers.createVariableFilename(this.cam.options.filename, this.now,
{
camId: this.camId
}))
.videoCodec(this.cam.options.videoCodec)
.outputFormat(this.cam.options.outputFormat);
command.on('end', function()
{
self.doEnd();
});
self.doStart();
command.run();
FFMPEGProcessor.super_.prototype.run.call(this);
}
module.exports = FFMPEGProcessor;

59
processor-http-ffmpeg.js Normal file
View File

@ -0,0 +1,59 @@
var fs = require('fs');
var util = require('util');
var stream = require('stream');
var FfmpegCommand = require('fluent-ffmpeg');
var helpers = require('./helpers');
var BaseHTTPStreamProcessor = require('./basehttpstreamprocessor');
function HTTPFFMPEGProcessor()
{
BaseHTTPStreamProcessor.apply(this, arguments);
}
util.inherits(HTTPFFMPEGProcessor, BaseHTTPStreamProcessor);
HTTPFFMPEGProcessor.prototype.run = function()
{
this.output = new stream.PassThrough();
var command = new FfmpegCommand();
command
.input(this.output)
.inputFormat(this.cam.options.inputFormat);
if (this.cam.options.inputFormat === 'mjpeg')
command.inputOption('-use_wallclock_as_timestamps 1');
command
.output(helpers.createVariableFilename(this.cam.options.filename, this.now,
{
camId: this.camId
}))
.videoCodec(this.cam.options.videoCodec)
.outputFormat(this.cam.options.outputFormat)
.run();
HTTPFFMPEGProcessor.super_.prototype.run.call(this);
}
HTTPFFMPEGProcessor.prototype.getStream = function()
{
return this.output;
}
HTTPFFMPEGProcessor.prototype.cleanup = function()
{
if (this.output !== null)
{
this.output.end();
this.output = null;
}
}
module.exports = HTTPFFMPEGProcessor;

View File

@ -0,0 +1,64 @@
var fs = require('fs');
var util = require('util');
var MjpegConsumer = require('mjpeg-consumer');
var FileOnWrite = require('file-on-write');
var helpers = require('./helpers');
var BaseHTTPStreamProcessor = require('./basehttpstreamprocessor');
function HTTPMJPEGSplitProcessor()
{
BaseHTTPStreamProcessor.apply(this, arguments);
}
util.inherits(HTTPMJPEGSplitProcessor, BaseHTTPStreamProcessor);
HTTPMJPEGSplitProcessor.prototype.run = function()
{
var self = this;
var frameCounter = 0;
this.output = new FileOnWrite({
filename: function(data)
{
frameCounter++;
return helpers.createVariableFilename(self.cam.options.filename, self.now,
{
camId: self.camId,
frame: frameCounter
});
}
});
this.consumer = new MjpegConsumer();
this.consumer.pipe(this.output);
HTTPMJPEGSplitProcessor.super_.prototype.run.call(this);
}
HTTPMJPEGSplitProcessor.prototype.getStream = function()
{
return this.consumer;
}
module.exports = HTTPMJPEGSplitProcessor;
HTTPMJPEGSplitProcessor.prototype.cleanup = function()
{
if (this.consumer !== null)
{
this.consumer.end();
this.consumer = null;
}
}
module.exports = HTTPMJPEGSplitProcessor;

46
processor-http-raw.js Normal file
View File

@ -0,0 +1,46 @@
var fs = require('fs');
var util = require('util');
var helpers = require('./helpers');
var BaseHTTPStreamProcessor = require('./basehttpstreamprocessor');
function HTTPRawProcessor()
{
BaseHTTPStreamProcessor.apply(this, arguments);
}
util.inherits(HTTPRawProcessor, BaseHTTPStreamProcessor);
HTTPRawProcessor.prototype.run = function()
{
this.output = fs.createWriteStream(helpers.createVariableFilename(this.cam.options.filename, this.now,
{
camId: this.camId
}));
HTTPRawProcessor.super_.prototype.run.call(this);
}
HTTPRawProcessor.prototype.getStream = function()
{
return this.output;
}
module.exports = HTTPRawProcessor;
HTTPRawProcessor.prototype.cleanup = function()
{
if (this.output !== null)
{
this.output.end();
this.output = null;
}
}
module.exports = HTTPRawProcessor;