#!/usr/bin/env python3 import http.server import socketserver import subprocess import threading import time BOUNDARY = b'--frame' LATEST_JPEG = None LATEST_LOCK = threading.Lock() WIDTH = 1280 HEIGHT = 720 FPS = 10 PORT = 8081 def camera_reader(): global LATEST_JPEG cmd = [ 'rpicam-vid', '--timeout', '0', '--nopreview', '--width', str(WIDTH), '--height', str(HEIGHT), '--framerate', str(FPS), '--codec', 'mjpeg', '-o', '-', ] while True: proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=0) buffer = bytearray() try: while True: chunk = proc.stdout.read(4096) if not chunk: break buffer.extend(chunk) while True: start = buffer.find(b'\xff\xd8') if start == -1: if len(buffer) > 1024 * 1024: del buffer[:-2] break end = buffer.find(b'\xff\xd9', start + 2) if end == -1: if start > 0: del buffer[:start] break jpeg = bytes(buffer[start:end + 2]) del buffer[:end + 2] with LATEST_LOCK: LATEST_JPEG = jpeg finally: proc.kill() proc.wait() time.sleep(1) class ReusableThreadingTCPServer(socketserver.ThreadingTCPServer): allow_reuse_address = True class Handler(http.server.BaseHTTPRequestHandler): def do_HEAD(self): if self.path in ('/', '/index.html'): self.send_response(200) self.send_header('Content-Type', 'text/html; charset=utf-8') self.end_headers() return if self.path == '/stream.mjpg': self.send_response(200) self.send_header('Age', '0') self.send_header('Cache-Control', 'no-cache, private') self.send_header('Pragma', 'no-cache') self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=frame') self.end_headers() return self.send_error(404) def do_GET(self): if self.path in ('/', '/index.html'): body = b'' self.send_response(200) self.send_header('Content-Type', 'text/html; charset=utf-8') self.send_header('Content-Length', str(len(body))) self.end_headers() self.wfile.write(body) return if self.path != '/stream.mjpg': self.send_error(404) return self.send_response(200) self.send_header('Age', '0') self.send_header('Cache-Control', 'no-cache, private') self.send_header('Pragma', 'no-cache') self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=frame') self.end_headers() while True: with LATEST_LOCK: frame = LATEST_JPEG if frame is None: time.sleep(0.1) continue try: self.wfile.write(BOUNDARY + b'\r\n') self.wfile.write(b'Content-Type: image/jpeg\r\n') self.wfile.write(f'Content-Length: {len(frame)}\r\n\r\n'.encode('ascii')) self.wfile.write(frame) self.wfile.write(b'\r\n') self.wfile.flush() time.sleep(1.0 / FPS) except (BrokenPipeError, ConnectionResetError): break def log_message(self, fmt, *args): return if __name__ == '__main__': threading.Thread(target=camera_reader, daemon=True).start() with ReusableThreadingTCPServer(('0.0.0.0', PORT), Handler) as server: server.daemon_threads = True server.serve_forever()