Seite 1 von 2
bottle - streamen von .jpg
Verfasst: Donnerstag 10. Juli 2014, 10:50
von lackschuh
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
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"/>'
Re: bottle - streamen von .jpg
Verfasst: Freitag 11. Juli 2014, 08:07
von jerch
@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.
Re: bottle - streamen von .jpg
Verfasst: Freitag 11. Juli 2014, 08:52
von lackschuh
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ÄVFI]E.XBö_ gQ´.01 c{&÷÷ójU«YóUìþþ)h¶h¢¼£e¾³0pù³y#§#*¤±̞rO8úp8Ñd;»dö<ô@5UÏÌ1nFJ¢ネ¹Ís¾º®´zm®ý.Gov[wçõ¯õw¡ÆdO\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.
Re: bottle - streamen von .jpg
Verfasst: Freitag 11. Juli 2014, 09:48
von Sirius3
@lackschuh: Du mußt auch den richtigen Content-Type übertragen.
Re: bottle - streamen von .jpg
Verfasst: Freitag 11. Juli 2014, 13:20
von lackschuh
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'
Re: bottle - streamen von .jpg
Verfasst: Freitag 11. Juli 2014, 13:45
von snafu
@lackschuh: Warum definierst du die Content-Length überhaupt gesondert?
Re: bottle - streamen von .jpg
Verfasst: Freitag 11. Juli 2014, 18:27
von jerch
@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()
Re: bottle - streamen von .jpg
Verfasst: Montag 14. Juli 2014, 13:32
von lackschuh
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
Re: bottle - streamen von .jpg
Verfasst: Montag 14. Juli 2014, 15:08
von jerch
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)
Re: bottle - streamen von .jpg
Verfasst: Dienstag 15. Juli 2014, 13:23
von lackschuh
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]
Re: bottle - streamen von .jpg
Verfasst: Mittwoch 16. Juli 2014, 00:04
von jerch
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.
Re: bottle - streamen von .jpg
Verfasst: Mittwoch 16. Juli 2014, 10:00
von jerch
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')
Re: bottle - streamen von .jpg
Verfasst: Mittwoch 16. Juli 2014, 12:51
von lackschuh
jerch mein Gott, mein König

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).
Re: bottle - streamen von .jpg
Verfasst: Mittwoch 16. Juli 2014, 13:11
von snafu
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).
Re: bottle - streamen von .jpg
Verfasst: Mittwoch 16. Juli 2014, 13:24
von snafu
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.
Re: bottle - streamen von .jpg
Verfasst: Mittwoch 16. Juli 2014, 13:43
von lackschuh
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.
Re: bottle - streamen von .jpg
Verfasst: Mittwoch 16. Juli 2014, 14:06
von snafu
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.

Re: bottle - streamen von .jpg
Verfasst: Freitag 18. Juli 2014, 15:00
von lackschuh
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
Re: bottle - streamen von .jpg
Verfasst: Freitag 18. Juli 2014, 18:19
von apollo13
Ja, über sowas braucht man sich keine sorgen zu machen.
Re: bottle - streamen von .jpg
Verfasst: Mittwoch 25. Februar 2015, 13:52
von lackschuh
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.