Skip to content

Instantly share code, notes, and snippets.

@n3wtron
Last active December 22, 2023 13:32
Show Gist options
  • Save n3wtron/4624820 to your computer and use it in GitHub Desktop.
Save n3wtron/4624820 to your computer and use it in GitHub Desktop.
Simple Python Motion Jpeg (mjpeg server) from webcam. Using: OpenCV,BaseHTTPServer
#!/usr/bin/python
'''
Author: Igor Maculan - n3wtron@gmail.com
A Simple mjpg stream http server
'''
import cv2
import Image
import threading
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
from SocketServer import ThreadingMixIn
import StringIO
import time
capture=None
class CamHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path.endswith('.mjpg'):
self.send_response(200)
self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary')
self.end_headers()
while True:
try:
rc,img = capture.read()
if not rc:
continue
imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
jpg = Image.fromarray(imgRGB)
tmpFile = StringIO.StringIO()
jpg.save(tmpFile,'JPEG')
self.wfile.write("--jpgboundary")
self.send_header('Content-type','image/jpeg')
self.send_header('Content-length',str(tmpFile.len))
self.end_headers()
jpg.save(self.wfile,'JPEG')
time.sleep(0.05)
except KeyboardInterrupt:
break
return
if self.path.endswith('.html'):
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
self.wfile.write('<html><head></head><body>')
self.wfile.write('<img src="http://127.0.0.1:8080/cam.mjpg"/>')
self.wfile.write('</body></html>')
return
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
def main():
global capture
capture = cv2.VideoCapture(0)
capture.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, 320);
capture.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, 240);
capture.set(cv2.cv.CV_CAP_PROP_SATURATION,0.2);
global img
try:
server = ThreadedHTTPServer(('localhost', 8080), CamHandler)
print "server started"
server.serve_forever()
except KeyboardInterrupt:
capture.release()
server.socket.close()
if __name__ == '__main__':
main()
@Kiolali
Copy link

Kiolali commented Jun 8, 2016

i fixed the error above by uninstalling pil and pillow (seem to cause conflicts if both are installed). Then I reinstalled only pillow.

Now I get a popup window "python.exe has stopped working.." when accessing the mjpg via browser

caused by this line:
jpg.save(self.wfile,'JPEG')

Any ideas?

Best, Hanna

@Vinggui
Copy link

Vinggui commented Aug 19, 2016

Nice, worked like a charm.... Thank you!
The only trouble I had was related to python OpenCV, which I needed to install, beside the OpenCV library, the python-opencv (sudo apt-get install python-opencv)
But working great now...

@plumgeek
Copy link

plumgeek commented Sep 6, 2016

Hello Igor, this looks very promising . I have a couple questions maybe someone could help with. Please forgive me I'm a bit of a noob at this.

I'm presently using SimpleCV, which includes an mjpeg streaming server that seems to work in a similar way to this code, however with that server I often have to re-load the page a few times to get the image feed to appear, and sometimes it disappears after it's been streaming for a while. I thought maybe it would be more reliable if I were to somehow get the stream to write to a location and then use Apache to actually serve it up. There is a post here about streaming OpenCV images via Apache. I discovered your code above when starting to work through that tutorial.

I don't understand how to write the code to get the components to talk to eachother. Would you mind giving a bit more detail and example of how to actually use the above code? I assume I would kick it off in a terminal and let it run, at which time I could access the stream via a browser, but I'm not sure how to actually save images from OpenCV/SimpleCV to the server.

And a followup, is there a reason you're not just using Apache? I'm sure there was a good reason, it just seems it's already installed with most Linux distros so I'm not sure why one would write another server.

Thanks so much!

@n3wtron
Copy link
Author

n3wtron commented Sep 7, 2016

Hi @plumgeek,
I've written this code for my raspberryPi robot (performance and lightweight).
Using apache it would be foolish.
If you want to save every frame with my script you have to extract the infinite loop at line 21 and save the frames into a folder instead of using tmpFile = StringIO.StringIO()

I hope that I have been useful to you

@andresR8
Copy link

I have the same issue that @Kiolali

@n3wtron
Copy link
Author

n3wtron commented Nov 9, 2016

Hi @andresR8,
I'm sorry, but I don't have any Windows to try, the only thing that I can say is try to check windows firewall on python.exe, maybe (really maybe) the problem is access to the network port.
I'm saying that by the description of wfile : "Contains the output stream for writing a response back to the client. Proper adherence to the HTTP protocol must be used when writing to this stream."

