bottle - streamen von .jpg

Django, Flask, Bottle, WSGI, CGI…
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Ich hab folgendes Skript im www gefunden und wollte mal wissen, ob dies so in bottle auch geht. Hab leider nicht so viel Ahnung von Netzwerktechnik. Bis jetzt habe ich dieses Skript separat ausgeführt und per iFrame in meine bottle App eingebunden. Denke aber, dass dies nicht viel Sinn macht. Für ein paar Tipps wäre ich dankbar 8)
Quellen:
picamera, PiCameraStream

Code: Alles auswählen

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import io
import time
import picamera
 
camera=None
 
 
class CamHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # wenn der request Pfad mit .mjpg endet
        if self.path.endswith('.mjpg'):
            self.send_response(200)
            self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary')
            self.end_headers()
            stream=io.BytesIO()
            try:
                for foo in camera.capture_continuous(stream,'jpeg'):
                    self.wfile.write("--jpgboundary")
                    self.send_header('Content-type','image/jpeg')
                    self.send_header('Content-length',len(stream.getvalue()))
                    self.end_headers()
                    self.wfile.write(stream.getvalue())
                    stream.seek(0)
                    stream.truncate()
                    time.sleep(.5)
            except KeyboardInterrupt:
                exit() 
            return
        else:
            self.send_response(200)
            self.send_header('Content-type','text/html')
            self.end_headers()
            self.wfile.write("""<html><head></head><body>
        <img src="/cam.mjpg"/>
      </body></html>""")
        return
 
def main():
    global camera
    camera = picamera.PiCamera()
    camera.rotation = 180
    camera.resolution = (640, 480)
    try:
        server = HTTPServer(('10.0.2.108',8080),CamHandler)
        print "server started"
        server.serve_forever()
    except KeyboardInterrupt:
        camera.close()
        server.socket.close()
 
if __name__ == '__main__':
  main()
bottle:

Code: Alles auswählen

@route('/live-stream', method='GET')
def live_stream():
    #response.content_type = 'multipart/x-mixed-replace; boundary=--jpgboundary'
    my_stream = io.BytesIO()
    with picamera.PiCamera() as camera:
        camera.start_preview()
        # Camera warm-up time
        time.sleep(2)
        camera.capture(my_stream, 'jpeg')
        
    return '<img src="/cam.mjpg"/>'
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@lackschuh:
So wie Du es jetzt hast, kommt da kein Bild an sondern nur der String '<img src="/cam.mjpg"/>'. Du musst 2 Dinge unterscheiden:

Zum einem muss dem Brwoser die Resource bekannt gegeben werden. Das machst Du z.B. mit dem String, wobei man den doch eher in ein HTML-Dokument einbetten würde.

Zweitens brauchst Du eine Route auf die Resource '/cam.mjpg', dort gibst Du die Binärdaten von der Kamera zurück. Die musst Du mit dem korrekten Header ausliefern (wie siehst Du in Deinem anderen Bsp.). Zu testen wäre, wie Bottle mit den Daten umgeht und Iteratoren korrekt ins WGSI umsetzt. Dann könnest Du analog zu diesem Beispiel (https://gist.github.com/tzicatl/2409815) einen Iterator über dem Datenstream zurückgeben.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Danke für die Info. Werde es mir durchlesen. Bis jetzt zeigt es mir nur die Binärdaten wie

Code: Alles auswählen

Tp £-Àp‘Œã׌ÎWb12ÄVFI]E.XBö_ gQ´.01    c{&÷÷ójU«YóU”ìþþ)h¶h¢¼£e¾³0pù³“y#§#*¤±̞rO8úp8іd;»d€ö<ô@5UÏÌ1nFJŸ¢ネ¹Ís¾º®´zm®ý.Gov[wçõ¯õw¡žÆdŒ’O\u9ǧpAj9 dv9ÁïêANsԊp0z¶sŒœóß߱>½FpiHTvûûÇ8#æŸzT=ޫntò۳ôԯ¿ññ_yž~:ŸRs^r8?Í6PNy'=:dõ88u3ߚsòl–Ïϩ:ª¾q9ÛۑؑקPHS¥.ڭ»ùù§¿ž(‘’WJ»£¯^£ùÇ8$VeAêy=}HÉ÷÷K³òò<Ӟ ;säc¼)ƒž‡Œcöö ߌs......
an.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@lackschuh: Du mußt auch den richtigen Content-Type übertragen.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo Sirius3

