Reputation: 924
I have a camera which sends JPEG images to a webserver through a continuous multipart http stream. When I visit the IP address of the stream, the browser reads this stream as a series of images which mimics a video. I am wanting to download the files from this stream to a remote server.
I do not know how to parse the stream and the save the files to either my ubuntu server directly, or through my ruby on rails application filesystem.
Here is how the browser sees the stream:
Response Headers:
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame
Request Headers:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cache-Control: max-age=0
Connection: keep-alive
DNT: 1
Host: my-ip-address
Please help me find the correct approach to this problem.
Upvotes: 6
Views: 4219
Reputation: 146510
So I found that ffmpeg
also has featrue for saving the images
-vframes option
Output a single frame from the video into an image file:
ffmpeg -f mjpeg -i http://192.168.1.203/stream -vframes 1 out.png This example will output one frame (-vframes 1) into a PNG file.
fps video filter
Output one image every second, named out1.png, out2.png, out3.png, etc.
ffmpeg -f mjpeg -i http://192.168.1.203/stream -vf fps=1 out%d.png
Output one image every minute, named img001.jpg, img002.jpg, img003.jpg, etc. The %03d dictates that the ordinal number of each output image will be formatted using 3 digits.
ffmpeg -f mjpeg -i http://192.168.1.203/stream -vf fps=1/60 img%03d.jpg
Output one image every ten minutes:
ffmpeg -f mjpeg -i http://192.168.1.203/stream -vf fps=1/600 thumb%04d.bmp
PS: Taken from https://trac.ffmpeg.org/wiki/Create%20a%20thumbnail%20image%20every%20X%20seconds%20of%20the%20video
Upvotes: 1
Reputation: 227
You can use ffmpeg to download a video stream from a continues video stream. Since you are using ubuntu, you can do it by simply running a command in your terminal and save the stream to your remote server. Following command is a sample ffmpeg command to save a live stream to your local disk.
ffmpeg.exe -y -i http://stream2.cnmns.net/hope-mp3 hopestream-latest.mp3
In above command -i indicates URL to be recorded. "hopestream-latest.mp3" is the output mp3 file. You can replace this with your remote server file path.
Upvotes: 3
Reputation: 146510
I don't have a sample server which does so. I made one myself and tried to test the solution.
const request = require('request');
const fs = require('fs')
var boundary = "";
var first = null;
var last_image = "";
let next_type = 3;
let content_length = -1;
let content_type = '';
request.get({
url: "http://localhost:9192/online.png",
forever: true,
headers: {
'referer': 'http://localhost:9192/'
},
// encoding: 'utf-8'
}
)
.on('error', (err) =>
console.log(err)
).on('response', (resp) => {
// console.log(resp)
boundary = resp.headers['content-type'].split('boundary=')[1]
// 0 - data
// 1 - content-type
// 2 - content-length
// 3 - boundary
// 4 - blank line
resp.on('data', (data)=> {
switch (next_type) {
case 0:
if (data.length + last_image.length == content_length)
{
last_image = data;
next_type = 3
} else {
last_image += data;
}
break;
case 1:
if (data.toString() == "\r\n")
{
next_type = 3
} else {
content_type = data.toString().toLowerCase().split("content-type:")[1].trim()
next_type = 2
}
break;
case 2:
content_length = parseInt(data.toString().toLowerCase().split("content-length:")[1].trim())
next_type =4
break;
case 3:
// we have got a boundary
next_type = 1;
if (last_image) {
fs.writeFileSync("image.png", last_image)
}
console.log(last_image)
last_image = ""
break;
case 4:
next_type = 0;
break;
}
})
})
This is node
, since you were open to non ROR solutions also. Below is the test server I had used
streamServer.js
/* Real-Time PNG-Streaming HTTP User Counter
Copyright Drew Gottlieb, 2012
Free for any use, but don't claim
that this is your work.
Doesn't work on Windows because
node-canvas only works on Linux and OSX. */
var moment = require('moment');
var http = require('http');
var _ = require('underscore');
var Backbone = require('backbone');
var Canvas = require('canvas');
var config = {
port: 9192,
host: "0.0.0.0",
updateInterval: 3000, // 5 seconds
multipartBoundary: "whyhellothere"
};
var Client = Backbone.Model.extend({
initialize: function() {
var req = this.get('req');
var res = this.get('res');
console.log("Page opened:", req.headers.referer);
res.on('close', _.bind(this.handleClose, this));
req.on('close', _.bind(this.handleClose, this));
this.sendInitialHeaders();
this.set('updateinterval', setInterval(_.bind(this.sendUpdate, this), config.updateInterval));
},
// Re-send the image in case it needs to be re-rendered
sendUpdate: function() {
if (this.get('sending')) return;
if (!this.get('imagecache')) return;
this.sendFrame(this.get('imagecache'));
},
// Sends the actual HTTP headers
sendInitialHeaders: function() {
this.set('sending', true);
var res = this.get('res');
res.writeHead(200, {
'Connection': 'Close',
'Expires': '-1',
'Last-Modified': moment().utc().format("ddd, DD MMM YYYY HH:mm:ss") + ' GMT',
'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0, false',
'Pragma': 'no-cache',
'Content-Type': 'multipart/x-mixed-replace; boundary=--' + config.multipartBoundary
});
res.write("--" + config.multipartBoundary + "\r\n");
this.set('sending', false);
},
// Sends an image frame, followed by an empty part to flush the image through
sendFrame: function(image) {
this.set('sending', true);
this.set('imagecache', image);
var res = this.get('res');
res.write("Content-Type: image/png\r\n");
res.write("Content-Length: " + image.length + "\r\n");
res.write("\r\n");
res.write(image);
res.write("--" + config.multipartBoundary + "\r\n");
res.write("\r\n");
res.write("--" + config.multipartBoundary + "\r\n");
this.set('sending', false);
},
// Handle a disconnect
handleClose: function() {
if (this.get('closed')) return;
this.set('closed', true);
console.log("Page closed:", this.get('req').headers.referer);
this.collection.remove(this);
clearInterval(this.get('updateinterval'));
}
});
var Clients = Backbone.Collection.extend({
model: Client,
initialize: function() {
this.on("add", this.countUpdated, this);
this.on("remove", this.countUpdated, this);
},
// Handle the client count changing
countUpdated: function() {
var image = this.generateUserCountImage(this.size());
this.each(function(client) {
client.sendFrame(image);
});
console.log("Connections:", this.size());
},
// Generate a new image
generateUserCountImage: function(count) {
var canvas = new Canvas(200, 30);
var ctx = canvas.getContext('2d');
// Background
ctx.fillStyle = "rgba(100, 149, 237, 0)";
ctx.fillRect(0, 0, 200, 30);
// Text
ctx.fillStyle = "rgb(0, 100, 0)";
ctx.font = "20px Impact";
ctx.fillText("Users online: " + count, 10, 20);
return canvas.toBuffer();
}
});
function handleRequest(req, res) {
switch (req.url) {
case '/':
case '/index.html':
showDemoPage(req, res);
break;
case '/online.png':
showImage(req, res);
break;
default:
show404(req, res);
break;
}
}
function showDemoPage(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write("<h1>Users viewing this page:</h1>");
res.write("<img src=\"/online.png\" />");
res.write("<h5>(probably won't work on IE or Opera)</h5>");
res.end();
}
function showImage(req, res) {
// If this image is not embedded in a <img> tag, don't show it.
if (!req.headers.referer) {
res.writeHead(403, {'Content-Type': 'text/html'});
res.end("You can't view this image directly.");
return;
}
// Create a new client to handle this connection
clients.add({
req: req,
res: res
});
}
function show404(req, res) {
res.writeHead(404, {'Content-Type': 'text/html'});
res.end("<h1>not found</h1><br /><a href=\"/\">go home</a>");
}
// Ready, Set, Go!
var clients = new Clients();
http.createServer(handleRequest).listen(config.port, config.host);
console.log("Started.");
PS: Taken from https://gist.github.com/dag10/48e6d25415ca92318815
Upvotes: 2