@nagarashish
Copy link

I am having issue when multiple clients connect to the server. When first client connects, streaming works perfectly. As soon as I second client connects, get following dump many times and server crashes:

[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 127 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 8 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 98 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 127 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 127 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 12 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 50 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 3 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 41 bytes
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 32 bytes
[mjpeg @ 0x560aa30faae0] error count: 64
[mjpeg @ 0x560aa30faae0] error y=19 x=8
[mjpeg @ 0x560aa30faae0] overread 8
[mjpeg @ 0x560aa30faae0] No JPEG data found in image

Any idea how to fix multi client access issue?

Thanks

@pauls06
Copy link

pauls06 commented Dec 30, 2016

I had the same issue as @Kiolali and @andresR8.

Replacing the problem line
jpg.save(self.wfile,'JPEG')
with
self.wfile.write( tmpFile.getvalue() )
avoids the problem.

@Kiolali
Copy link

Kiolali commented Jan 6, 2017

I have the same issue as @wpoet ("broken pipe")
Any ideas how to solve it?

accessing the mjpeg stream via browser works - but if I use a test client like "insomnia" (GET url_mjpeg_stream) I don't get any response..

@limtingfei
Copy link

Thank you for the code. I got an output "server started" . Is it considered successful ?
Because I was expecting to stream it in opencv just like how I used to stream the webcam directly into my pc.

@awood85
Copy link

awood85 commented Feb 13, 2017

Hi, thanks very much for this code, exactly what I was after.

I had it working locally on the rpi but couldn't access it from either my phone or PC when trying to access it across the network. The Python output didn't show the 200 response message.

I adjusted lines 44 and 60 to point to the local IP of the rpi (which I've got acting as an access point if that makes any difference) and could get the 200 response while trying to access from both phone and PC although I never saw any output. This also stopped me viewing the stream locally from the pi. Reverted back and still no images locally or over the network.

I'll delete the code and start again and see if that fixes it but any ideas in the mean time why it might not be working. Ultimately I want to view the processed output after some openCV manipulation on my phone with the rpi being headless.

Thanks

Adam

@amvlr
Copy link

amvlr commented Mar 9, 2017

Traceback (most recent call last):
File "/home/pi/simple_mjpeg_streamer_http_server", line 6, in
import cv2
ImportError: No module named 'cv2'

Please help me out!

@RichLewis007
Copy link

RichLewis007 commented Mar 16, 2017

I got it working on macOS by doing the following:
note: it's written for Python 2.

  • install OpenCV 3.x for python
  • install pillow for Python2:
    pip2 install pillow
  • removed the unneeded lines that are causing errors:
	capture.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, 320); 
	capture.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, 240);
	capture.set(cv2.cv.CV_CAP_PROP_SATURATION,0.2);
  • run it:
    python2 motion-jpeg-server.py

  • see it:
    in browser, go to:
    http://127.0.0.1:8080/cam.mjpg
    to see video.

@adham-elsabbagh
Copy link

how can i save the video streaming on my hard ?

Copy link

ghost commented Jun 6, 2017

Thank you for the code. I got an output "server started", but i see nothing in browser from 127.0.0.1:8080/cam.mjpg.
What it can be?

@Morfonio
Copy link

Hello!

I have the same problem as @wpoet and @Kiolali with the broken pipe.

How did you fix it?

127.0.0.1 - - [14/Aug/2017 11:29:15] "GET /cam.mjpg HTTP/1.1" 200 -

Exception happened during processing of request from ('127.0.0.1', 43004)
Traceback (most recent call last):
File "/usr/lib/python2.7/SocketServer.py", line 593, in process_request_thread
self.finish_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 334, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python2.7/SocketServer.py", line 651, in init
self.finish()
File "/usr/lib/python2.7/SocketServer.py", line 710, in finish
self.wfile.close()
File "/usr/lib/python2.7/socket.py", line 279, in close
self.flush()
File "/usr/lib/python2.7/socket.py", line 303, in flush
self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe

@charmed12
Copy link

@Morfonio , i have the same pb when I tried to connect via Firefox. But the error disappears and the code work when I used chrome

@ashish1405
Copy link

modified for Python 3

#!/usr/bin/python
'''
	Author: Igor Maculan - n3wtron@gmail.com
	A Simple mjpg stream http server
'''
import cv2
from PIL import Image
import threading
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
from io import StringIO,BytesIO
import time
capture=None