Ich probiere gerade Stück für Stück das PiCameraStream Beispiel umzusetzen aber
mit

Code: Alles auswählen

@route('/live-stream/', method='GET')
def live_stream():
    response.content_type = 'multipart/x-mixed-replace; boundary=--jpgboundary'
    stream = io.BytesIO()
    with picamera.PiCamera() as camera:
        camera.start_preview()
        time.sleep(2)
        camera.capture(stream, 'jpeg')
    stream.seek(0)
    stream.truncate()
    response.content_type = 'image/jpeg'
    response.content_length = len(stream.getvalue())
    #image = stream.getvalue()
bekomm ich folgende Meldung:
File "/usr/local/lib/python2.7/dist-packages/cherrypy/wsgiserver/wsgiserver2.py", line 2349, in write
"Response body exceeds the declared Content-Length.")
ValueError: Response body exceeds the declared Content-Length.
Laut bottle Doku wird der Content-Typ mit response.content_type = 'text/html' 'übertragen'? Oder so: response.headers['Content-Type'] = 'multipart/x-mixed-replace; boundary=--jpgboundary'
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@lackschuh: Warum definierst du die Content-Length überhaupt gesondert?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@lackschuh:
Warum überschreibst Du weiter unten den Contenttype wieder? Desweiteren wird da noch kein Stream draus, dazu fehlt Dir die Schleifenlogik, welche ständig Daten "nachschiebt". Bitte lies die Spezifikation zu MJPEG nach, da steht, dass die Verbindung offen gehalten wird, heisst, Du musst aus dem Block heraus mehrfach Werte zurückgeben können. Das ginge mit einem Iterator, wie gesagt weiss ich nicht wie Bottle damit umgeht. Ausserdem muss der WSGI-Server die Verbindung offen halten und die Rückgabenwerte einzeln rausschreiben können (asychron, kann nicht jeder Server). Das ist der große Unterschied zu Deinem obigen Beispiel - dort wird direkt in die Verbindung geschrieben, was in WSGI nicht so ohne Weiteres möglich ist.

Schematisch:

Code: Alles auswählen

@route('live-stream')
def live_stream():
    response.content_type = 'multipart/x-mixed-replace; boundary=--wotever'
    boundery = '--wotever'
    while True:
        # Kann bottle asynchrones yield?
        # Laut Spec muss boundery immer mitgeschickt werden
        yield boundery + get_data_from_camera()
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Danke mal für die Infos. Hab nach langem Suchen in der Bottle Doku folgendes gefunden:

Code: Alles auswählen

from gevent import monkey; monkey.patch_all()

import time
from bottle import route, run

@route('/stream')
def stream():
    yield 'START'
    time.sleep(3)
    yield 'MIDDLE'
    time.sleep(5)
    yield 'END'

run(host='0.0.0.0', port=8080, server='gevent')
http://bottlepy.org/docs/dev/recipes.html

Weiters steht in der Doku: A gevent-based server is actually not asynchronous, but massively multi-threaded.

Die 'gevent' Bibliothek habe ich mir installiert und nun versuche ich es anhand deiner schematischen Darstellung umzusetzen. Aber ein paar Theorie-Fragen habe ich noch:

- ich schreib mir eine normale Funktion (get_data_from_camera), welche die Bilder aufnimmt und als Byte-Objekt zurückgibt?
- in der Funktion live_stream() wird mittels dem yield-Statement 'boundery + get_data_from_camera()' zurückgegeben. Aber über was wird denn hier iteriert? Müsste ich dann in der live_stream() Funktion eine for-Schleife zuerst einbauen wie im obigen Beispiel 'for foo in camera.capture_continuous(stream,'jpeg'):'?

