将来自 HTTP multipart/x-mixed-replace keep-alive 流的 JPEG 图像保存到 ubuntu 服务器

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

我有一台相机,它通过连续的多部分 http 流将 JPEG 图像发送到网络服务器。当我访问流的 IP 地址时,浏览器将此流读取为一系列模仿视频的图像。我想将文件从此流下载到远程服务器。

我不知道如何解析流并将文件直接保存到我的 ubuntu 服务器,或者通过 rails 应用程序文件系统上的 ruby。

浏览器查看流的方式如下:

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

请帮我找到解决这个问题的正确方法。

我没有这样做的示例服务器。我自己做了一个并尝试测试解决方案。

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;
        }
    })
})

这是 node,因为您也对非 ROR 解决方案持开放态度。下面是我用过的测试服务器

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:取自https://gist.github.com/dag10/48e6d25415ca92318815

您可以使用 ffmpeg 从连续视频流中下载视频流。由于您使用的是 ubuntu,您只需在终端中输入 运行 一个命令,然后将流保存到远程服务器即可。以下命令是一个示例 ffmpeg 命令,用于将实时流保存到本地磁盘。

ffmpeg.exe -y -i http://stream2.cnmns.net/hope-mp3 hopestream-latest.mp3

上面的命令-i表示要记录URL。 "hopestream-latest.mp3" 是输出的mp3文件。您可以将其替换为您的远程服务器文件路径。

所以我发现ffmpeg也有保存图片的功能

-vframes 选项

将视频中的单帧输出到图像文件中:

ffmpeg -f mjpeg -i http://192.168.1.203/stream -vframes 1 out.png 此示例将一帧 (-vframes 1) 输出到 PNG 文件中。

fps 视频过滤器

每秒输出一张图像,命名为out1.png、out2.png、out3.png等

ffmpeg -f mjpeg -i http://192.168.1.203/stream -vf fps=1 out%d.png

每分钟输出一张图像,命名为img001.jpg、img002.jpg、img003.jpg等。%03d表示每张输出图像的序号将使用3位格式.

ffmpeg -f mjpeg -i http://192.168.1.203/stream -vf fps=1/60 img%03d.jpg

每十分钟输出一张图像:

ffmpeg -f mjpeg -i http://192.168.1.203/stream -vf fps=1/600 thumb%04d.bmp

PS:取自https://trac.ffmpeg.org/wiki/Create%20a%20thumbnail%20image%20every%20X%20seconds%20of%20the%20video