class CamHandler(BaseHTTPRequestHandler):
	def do_GET(self):
		if self.path.endswith('.mjpg'):
			self.send_response(200)
			self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary')
			self.end_headers()
			while True:
				try:
					rc,img = capture.read()
					if not rc:
						continue
					imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
					jpg = Image.fromarray(imgRGB)
					tmpFile = BytesIO()
					jpg.save(tmpFile,'JPEG')
					self.wfile.write("--jpgboundary".encode())
					self.send_header('Content-type','image/jpeg')
					self.send_header('Content-length',str(tmpFile.getbuffer().nbytes))
					self.end_headers()
					jpg.save(self.wfile,'JPEG')
					time.sleep(0.05)
				except KeyboardInterrupt:
					break
			return
		if self.path.endswith('.html'):
			self.send_response(200)
			self.send_header('Content-type','text/html')
			self.end_headers()
			self.wfile.write('<html><head></head><body>'.encode())
			self.wfile.write('<img src="http://127.0.0.1:8087/cam.mjpg"/>'.encode())
			self.wfile.write('</body></html>'.encode())
			return


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
	"""Handle requests in a separate thread."""

def main():
	global capture
	capture = cv2.VideoCapture(0)
	capture.set(cv2.CAP_PROP_FRAME_WIDTH, 320); 
	capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 240);
	capture.set(cv2.CAP_PROP_SATURATION,0.2);
	global img
	try:
		server = ThreadedHTTPServer(('localhost', 8087), CamHandler)
		print( "server started")
		server.serve_forever()
	except KeyboardInterrupt:
		capture.release()
		server.socket.close()

if __name__ == '__main__':
	main()

@amakukha
Copy link

amakukha commented Dec 26, 2017

I does work in Chrome, but it doesn't in Firefox for some reason. I get "[Errno 32] Broken pipe".

What worked for me in Mozilla Firefox was pymjpeg project. I merged the two solutions for my purpose.

@marcokuchla
Copy link

marcokuchla commented Feb 28, 2018

I modified a little bit the code to get the video_capture_path by command line (it works with file/webcam/rtsp as input). Also, since it's multithreaded, I create a lock to avoid race condition on camera's read() method. I've dropped the dependency to PIL since OpenCV can encode to JPEG and the dependency to StringIO, BytesIO, due to use numpy "tobytes()" method. One limitation with the following code is that multiple threads cannot read the same frame, so using a video file as input, more viewers implies in "fast" video because between consecutive reads in one thread, some frames may have already been skipped (read) by other threads.

#!/usr/bin/python3
"""
   Author: Igor Maculan - n3wtron@gmail.com
   A Simple mjpg stream http server
"""
import cv2
import threading
import http
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
import time
import sys


class CamHandler(BaseHTTPRequestHandler):
    
    def __init__(self, request, client_address, server):
        img_src = 'http://{}:{}/cam.mjpg'.format(server.server_address[0], server.server_address[1])
        self.html_page = """
            <html>
                <head></head>
                <body>
                    <img src="{}"/>
                </body>
            </html>""".format(img_src)
        self.html_404_page = """
            <html>
                <head></head>
                <body>
                    <h1>NOT FOUND</h1>
                </body>
            </html>"""
        BaseHTTPRequestHandler.__init__(self, request, client_address, server)

    def do_GET(self):
        if self.path.endswith('.mjpg'):
            self.send_response(http.HTTPStatus.OK)
            self.send_header('Content-type', 'multipart/x-mixed-replace; boundary=--jpgboundary')
            self.end_headers()
            while True:
                try:
                    img = self.server.read_frame()
                    retval, jpg = cv2.imencode('.jpg', img)
                    if not retval:
                        raise RuntimeError('Could not encode img to JPEG')
                    jpg_bytes = jpg.tobytes()
                    self.wfile.write("--jpgboundary\r\n".encode())
                    self.send_header('Content-type', 'image/jpeg')
                    self.send_header('Content-length', len(jpg_bytes))
                    self.end_headers()
                    self.wfile.write(jpg_bytes)
                    time.sleep(self.server.read_delay)
                except (IOError, ConnectionError):
                    break
        elif self.path.endswith('.html'):
            self.send_response(http.HTTPStatus.OK)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(self.html_page.encode())
        else:
            self.send_response(http.HTTPStatus.NOT_FOUND)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(self.html_404_page.encode())


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""
    def __init__(self, capture_path, server_address, RequestHandlerClass, bind_and_activate=True):
        HTTPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate)
        ThreadingMixIn.__init__(self)
        try:
            # verifies whether is a webcam
            capture_path = int(capture_path)
        except TypeError:
            pass
        self._capture_path = capture_path
        fps = 30
        self.read_delay = 1. / fps
        self._lock = threading.Lock()
        self._camera = cv2.VideoCapture()

    def open_video(self):
        if not self._camera.open(self._capture_path):
            raise IOError('Could not open Camera {}'.format(self._capture_path))

    def read_frame(self):
        with self._lock:
            retval, img = self._camera.read()
            if not retval:
                self.open_video()
        return img

    def serve_forever(self, poll_interval=0.5):
        self.open_video()
        try:
            super().serve_forever(poll_interval)
        except KeyboardInterrupt:
            self._camera.release()