mfg
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

lackschuh hat geschrieben:- ich schreib mir eine normale Funktion (get_data_from_camera), welche die Bilder aufnimmt und als Byte-Objekt zurückgibt?
Im Prinzip ja, wobei ich Dir nicht sagen kann, wie der Bytestring intern aufzubauen ist (welche Bildformate unterstützt werden etc.). Dazu musst Du wiederum die Spec bemühen.
lackschuh hat geschrieben:- in der Funktion live_stream() wird mittels dem yield-Statement 'boundery + get_data_from_camera()' zurückgegeben. Aber über was wird denn hier iteriert? Müsste ich dann in der live_stream() Funktion eine for-Schleife zuerst einbauen wie im obigen Beispiel 'for foo in camera.capture_continuous(stream,'jpeg'):'?
Mein Bsp. war nur schematisch, `while True` iteriert immer - eine bessere Version würde hier z.B. solange iterieren, bis der Bildernachschub von der Kamera abbricht (was wohl das Bsp. macht). Bei dem boundery müsstest Du wieder in die Spec schauen, wie das zu formatieren und wo einzusetzen ist (wahrscheinlich zwischen den Einzelbildern).

Wenn das Ganze auch für mehrere Clients verlässlich funktionieren soll, brauchst Du zwischen den `live_stream`-Aufrufen durch bottle und der Kamera noch einen Puffer, der nicht "leerläuft" oder blockiert, wenn die Bilder von bottle abgeholt werden (z.B. einen Ringpuffer mit deque)
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Mir fehlt irgendwie noch die Idee/Logik, wie ich den Bytestring als jpg bzw. mjpg angezeigt bekomme. Im obigen Beispiel wird der Bytestring mittels BaseHTTPRequestHandler.wfile.write(stream.getvalue()) als Antwort an den Client geschrieben/geschickt. Also muss ich in der Funktion get_data_from_camera() den String irgendwie ins 'Template' schreiben und alsdann das Template zurückgeben(?)
stream.getvalue() wären die Binärdaten.

Alternative zu mjpg wäre h264.
http://picamera.readthedocs.org/en/rele ... o-a-stream

Code: Alles auswählen

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()
            stream=io.BytesIO()
            try:
                for foo in camera.capture_continuous(stream,'jpeg'):
                    self.wfile.write("--jpgboundary")
                    self.send_header('Content-type','image/jpeg')
                    self.end_headers()
                    self.wfile.write(stream.getvalue())
                    stream.seek(0)
                    stream.truncate()
                    time.sleep(.5)
            except KeyboardInterrupt:
                pass

        else:
            self.send_response(200)
            self.send_header('Content-type','text/html')
            self.end_headers()
            self.wfile.write("""<html><head></head><body>
                                     <img src="/cam.mjpg"/>
                                     </body></html>""")
[/size]
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

lackschuh hat geschrieben:Mir fehlt irgendwie noch die Idee/Logik, wie ich den Bytestring als jpg bzw. mjpg angezeigt bekomme. Im obigen Beispiel wird der Bytestring mittels BaseHTTPRequestHandler.wfile.write(stream.getvalue()) als Antwort an den Client geschrieben/geschickt. Also muss ich in der Funktion get_data_from_camera() den String irgendwie ins 'Template' schreiben und alsdann das Template zurückgeben(?)
stream.getvalue() wären die Binärdaten.
Nein, die Binärdaten haben nichts im Template zu suchen. Die Binärdaten müssen unter der Resource, welche Du im HTML-Template unter 'src="<resource>"' angegeben hast, erreichbar sein. Damit der Browser die Daten korrekt interpretiert, musst Du die Spec für das MJPEG-Format einhalten - also Header richtig setzen als auch die Einzelbilder mit korrekten boundaries rausschicken.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Das funktioniert zumindest im Firefox:

Code: Alles auswählen

from gevent import monkey; monkey.patch_all()
from bottle import route, run, response
import time, os
from glob import glob
from itertools import cycle

BOUNDARY = '--nixda'

@route('/index.html')
def index():
    return '<html><head></head><body><img src="/test.mjpg" /></body></html>'

@route('/test.mjpg')
def mjpeg():
    # global header
    response.content_type = 'multipart/x-mixed-replace;boundary=%s' % BOUNDARY
    yield BOUNDARY+'\r\n'
    for filename in cycle(glob('img/*.jpg')):
        time.sleep(.1)
        # pic header
        yield 'Content-Type: image/jpeg\r\nContent-Length: %s\r\n\r\n' % os.path.getsize(filename)
        # pic data
        yield open(filename).read() + BOUNDARY+'\r\n'


run(host='0.0.0.0', port=8080, server='gevent')
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

jerch mein Gott, mein König :mrgreen: :lol: es geht!

Gäbe es noch ein paar Schönheitskorrekturen?

Code: Alles auswählen

# coding: utf-8

from gevent import monkey; monkey.patch_all()
from bottle import route, run, response, debug
import time
import picamera
import io

BOUNDARY = '--jpgboundary'


@route('/index.html')
def index():
    return '<html><head></head><body><img src="/test.mjpg" /></body></html>'
 
@route('/test.mjpg')
def mjpeg():
    camera = picamera.PiCamera()
    camera.rotation = 180
    camera.resolution = (640, 480)
    
    response.content_type = 'multipart/x-mixed-replace;boundary=%s' % BOUNDARY
    stream = io.BytesIO()
    yield BOUNDARY+'\r\n'
    
    for foo in camera.capture_continuous(stream,'jpeg'):
        yield BOUNDARY+'\r\n'
        yield 'Content-Type: image/jpeg\r\nContent-Length: %s\r\n\r\n' % len(stream.getvalue())
        yield stream.getvalue()
        stream.seek(0)
        stream.truncate()
        time.sleep(.1)

run(reloader=True, host='0.0.0.0', port=8080, server='gevent')
EDIT:
So wie es jetzt läuft kann halt nur ein Client zugreifen. Im Prinzip für meine Bedürfnisse ausreichend aber ich werde mich mal damit auseinander setzen (Ringpuffer).
Zuletzt geändert von lackschuh am Mittwoch 16. Juli 2014, 13:11, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

lackschuh hat geschrieben:Gäbe es noch ein paar Schönheitskorrekturen?
Aus der ``for``-Schleife könnte man eine ``while``-Schleife machen, dann braucht man auch keinen Dummywert mehr. Etwas unschön finde ich auch den zweimaligen Aufruf von ``.getvalue()``, besonders in Verbindung mit ``len()``. Du könntest stattdessen ``stream.tell()`` benutzen, um die aktuelle Cursorposition zu erfahren. Diese entspricht nach dem Schreiben ja der Gesamtlänge des Streaminhalts (sprich: der Anzahl an Bytes).
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

lackschuh hat geschrieben:So wie es jetzt läuft kann halt nur ein Client zugreifen. Im Prinzip für meine Bedürfnisse ausreichend aber ich werde mich mal damit auseinander setzen (Ringpuffer).
Ich würde da eigentlich nur das aktuelle Bild puffern, sofern ein Live-Zugriff auf die Kamera angedacht ist. In einer Schleife wird dann halt alle 100 Millisekunden ein neues Bild abgelegt und die verschiedenen Clients fragen deinen Kamera-Wrapper nach dem aktuellen Bild. Dann muss sich die Kamera an sich nicht weiter damit beschäftigen, sondern sie liefert stumpf in gleichbleibenden Abständen das Bildmaterial, welches von der Wrapper-Klasse erfragt wird. Wenn gerade kein Client ein Bild sehen will, dann wird das ungenutzte Bild halt wieder verworfen.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Danke für die Info.
Im Prinzip soll die Kamera ja gar nicht laufen/eingeschaltet sein sondern nur, wenn eine bestimmte url/route aufgerufen wird.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

