0

I’m very new to Python and have been struggling with the code for a wildlife camera project on the Pi. I’ve managed to get the PIR sensor and automatic camera capture working, but I’d like to have a streaming mode in the same script which allows me to view the camera feed on my phone when I’m first positioning the camera to check it’s got a good view (toggled by a button wired to one of the GPIO pins).

I've managed to get the button to work to move from capture mode to streaming mode, and the streaming script appears to work perfectly. But the serve_forever command means that it’s permanently streaming (unsurprisingly!) and won't move out of it to go back to capture. I've tried various edits, but it won’t respond to a GPIO input to tell it to stop streaming and then move on to the camera capture part of the script.

This is the code I've adapted (https://randomnerdtutorials.com/video-streaming-with-raspberry-pi-camera/):

import io
import picamera
import logging
import socketserver
from threading import Condition
from http import server

PAGE="""\
<html>
<head>
<title>Raspberry Pi - Surveillance Camera</title>
</head>
<body>
<center><h1>Raspberry Pi - Surveillance Camera</h1></center>
<center><img src="stream.mjpg" width="640" height="480"></center>
</body>
</html>
"""

class StreamingOutput(object):
    def __init__(self):
        self.frame = None
        self.buffer = io.BytesIO()
        self.condition = Condition()

    def write(self, buf):
        if buf.startswith(b'\xff\xd8'):
            # New frame, copy the existing buffer's content and notify all
            # clients it's available
            self.buffer.truncate()
            with self.condition:
                self.frame = self.buffer.getvalue()
                self.condition.notify_all()
            self.buffer.seek(0)
        return self.buffer.write(buf)

class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            self.send_response(301)
            self.send_header('Location', '/index.html')
            self.end_headers()
        elif self.path == '/index.html':
            content = PAGE.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', len(content))
            self.end_headers()
            self.wfile.write(content)
        elif 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()
            try:
                while True:
                    with output.condition:
                        output.condition.wait()
                        frame = output.frame
                    self.wfile.write(b'--FRAME\r\n')
                    self.send_header('Content-Type', 'image/jpeg')
                    self.send_header('Content-Length', len(frame))
                    self.end_headers()
                    self.wfile.write(frame)
                    self.wfile.write(b'\r\n')
            except Exception as e:
                logging.warning(
                    'Removed streaming client %s: %s',
                    self.client_address, str(e))
        else:
            self.send_error(404)
            self.end_headers()

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True

with picamera.PiCamera(resolution='640x480', framerate=24) as camera:
    output = StreamingOutput()
    #Uncomment the next line to change your Pi's Camera rotation (in degrees)
    #camera.rotation = 90
    camera.start_recording(output, format='mjpeg')
    try:
        address = ('', 8000)
        server = StreamingServer(address, StreamingHandler)
        server.serve_forever()
    finally:
        camera.stop_recording()

Is there a way I can adapt this script so I can end the stream when there’s a GPIO input? Or a way of implementing a timeout on the server, so it only streams for a few seconds, and then moves on to check the GPIO input and either repeat the streaming or go onto capture mode?