def main():
    server = ThreadedHTTPServer(sys.argv[1], ('127.0.0.1', 8080), CamHandler)
    print("server started")
    server.serve_forever()


if __name__ == '__main__':
    main()

@menymp
Copy link

menymp commented Mar 5, 2018

Nice work, im using it for a iot project it works very well

@s7711
Copy link

s7711 commented Jun 20, 2018

Awesome code, really appreciate the project. To make this work in Edge change line 30 to:
self.wfile.write("--jpgboundary\r\n")
The CR,LF makes it work (for me in Edge version 42.17134.1.0). Chrome works either way. Also, for those who want to access from a different computer change line 44 to:
self.wfile.write('<img src="cam.mjpg"/>')
and change line 60 to:
server = ThreadedHTTPServer(('', 8080), CamHandler)
Then look up your IP address, in your browser enter address:
http://IP_ADDRESS:8080/cam.html
Many thanks n3wtron!

@n3wtron
Copy link
Author

n3wtron commented Aug 8, 2018

Hi at all!
First of all I apologize for not responding to you, but the gist have no notifications.
To prevent this I've just created this repo https://github.com/n3wtron/simple_mjpeg_streamer_http_server , so please open issues, fork the project and open merge requests, so that we can improve the script together.

I'll create a branch for python3 version.

Thank you!

@tom131313
Copy link

I wanted a stripped down example for teaching Python and openCV to kids. The above program was a great start and I got help from several other sources on the web. I did strip down the program some more and it's now unsuitable as a fork. I may be able to pass only my corrections along in your github but for now this is what I have that runs on a Raspberry Pi, Python 3, openCV 3.

Note that another CRLF is added to the boundary and note the boundary at the end of a MIME part has to have two dashes in front of the boundary specified in the Content-Type so I removed the extraneous dashes in Content-Type. Now the message is much closer to the standard and should work on most or all browsers.

For the drawing example, I included an openCV drawing from another web posting.

Camera commands are for an old Logitech camera and I figured them out (plus several other properties not in this example) using program v4l2-ctl to dump the camera properties and openCV get property help figure out what openCV was doing to them (mostly normalizing what the camera needs to values between 0 and 1).

# Stream an edited video camera image to HTTP JPEG format (MJPEG)

# Capture a USB camera image using openCV
# Change the image using openCV
# Serve the image as HTTP in MIME multipart JPG format and each image replaces the previous

import cv2
import threading
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
import time
capture=None

class CamHandler(BaseHTTPRequestHandler):
	def do_GET(self):
		self.send_response(200)
		self.send_header('Content-type','multipart/x-mixed-replace; boundary=jpgboundary')
		self.end_headers()
		while True:
				try:
						# capture image from camera
						rc,img = capture.read()
						if not rc:
								continue
								
						# play with image to be displayed - example "scribbling" with line, rectangle with a circle in it, half an ellipse, and top part of some text
						# note that in Python many but not all the draw functins will also return the image
						# line(img...) is basic - img is changed; can also use img = line(img...), and another copy can be made with img2 = line(img...) (I think)
						cv2.line(img,(0,0),(511,511),(255,0,0),5)
						cv2.rectangle(img,(384,0),(510,128),(0,255,0),3)
						cv2.circle(img,(447,63), 63, (0,0,255), -1)
						cv2.ellipse(img,(256,256),(100,50),0,0,180,255,-1)
						cv2.putText(img,'OpenCV',(10,500), cv2.FONT_HERSHEY_SIMPLEX, 4,(255,255,255),2,cv2.LINE_AA)
						# done playing with image
						
						img_str = cv2.imencode('.jpg', img)[1].tostring() # change image to jpeg format
						self.send_header('Content-type','image/jpeg')
						self.end_headers()
						self.wfile.write(img_str)
						self.wfile.write(b"\r\n--jpgboundary\r\n") # end of this part
				except KeyboardInterrupt:
						# end of the message - not sure how we ever get here, though
						print("KeyboardInterrpt in server loop - breaking the loop (server now hung?)")
						self.wfile.write(b"\r\n--jpgboundary--\r\n")
						break
		return
            
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
	"""Handle requests in a separate thread."""

