Initial version
This commit is contained in:
commit
8407e980cb
12 changed files with 165 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
venv/
|
||||
*.pyc
|
||||
__pycache__/
|
||||
settings.ini
|
0
__main__.py
Normal file
0
__main__.py
Normal file
10
camstream.py
Normal file
10
camstream.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from classes.config import Config
|
||||
from classes.server import ImageServer
|
||||
|
||||
def main():
|
||||
cfg = Config.fromFile("settings.ini")
|
||||
server = ImageServer(cfg.source, cfg.fallback)
|
||||
server.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
0
classes/__init__.py
Normal file
0
classes/__init__.py
Normal file
18
classes/config.py
Normal file
18
classes/config.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from configparser import ConfigParser
|
||||
|
||||
from static import CONFIG_SECTION, CONFIG_FALLBACK, CONFIG_FREQUENCY, CONFIG_SOURCE, CONFIG_PORT
|
||||
|
||||
class Config:
|
||||
@classmethod
|
||||
def fromFile(cls, path):
|
||||
obj = cls()
|
||||
parser = ConfigParser()
|
||||
|
||||
parser.read(path)
|
||||
|
||||
obj.source = parser.get(CONFIG_SECTION, CONFIG_SOURCE)
|
||||
obj.frequency = parser.getint(CONFIG_SECTION, CONFIG_FREQUENCY)
|
||||
obj.fallback = parser.get(CONFIG_SECTION, CONFIG_FALLBACK)
|
||||
obj.port = parser.get(CONFIG_SECTION, CONFIG_PORT)
|
||||
|
||||
return obj
|
65
classes/handler.py
Normal file
65
classes/handler.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
from http.server import BaseHTTPRequestHandler
|
||||
from urllib.request import urlopen
|
||||
from urllib.error import URLError
|
||||
from socket import error, timeout
|
||||
from io import BytesIO
|
||||
from time import sleep
|
||||
|
||||
from classes.image import Image
|
||||
from static import START_HEADERS, PART_BOUNDARY
|
||||
|
||||
class ImageHandler(BaseHTTPRequestHandler):
|
||||
def __init__(self, source, fallback, *args, **kwargs):
|
||||
self.source_image = source
|
||||
self.fallback_image = fallback
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_next_image(self):
|
||||
try:
|
||||
bfr = BytesIO()
|
||||
src = urlopen(self.source_image).read()
|
||||
bfr.write(src)
|
||||
bfr.seek(0)
|
||||
img = Image.open(bfr)
|
||||
except:
|
||||
with open(self.fallback_image, "rb") as infile:
|
||||
bfr = BytesIO()
|
||||
src = infile.read()
|
||||
bfr.write(src)
|
||||
bfr.seek(0)
|
||||
img = Image.open(bfr)
|
||||
|
||||
return img
|
||||
|
||||
def send_image(self, image=None):
|
||||
image = image or self.get_next_image()
|
||||
|
||||
self.end_headers()
|
||||
self.wfile.write(PART_BOUNDARY.encode("utf-8"))
|
||||
self.end_headers()
|
||||
|
||||
data, headers = image.prepare_sending()
|
||||
|
||||
for key, value in headers.items():
|
||||
self.send_header(key, value)
|
||||
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(data)
|
||||
|
||||
def do_GET(self):
|
||||
if not self.path == "/":
|
||||
self.send_response_only(404)
|
||||
return
|
||||
|
||||
self.send_response(200)
|
||||
|
||||
for key, value in START_HEADERS.items():
|
||||
self.send_header(key, value)
|
||||
|
||||
try:
|
||||
while True:
|
||||
self.send_image()
|
||||
sleep(5)
|
||||
except:
|
||||
pass
|
32
classes/image.py
Normal file
32
classes/image.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from PIL.Image import open as PILopen
|
||||
from io import BytesIO
|
||||
|
||||
import time
|
||||
|
||||
class Image:
|
||||
@classmethod
|
||||
def open(cls, *args, **kwargs):
|
||||
img = PILopen(*args, **kwargs)
|
||||
return cls(img)
|
||||
|
||||
def __init__(self, img):
|
||||
self._img = img
|
||||
|
||||
def prepare_sending(self):
|
||||
buffer = BytesIO()
|
||||
|
||||
self.save(buffer, "JPEG")
|
||||
|
||||
headers = {
|
||||
'X-Timestamp': time.time(),
|
||||
'Content-Length': len(buffer.getvalue()),
|
||||
'Content-Type': "image/jpeg"
|
||||
}
|
||||
|
||||
return buffer.getvalue(), headers
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key == '_img':
|
||||
raise AttributeError()
|
||||
|
||||
return getattr(self._img, key)
|
14
classes/server.py
Normal file
14
classes/server.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from http.server import ThreadingHTTPServer
|
||||
|
||||
from classes.handler import ImageHandler
|
||||
|
||||
class ImageServer:
|
||||
def __init__(self, source, fallback, port=8090, ip="0.0.0.0"):
|
||||
class Handler(ImageHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(source, fallback, *args, **kwargs)
|
||||
|
||||
self.server = ThreadingHTTPServer((ip, port), Handler)
|
||||
|
||||
def start(self):
|
||||
self.server.serve_forever()
|
BIN
img/offline.jpeg
Normal file
BIN
img/offline.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 234 KiB |
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Pillow
|
5
settings.dist.ini
Normal file
5
settings.dist.ini
Normal file
|
@ -0,0 +1,5 @@
|
|||
[CAMSTREAM]
|
||||
Source = https://example.com/source
|
||||
Frequency = 5
|
||||
Fallback = img/offline.jpeg
|
||||
Port = 8090
|
16
static.py
Normal file
16
static.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
PART_BOUNDARY = "--BOUNDARY"
|
||||
|
||||
CONFIG_SECTION = "CAMSTREAM"
|
||||
CONFIG_SOURCE = "Source"
|
||||
CONFIG_FREQUENCY = "Frequency"
|
||||
CONFIG_FALLBACK = "Fallback"
|
||||
CONFIG_PORT = "Port"
|
||||
|
||||
START_HEADERS = {
|
||||
'Cache-Control': 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
|
||||
'Connection': 'close',
|
||||
'Content-Type': 'multipart/x-mixed-replace;boundary=%s' % PART_BOUNDARY,
|
||||
'Expires': 'Mon, 1 Jan 2001 00:00:00 GMT',
|
||||
'Pragma': 'no-cache',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
Loading…
Reference in a new issue