|
|
|
@ -3,23 +3,19 @@ |
|
|
|
|
import aiohttp |
|
|
|
|
import asyncio |
|
|
|
|
import cv2 |
|
|
|
|
import io |
|
|
|
|
import numpy as np |
|
|
|
|
import os |
|
|
|
|
import socket |
|
|
|
|
import sys |
|
|
|
|
from datetime import datetime |
|
|
|
|
from kubernetes import client, config |
|
|
|
|
from math import ceil |
|
|
|
|
from prometheus_client import Counter, Gauge |
|
|
|
|
from sanic import Sanic, response |
|
|
|
|
from sanic.response import stream |
|
|
|
|
from sanic_prometheus import monitor |
|
|
|
|
|
|
|
|
|
SERVICE_NAMESPACE = os.getenv("SERVICE_NAMESPACE", "camtiler") |
|
|
|
|
SERVICE_LABEL_SELECTOR = os.getenv("SERVICE_LABEL_SELECTOR", "component=camera-motion-detect") |
|
|
|
|
|
|
|
|
|
targets = [(j,j) for j in sys.argv[1:]] |
|
|
|
|
targets = [(j, j) for j in sys.argv[1:]] |
|
|
|
|
if not targets: |
|
|
|
|
# If no targets are specified, fall back to Kube API |
|
|
|
|
config.load_incluster_config() |
|
|
|
@ -56,7 +52,7 @@ counter_tx_frames = Counter( |
|
|
|
|
counter_tx_events = Counter( |
|
|
|
|
"camtiler_client_tx_events", |
|
|
|
|
"Events emitted") |
|
|
|
|
counter_errors = Counter( |
|
|
|
|
counter_errors = Counter( |
|
|
|
|
"camtiler_errors", |
|
|
|
|
"Upstream connection errors", |
|
|
|
|
["exception"]) |
|
|
|
@ -70,19 +66,19 @@ gauge_build_info.labels( |
|
|
|
|
|
|
|
|
|
app = Sanic("camtiler") |
|
|
|
|
|
|
|
|
|
STREAM_RESPONSE = \ |
|
|
|
|
b""" |
|
|
|
|
STREAM_RESPONSE = b""" |
|
|
|
|
--frame |
|
|
|
|
Content-Type: image/jpeg |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
MERGED_SUBSAMPLE=3 |
|
|
|
|
MERGED_SUBSAMPLE = 3 |
|
|
|
|
|
|
|
|
|
# Blank frame |
|
|
|
|
BLANK = np.full((720, 1280, 3), 0) |
|
|
|
|
GREEN = np.full((720, 1280, 3), (0, 135, 0)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def tilera(iterable): |
|
|
|
|
hstacking = ceil(len(iterable) ** 0.5) |
|
|
|
|
vstacking = ceil(len(iterable) / hstacking) |
|
|
|
@ -131,22 +127,22 @@ async def client_connect(name, resp): |
|
|
|
|
|
|
|
|
|
if buf: |
|
|
|
|
# If we already have something in buffer, seek end in new data |
|
|
|
|
marker = data.find(b'\xff\xd9') |
|
|
|
|
marker = data.find(b"\xff\xd9") |
|
|
|
|
if marker < 0: |
|
|
|
|
# If no end marker was found add it to buffer |
|
|
|
|
buf += data |
|
|
|
|
continue |
|
|
|
|
else: |
|
|
|
|
# If end marker was found, decode JPEG frame |
|
|
|
|
blob = np.frombuffer(buf + data[:marker+2], dtype=np.uint8) |
|
|
|
|
blob = np.frombuffer(buf + data[:marker + 2], dtype=np.uint8) |
|
|
|
|
img = cv2.imdecode(blob, cv2.IMREAD_UNCHANGED) |
|
|
|
|
app.ctx.frames[name] = img |
|
|
|
|
data = data[marker+2:] |
|
|
|
|
data = data[marker + 2:] |
|
|
|
|
buf = b"" |
|
|
|
|
counter_rx_frames.inc() |
|
|
|
|
|
|
|
|
|
# Seek begin in newly received data |
|
|
|
|
marker = data.find(b'\xff\xd8') |
|
|
|
|
marker = data.find(b"\xff\xd8") |
|
|
|
|
if marker >= 0: |
|
|
|
|
data, buf = data[:marker], data[marker:] |
|
|
|
|
|
|
|
|
@ -156,12 +152,12 @@ async def client_connect(name, resp): |
|
|
|
|
|
|
|
|
|
async def client(name, url): |
|
|
|
|
print("Opening upstream connection to %s" % url) |
|
|
|
|
kwargs = dict( |
|
|
|
|
headers = { |
|
|
|
|
kwargs = { |
|
|
|
|
"headers": { |
|
|
|
|
"User-Agent": "camtiler/%s" % GIT_COMMIT_TIMESTAMP |
|
|
|
|
}, |
|
|
|
|
skip_auto_headers = True, |
|
|
|
|
timeout = aiohttp.ClientTimeout(connect=5, sock_read=2)) |
|
|
|
|
"timeout": aiohttp.ClientTimeout(connect=5, sock_read=2) |
|
|
|
|
} |
|
|
|
|
while True: |
|
|
|
|
app.ctx.frames[name] = None |
|
|
|
|
async with aiohttp.ClientSession(**kwargs) as session: |
|
|
|
@ -169,9 +165,9 @@ async def client(name, url): |
|
|
|
|
async with session.get(url) as resp: |
|
|
|
|
await client_connect(name, resp) |
|
|
|
|
except (aiohttp.ClientError, asyncio.exceptions.TimeoutError) as e: |
|
|
|
|
j = "%s.%s" % (e.__class__.__module__, e.__class__.__name__) |
|
|
|
|
print("Caught exception %s for %s" % (j, name)) |
|
|
|
|
counter_errors.labels(exception=j).inc() |
|
|
|
|
j = "%s.%s" % (e.__class__.__module__, e.__class__.__name__) |
|
|
|
|
print("Caught exception %s for %s" % (j, name)) |
|
|
|
|
counter_errors.labels(exception=j).inc() |
|
|
|
|
await asyncio.sleep(1) |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -189,10 +185,11 @@ async def stream_wrapper(request): |
|
|
|
|
|
|
|
|
|
return response.stream( |
|
|
|
|
stream_tiled, |
|
|
|
|
content_type='multipart/x-mixed-replace; boundary=frame' |
|
|
|
|
content_type="multipart/x-mixed-replace; boundary=frame" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
@app.listener('before_server_start') |
|
|
|
|
|
|
|
|
|
@app.listener("before_server_start") |
|
|
|
|
async def setup_db(app, loop): |
|
|
|
|
app.ctx.event_frame = asyncio.Event() |
|
|
|
|
app.ctx.frames = {} |
|
|
|
@ -202,7 +199,7 @@ async def setup_db(app, loop): |
|
|
|
|
app.ctx.motion_end = None |
|
|
|
|
for name, url in targets: |
|
|
|
|
app.ctx.frames[name] = None |
|
|
|
|
task = asyncio.create_task(client(name, url)) |
|
|
|
|
asyncio.create_task(client(name, url)) |
|
|
|
|
|
|
|
|
|
monitor(app).expose_endpoint() |
|
|
|
|
|
|
|
|
|