def main():
	global capture
	capture = cv2.VideoCapture(0) # connect openCV to camera 0
	
	# set desired camera properties
	capture.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) # 0.25 is turn OFF auto exposure (Logitech); 0.75 is ON
	time.sleep(.5) # wait for auto exposure change to be set
	capture.set(cv2.CAP_PROP_EXPOSURE, .01) # fairly dark - low exposure

# a few other properties that can be set - not a complete list
#	capture.set(cv2.CAP_PROP_BRIGHTNESS, .4); #1 is bright 0 or-1 is dark .4 is fairly dark default Brightness  0.5019607843137255
#	capture.set(cv2.CAP_PROP_CONTRAST, 1); 
#	capture.set(cv2.CAP_PROP_FRAME_WIDTH, 320); 
#	capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 240);
#	capture.set(cv2.CAP_PROP_SATURATION,0.2);

	try:
		server = ThreadedHTTPServer(('localhost', 1181), CamHandler)
		print("server starting")
		server.serve_forever()
		
	except KeyboardInterrupt:
		# ctrl-c comes here but need another to end all.  Probably should have terminated thread here, too.
		print("KeyboardInterrpt in server - ending server")
		capture.release()
		server.socket.close()

if __name__ == '__main__':
	main()

@DickingAround
Copy link

Great stuff. BTW, if you're viewing this on a pi (i.e. localhost) at the moment this appears not to work on raspberri pi chromium (broken pipe after a few renders). But it works on luakit browser

@Ajinkz
Copy link

Ajinkz commented Apr 22, 2019

Not able to stream video and facing this error. I have made some changes in above code but still not working with Python 3.6.x
Please help me know whats going on wrong

(face) F:\FR\IGL\flask server>python simple_mjpeg_streamer_http_server.py
server started
127.0.0.1 - - [23/Apr/2019 00:20:34] "GET /cam.mjpg HTTP/1.1" 200 -
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 60377)
Traceback (most recent call last):
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\site-packages\PIL\ImageFile.py", line 485, in _save
    fh = fp.fileno()
io.UnsupportedOperation: fileno

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\socketserver.py", line 651, in process_request_thread
    self.finish_request(request, client_address)
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\socketserver.py", line 361, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\socketserver.py", line 721, in __init__
    self.handle()
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\http\server.py", line 418, in handle
    self.handle_one_request()
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\http\server.py", line 406, in handle_one_request
    method()
  File "simple_mjpeg_streamer_http_server.py", line 24, in do_GET
    jpg.save(tmpFile,'JPEG')
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\site-packages\PIL\Image.py", line 1969, in save
    save_handler(self, fp, filename)
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\site-packages\PIL\JpegImagePlugin.py", line 761, in _save
    ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
  File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\site-packages\PIL\ImageFile.py", line 500, in _save
    fp.write(d)
TypeError: string argument expected, got 'bytes'

This is the code

import cv2
from PIL import Image
import threading
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
from io import StringIO
import time
capture=None

class CamHandler(BaseHTTPRequestHandler):
	def do_GET(self):
		if self.path.endswith('.mjpg'):
			self.send_response(200)
			self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary')
			self.end_headers()
			while True:
				try:
					rc,img = capture.read()
					if not rc:
						continue
					imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
					jpg = Image.fromarray(imgRGB)
					tmpFile = StringIO() #out_s = StringIO()
					jpg.save(tmpFile,'JPEG')
					self.wfile.write("--jpgboundary")
					self.send_header('Content-type','image/jpeg')
					self.send_header('Content-length',str(tmpFile.len))
					self.end_headers()
					jpg.save(self.wfile,'JPEG')
					time.sleep(0.05)
				except KeyboardInterrupt:
					break
			return
		if self.path.endswith('.html'):
			self.send_response(200)
			self.send_header('Content-type','text/html')
			self.end_headers()
			self.wfile.write('<html><head></head><body>')
			self.wfile.write('<img src="http://127.0.0.1:8081/cam.mjpg"/>')
			self.wfile.write('</body></html>')
			return


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
	"""Handle requests in a separate thread."""