I’ve seen a few examples using KeyboardInterrupt but I think this might work differently, with Python always listening out for that, rather than the GPIO signals which need to have a line of code to check their status (and obviously can't be run while serve_forever is being executed).

I’ve seen lots of references to threading for ending streaming servers. But every example I’ve seen is far too technical for me, and I have no clue how to adapt it for my current project. So if anyone is able to explain precisely what I need to do with my code to use threading for this, that would be incredibly helpful.

Many thanks in advance, and apologies for my complete lack of knowledge!

Joel

0

Thanks to you, i've made this, i try to make the same thing as you (take a snap from a stream to save a picture).

I thought about stopping the stream, taking the picture and going back to stream via another URL. It has worked once but no way to make it work a second time...

I've added some functions to manage the camera to the server.

For the GPIO input i thought about a thread that trigger the GPIO and load the "shot.html" via wget.

Have you find another answer ?

import picamera
from datetime import datetime
import io
import logging
import socketserver
from threading import Condition
from http import server

PAGE="""\
<html>
<head>
<title>Raspberry Pi - Surveillance Camera</title>
</head>
<body>
<center><h1>Raspberry Pi - Surveillance Camera</h1></center>
<center><a href="/shot.html"><img src="stream.mjpg" width="640" height="480"></a></center>
</body>
</html>
"""

class StreamingOutput(object):
    def __init__(self):
        self.frame = None
        self.buffer = io.BytesIO()
        self.condition = Condition()

    def write(self, buf):
        if buf.startswith(b'\xff\xd8'):
            # New frame, copy the existing buffer's content and notify all
            # clients it's available
            self.buffer.truncate()
            with self.condition:
                self.frame = self.buffer.getvalue()
                self.condition.notify_all()
            self.buffer.seek(0)
        return self.buffer.write(buf)

class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            self.send_response(301)
            self.send_header('Location', '/index.html')
            self.end_headers()
        elif self.path == '/index.html':
            content = PAGE.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', len(content))
            self.end_headers()
            self.wfile.write(content)
        elif 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()
            try:
                while True:
                    with output.condition:
                        output.condition.wait()
                        frame = output.frame
                    self.wfile.write(b'--FRAME\r\n')
                    self.send_header('Content-Type', 'image/jpeg')
                    self.send_header('Content-Length', len(frame))
                    self.end_headers()
                    self.wfile.write(frame)
                    self.wfile.write(b'\r\n')
            except Exception as e:
                logging.warning(
                    'Removed streaming client %s: %s',
                    self.client_address, str(e))
        elif self.path == '/shot.html':
            server.shot_camera()
            self.send_response(301)
            self.send_header('Location', '/index.html')
            self.end_headers()
        else:
            self.send_error(404)
            self.end_headers()

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True
    def shot_camera(self):
        self.camera.stop_recording()
        self.camera.resolution=(1920,1080)
        self.camera.start_preview()
        sleep(5)
        self.camera.capture("/home/pi/" + datetime.now().strftime("%d-%b-%Y.(%H_%M_%S_%f)") + ".jpg")
        self.camera.resolution=(640,480)
        self.camera.start_recording(output, format='mjpeg')
    def set_camera(self):
        self.camera=picamera.PiCamera(resolution='640x480', framerate=24)
        #Uncomment the next line to change your Pi's Camera rotation (in degrees)
        #camera.rotation = 90
        self.camera.start_recording(output, format='mjpeg')
    def stop_camera(self):
        self.camera.stop_recording()

output = StreamingOutput()
try:
        address = ('', 8000)
        server = StreamingServer(address, StreamingHandler)
        server.set_camera()
        server.serve_forever()
finally:
        server.stop_camera()
  • This doesn't seem like an answer to the question. It's either a comment or a new question. I'm having trouble telling which. – Brick Aug 20 at 13:37
  • I've not read that giving a part of the solution was that bad... sorry. – Fardouk Aug 20 at 14:09
0

I've found the answer !

Why this only execute once was the 301 answer (permanent redirection) the browser have memorized ! So, to avoid it, i've put a simple html redirection.

import picamera
from datetime import datetime
import io
import logging
import socketserver
from threading import Condition
from http import server
from time import sleep

PAGE="""\
<html>
<head>
<title>Raspberry Pi - Surveillance Camera</title>
</head>
<body>
<center><h1>Raspberry Pi - Surveillance Camera</h1></center>
<center><a href="/shot.html"><img src="stream.mjpg" width="640" height="480"></a></center>
</body>
</html>
"""
Redirection="""<html><head><meta http-equiv="refresh" content="0;URL=/index.html"></head></html>"""

class StreamingOutput(object):
    def __init__(self):
        self.frame = None
        self.buffer = io.BytesIO()
        self.condition = Condition()
        self.camera=picamera.PiCamera(resolution='640x480', framerate=24)

    def write(self, buf):
        if buf.startswith(b'\xff\xd8'):
            # New frame, copy the existing buffer's content and notify all
            # clients it's available
            self.buffer.truncate()
            with self.condition:
                self.frame = self.buffer.getvalue()
                self.condition.notify_all()
            self.buffer.seek(0)
        return self.buffer.write(buf)
    def shot_camera(self):
        self.camera.wait_recording(1)
        self.camera.stop_recording()
        sleep(1)
        self.camera.resolution=(1920,1080)
        self.camera.start_preview()
        sleep(5)
        self.camera.capture("/home/pi/" + datetime.now().strftime("%d-%b-%Y.(%H_%M_%S_%f)") + ".jpg")
        self.camera.resolution=(640,480)
        self.camera.start_recording(self, format='mjpeg')
    def start_recording(self):
        #Uncomment the next line to change your Pi's Camera rotation (in degrees)
        #camera.rotation = 90
        self.camera.start_recording(self, format='mjpeg')
    def stop_camera(self):
        self.camera.stop_recording()

class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            self.send_response(301)
            self.send_header('Location', '/index.html')
            self.end_headers()
        elif self.path == '/index.html':
            content = PAGE.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', len(content))
            self.end_headers()
            self.wfile.write(content)
        elif 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()
            try:
                while True:
                    with output.condition:
                        output.condition.wait()
                        frame = output.frame
                    self.wfile.write(b'--FRAME\r\n')
                    self.send_header('Content-Type', 'image/jpeg')
                    self.send_header('Content-Length', len(frame))
                    self.end_headers()
                    self.wfile.write(frame)
                    self.wfile.write(b'\r\n')
            except Exception as e:
                logging.warning(
                    'Removed streaming client %s: %s',
                    self.client_address, str(e))
        elif self.path == '/shot.html':
            output.shot_camera()
            content = Redirection.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', len(content))
            self.end_headers()
            self.wfile.write(content)
        else:
            self.send_error(404)
            self.end_headers()

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True

output = StreamingOutput()
output.start_recording()
try:
        address = ('', 8000)
        serveur = StreamingServer(address, StreamingHandler)
        serveur.serve_forever()
finally:
        output.stop_camera()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.