Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save JPEG images from HTTP multipart/x-mixed-replace keep-alive stream to ubuntu server

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.

like image 488
Travis Glover Avatar asked Apr 04 '18 16:04

Travis Glover


2 Answers

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.

like image 175
Wijaya Avatar answered Oct 03 '22 11:10

Wijaya


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

like image 41
Tarun Lalwani Avatar answered Oct 03 '22 11:10

Tarun Lalwani