def main():
	global capture
	capture = cv2.VideoCapture(0)
	capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640); 
	capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480);
	capture.set(cv2.CAP_PROP_SATURATION,0.2);
	global img
	try:
		server = ThreadedHTTPServer(('localhost', 8081), CamHandler)
		print("server started")
		server.serve_forever()
	except KeyboardInterrupt:
		capture.release()
		server.socket.close()

if __name__ == '__main__':
	main()

@muka
Copy link

muka commented Oct 22, 2019

This one works

import cv2
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn

capture = None


class CamHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        if self.path.endswith('.mjpg'):
            self.send_response(200)
            self.send_header(
                'Content-type',
                'multipart/x-mixed-replace; boundary=--jpgboundary'
            )
            self.end_headers()
            while True:
                try:

                    rc, img = capture.read()
                    if not rc:
                        continue

                    img_str = cv2.imencode('.jpg', img)[1].tostring()

                    self.send_header('Content-type', 'image/jpeg')
                    self.send_header('Content-length', len(img_str))
                    self.end_headers()

                    self.wfile.write(img_str)
                    self.wfile.write(b"\r\n--jpgboundary\r\n")

                except KeyboardInterrupt:
                    self.wfile.write(b"\r\n--jpgboundary--\r\n")
                    break
                except BrokenPipeError:
                    continue
            return

        if self.path.endswith('.html'):
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(b'<html><head></head><body>')
            self.wfile.write(b'<img src="http://127.0.0.1:8081/cam.mjpg"/>')
            self.wfile.write(b'</body></html>')
            return


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""


def main():

    global capture
    capture = cv2.VideoCapture(0)
    capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
    capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

    global img
    try:
        server = ThreadedHTTPServer(('localhost', 8081), CamHandler)
        print("server started at http://127.0.0.1:8081/cam.html")
        server.serve_forever()
    except KeyboardInterrupt:
        capture.release()
        server.socket.close()

if __name__ == '__main__':
    main()

@foilandwater
Copy link

Nice man!! exactly I was looking for on web. Saved my time 👍 )
I am doing this for rtsp stream.
And this way RTSP gets embedded in a web page.
Thanks

Hi,

Please can you tell me how you did it with an RTSP IP camera onto a web page? I've been researching for weeks to get this done!!

@wennycooper
Copy link

Great work, Igor!

