Create a mjpeg stream from jpeg images in python

Damien picture Damien · Jan 18, 2014 · Viewed 18.1k times · Source

I need to serve real-time graphs and I would like to deliver a mjpeg stream over http (so that it is easy to include the graphs in a web-page by using a plain tag).

Is it possible to create an mjpeg stream from multiple jpeg images, in realtime ?

My strategy is:

  1. Output the correct http headers:

    Cache-Control:no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0
    Connection:close
    Content-Type:multipart/x-mixed-replace;boundary=boundarydonotcross
    Expires:Mon, 3 Jan 2000 12:34:56 GMT
    Pragma:no-cache
    Server:MJPG-Streamer/0.2
    

    (got it from a curl -I {on a mjpeg-streamer instance}, but this seems strange)

  2. Simply yield the successive jpeg images binaries, taking care to:

    • prepend the correct headers at the beginning of the stream (as mjpeg-streamer does):

      Content-Type: image/jpeg
      Content-Length: 5427
      X-Timestamp: 3927662.086099
      
    • append the boundary string at the end of each jpeg streams.

      --boudary--
      

Questions:

Have you done that,

do you know a python module that does that,

do you think it would work,

have you got any advice ?

Answer

Damien picture Damien · Jan 18, 2014

I got it working as a proof-of-concept: https://github.com/damiencorpataux/pymjpeg

For memory:

import os, time
from glob import glob
import sys
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

boundary = '--boundarydonotcross'

def request_headers():
    return {
        'Cache-Control': 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
        'Connection': 'close',
        'Content-Type': 'multipart/x-mixed-replace;boundary=%s' % boundary,
        'Expires': 'Mon, 3 Jan 2000 12:34:56 GMT',
        'Pragma': 'no-cache',
    }

def image_headers(filename):
    return {
        'X-Timestamp': time.time(),
        'Content-Length': os.path.getsize(filename),
        #FIXME: mime-type must be set according file content
        'Content-Type': 'image/jpeg',
    }

# FIXME: should take a binary stream
def image(filename):
    with open(filename, "rb") as f:
        # for byte in f.read(1) while/if byte ?
        byte = f.read(1)
        while byte:
            yield byte
            # Next byte
            byte = f.read(1)

# Basic HTTP server
class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        # Response headers (multipart)
        for k, v in pymjpeg.request_headers().items():
            self.send_header(k, v) 
        # Multipart content
        for filename in glob('img/*'):
            # Part boundary string
            self.end_headers()
            self.wfile.write(pymjpeg.boundary)
            self.end_headers()
            # Part headers
            for k, v in pymjpeg.image_headers(filename).items():
                self.send_header(k, v) 
            self.end_headers()
            # Part binary
            for chunk in pymjpeg.image(filename):
                self.wfile.write(chunk)
    def log_message(self, format, *args):
        return

httpd = HTTPServer(('', 8001), MyHandler)
httpd.serve_forever()