lackschuh hat geschrieben:Im Prinzip soll die Kamera ja gar nicht laufen/eingeschaltet sein sondern nur, wenn eine bestimmte url/route aufgerufen wird.
Du meinst, dass du sie dann in Standby oder sowas schalten würdest? Ich kenne jetzt die Funktionsweise der von dir genutzten Bibliothek nicht, aber ich würde erwarten, dass eine aktive Kamera fortwährend Bilder liefert bis man sie halt wieder deaktiviert. Wenn längere Zeit kein Bild abgefragt wurde, dann ist das ja eigentlich sowas wie das Beenden des Streams und Neustart nach einem bestimmten Zeitpunkt. Wenn du einfach bei einem aktiven Stream eine Pause machst, könnte die Kamera blockieren, der interne Puffer der Kamera überlaufen oder ähnliches. Wie gesagt: Da müsste man das genaue Verhalten in solchen Fällen kennen.

Ich würde mir in der von mir vorgeschlagenen Wrapper-Klasse zusätzlich den Zeitpunkt, an dem zuletzt ein Bild von einem Client erfragt wurde, merken. Wenn dieser Zeitpunkt länger als eine bestimmte Zeitspanne vorbei ist (z.B. 30 Sekunden), dann könnte man die Kamera tatsächlich ausschalten. Dann hättest du nicht ständige Restarts der Kamera, was ja sicherlich auch etwas Zeit kostet und bestimmt nicht im Sinne des Erfinders ist. Es muss ja nicht mal der Betrachter der Bilder sein, der ständig auf Pause drückt, sondern es kann auch mal kurze Ausfälle durch einen Verbindungsabbruch geben oder dass eine Anfrage vom Client für das nächste Bild mal verloren geht.

Aber all das ist irgendwo auch abhängig vom konkreten Einsatzgebiet und ich mutmaße hier mehr oder weniger über das Protokoll. Das sollten erstmal nur grobe Denkanstöße sein. ;)
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Ich werde versuchen, das Obgenannte umzusetzen. Folgendes ist mir aufgefallen:

Wenn ich den Browser schliesse bzw. die Verbindung abbreche, sei es mir einem url Wechselt etc, dann kommt folgende Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/gevent/pywsgi.py", line 508, in handle_one_response
    self.run_application()
  File "/usr/local/lib/python2.7/dist-packages/gevent/pywsgi.py", line 495, in run_application
    self.process_result()
  File "/usr/local/lib/python2.7/dist-packages/gevent/pywsgi.py", line 486, in process_result
    self.write(data)
  File "/usr/local/lib/python2.7/dist-packages/gevent/pywsgi.py", line 376, in write
    self._write(data)
  File "/usr/local/lib/python2.7/dist-packages/gevent/pywsgi.py", line 369, in _write
    self._sendall(data)
  File "/usr/local/lib/python2.7/dist-packages/gevent/pywsgi.py", line 355, in _sendall
    self.socket.sendall(data)
  File "/usr/local/lib/python2.7/dist-packages/gevent/socket.py", line 458, in sendall
    data_sent += self.send(_get_memory(data, data_sent), flags)
  File "/usr/local/lib/python2.7/dist-packages/gevent/socket.py", line 435, in send
    return sock.send(data, flags)
error: [Errno 32] Broken pipe
{'GATEWAY_INTERFACE': 'CGI/1.1',
 'HTTP_ACCEPT': 'image/webp,*/*;q=0.8',
 'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
 'HTTP_ACCEPT_LANGUAGE': 'de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4',
 'HTTP_CACHE_CONTROL': 'max-age=0',
 'HTTP_CONNECTION': 'keep-alive',
 'HTTP_HOST': '10.0.2.108:8080',
 'HTTP_REFERER': 'http://10.0.2.108:8080/home',
 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36',
 'PATH_INFO': '/test.mjpg',
 'QUERY_STRING': '',
 'REMOTE_ADDR': '10.0.2.112',
 'REMOTE_PORT': '58388',
 'REQUEST_METHOD': 'GET',
 'SCRIPT_NAME': '',
 'SERVER_NAME': '10.0.2.108',
 'SERVER_PORT': '8080',
 'SERVER_PROTOCOL': 'HTTP/1.1',
 'SERVER_SOFTWARE': 'gevent/1.0 Python/2.7',
 'bottle.app': <bottle.Bottle object at 0xb6766730>,
 'bottle.raw_path': '/test.mjpg',
 'bottle.request': <LocalRequest: GET http://10.0.2.108:8080/test.mjpg>,
 'bottle.request.urlparts': SplitResult(scheme='http', netloc='10.0.2.108:8080', path='/test.mjpg', query='', fragment=''),
 'bottle.route': <GET '/test.mjpg' <function mjpeg at 0x1d6f930>>,
 'route.handle': <GET '/test.mjpg' <function mjpeg at 0x1d6f930>>,
 'route.url_args': {},
 'wsgi.errors': <open file '<stderr>', mode 'w' at 0xb6d460d0>,
 'wsgi.input': <gevent.pywsgi.Input object at 0x1e0f3f0>,
 'wsgi.multiprocess': False,
 'wsgi.multithread': False,
 'wsgi.run_once': False,
 'wsgi.url_scheme': 'http',
 'wsgi.version': (1, 0)} failed with error
Bedeutet dies einfach, dass die gehaltene Verbindung unterbrochen wurde?

mfg
apollo13
User
Beiträge: 827
Registriert: Samstag 5. Februar 2005, 17:53

Ja, über sowas braucht man sich keine sorgen zu machen.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Hab mich nun wieder ein wenig dem Thema gewidmet und folgendes Problem besteht noch:

1. Wenn ich als User zB die Route ``\stream`` verlasse, dann läuft die Kamera weiter, d.h. die Funktion ``def mjpeg():`` wird nicht beendet. Was muss ich hier beachten, dass, wenn der User sie Seite verlässt sich auch die Kamera anständig schliesst?

Code: Alles auswählen

@app.route('/stream')
def stream(db):
    username = is_logged_in(db)
    ip = request.environ.get('REMOTE_ADDR')
    if not username:
        return template('main', username=False,  ip=ip)
    else:
        return template('stream', username=username)

        
@app.route('/test.mjpg')
def mjpeg():
    response.content_type = 'multipart/x-mixed-replace;boundary=%s' % BOUNDARY
    stream = io.BytesIO()
    yield BOUNDARY+'\r\n'
    with picamera.PiCamera() as camera:
        camera.led = False
        camera.exposure_mode = 'night'
        #camera.exposure_mode ='auto'
        camera.rotation = 180
        camera.resolution = (640, 480)
        camera.start_preview()
        camera.annotate_background = True
        time.sleep(2)
        
        while True:
            camera.annotate_text = dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            camera.capture(stream, 'jpeg')
            yield stream.getvalue()
            stream.seek(0)
            stream.truncate()
            time.sleep(.1)
Edit:
Template:

Code: Alles auswählen

<h6>You are logged in as <b>{{username}}</b></h6>
% rebase('layout', title='Live Stream', username=username)
<center><img src="/test.mjpg'" class="img-thumbnail"></center>
PS:
Ich hab mal testhalber eine Klasse geschrieben, in welcher pro User ein Thread gestartet wird. So können x User gleichzeitig den Stream verfolgen. Ich habe aber wieder Abstand davon genommen, denn, wenn ein User wie blöd die URL aktualisiert, werden x Threads gestartet und der RPi fängt dann an zu glühen. Außerdem soll primär die Kamera ausgeschaltet sein. Es ist zurzeit auch nicht vorgesehen, dass mehrere User das ''Interface'' bedienen können/sollen.
Antworten