Wolfgang, the program uses pipes (http://www.python-course.eu/pipes.php) to communicate the output to the server. When you disconnect from the port, it breaks the existing pipe, and when you reconnect, it breaks the previous pipe and starts a new one.

If you were running the script locally and the opencv output was displayed in a window, you could use the 'Keyboard Interrupt' to break the loop without any errors. That doesn't work in the browser because you've already used a pipe to get there.

tl;dr : that error is nothing to worry about, you'll pretty much see it every time you disconnect or reconnect.

Hi saif-data, Do you have any idea how to remove those broken pipe error messages? Try/Exception not work..

@ashwin2401
Copy link

import cv2
import numpy as np
import time
from PIL import Image
from io import BytesIO
import threading
from multiprocessing import Process
import config_ip_vid
try:
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
except ImportError:
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
from SocketServer import ThreadingMixIn

debug = False
gracefulexit=False
TFrame = None
videoserver = None

port = config_ip_vid.port_number

vid = config_ip_vid.vid_number

for i,j in zip(port,vid):

print("all file and ports******",i,j)

#print('port number -------',port)
#print('video number -------',vid)

#importing all the ports and video from config file
port_8081 = config_ip_vid.port_number1
vid1 = str(config_ip_vid.vid_number1)
print("@@@@@$$$$$$$",str(config_ip_vid.vid_number1))
port_8082 = config_ip_vid.port_number2
vid2 = config_ip_vid.vid_number2

port_8083 = config_ip_vid.port_number3
vid3 = config_ip_vid.vid_number3

port_8084 = config_ip_vid.port_number4
vid4 = config_ip_vid.vid_number4

port_8085 = config_ip_vid.port_number5
vid5 = config_ip_vid.vid_number5

class CamHandler(BaseHTTPRequestHandler):

def do_GET(self):
    global gracefulexit
    if self.path.endswith('.mjpg'):
        self.send_response(200)
        self.send_header('Content-Type','multipart/x-mixed-replace; boundary=--jpgboundary')
        self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0')
        self.end_headers()
        dummy=np.zeros((100,100,3), np.uint8)
        if debug:
            print("vidserver: got new connection")
        while True:
            if gracefulexit:
                break
            try:
                img=TFrame
                if img is None:
                    if debug:
                        print("vidserver: sending dummy")
                    img = dummy

                imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
                jpg = Image.fromarray(imgRGB)
                tmpFile = BytesIO()
                jpg.save(tmpFile,'JPEG')
                self.wfile.write("--jpgboundary".encode())
                self.send_header('Content-type','image/jpeg')
                self.send_header('Content-length',str(len(tmpFile.getvalue())))
                self.end_headers()
                jpg.save(self.wfile,'JPEG')
                time.sleep(0.05)
            except KeyboardInterrupt:
                print("vidserver: got interrupt")
                break
            except Exception as e:
                print("vidserver error:",e)
                break
        print("Exiting cam thread")
        return
    elif self.path.endswith('.html'):
        self.send_response(200)
        self.send_header('Content-type','text/html')
        self.end_headers()
        self.wfile.write('<html><head></head><body>'.encode())
        self.wfile.write('<img src="http://127.0.0.1:8087/cam.mjpg"/>'.encode())
        self.wfile.write('</body></html>'.encode())
        return

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads=True
pass
"""Handle requests in a separate thread."""

def startVideoServer(video_transmit_ip,video_transmit_port):
print("starting video server on %s" % video_transmit_ip)
global gracefulexit

    try:
        videoserver = ThreadedHTTPServer((video_transmit_ip, video_transmit_port), CamHandler)
        # videoserver.anprObj=self
        videoserver.serve_forever()
    except KeyboardInterrupt:
        pass
    print("Exiting videoserver thread")
    gracefulexit=True
    videoserver.shutdown()

    # cap = cv2.VideoCapture(vid)
    # print("############$$$$$$$$$$$$$$$$$ Cap in process",cap)
    # if (cap.isOpened() == False):
    #     print("Error opening video stream or file")
    #
    # while (cap.isOpened()):
    #     ret, TFrame = cap.read()
    #     print("Tframe---=======",TFrame)
    #     if ret != True: break
    # cap.release()
    # if videoserver is not None:
    #     print("Shutting down video server")
    #     videoserver.shutdown()
    #     videoserver.server_close()
    #     videoserver.socket.close()
    # cv2.destroyAllWindows()

print("port 1%%%%%%",port[0])

print("port 2%%%%%%",port[1])

print("video 1%%%%%%",vid[0])

print("video 2%%%%%%",vid[1])

#for i,j in zip(port,vid):

video_transmit_ip = "localhost"
video_transmit_port = int(port_8081)#int(i)
video_transmit_port2 = int(port_8082)
video_transmit_port3 = int(port_8083)
video_transmit_port4 = int(port_8084)
video_transmit_port5 = int(port_8085)
#print("==========",i)
#print("==========",j)
vthread=threading.Thread(target=startVideoServer,args=(video_transmit_ip,video_transmit_port))
vthread.start()

vthread2=threading.Thread(target=startVideoServer,args=(video_transmit_ip,video_transmit_port2))
vthread2.start()

cap = cv2.VideoCapture(vid1)
cap2 = cv2.VideoCapture(vid2)
#print("############$$$$$$$$$$$$$$$$$ Cap in process",cap)
if (cap.isOpened() == False):
print("Error opening video stream or file")
if (cap2.isOpened() == False):
print("Error opening video stream or file")

while (cap.isOpened()|cap2.isOpened()):
ret, TFrame = cap.read()
ret1, img1 = cap2.read()
#print("Tframe---=======",TFrame)
if ret != True: break
cap.release()
cap2.release()
if videoserver is not None:
print("Shutting down video server")
videoserver.shutdown()
videoserver.server_close()
videoserver.socket.close()
cv2.destroyAllWindows()

**I want add multiple video input source to the web but it only displays one of the starting can any of you guys can help me through it i want to play multiple videos the above code is attached for the reference
**

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment