Initial commit
This commit is contained in:
commit
5617c368b7
|
@ -0,0 +1,10 @@
|
|||
Catpic
|
||||
|
||||
1) Create ./images/ directory
|
||||
2) Install the python modules mentioned in requirements.txt
|
||||
3) Install "imagesnap" (available from Homebrew) and Imagemagick (we need the "convert" command for jpegs)
|
||||
4) Run `python3 catpic.py`
|
||||
5) Browse to http://127.0.0.1:3000/
|
||||
|
||||
The application uses a websocket to push new frames to clients. The initial frame is loaded from /latest.jpg.
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from threading import Thread
|
||||
import traceback
|
||||
import os
|
||||
import cherrypy
|
||||
import subprocess
|
||||
from base64 import b64encode
|
||||
from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
|
||||
from ws4py.websocket import EchoWebSocket
|
||||
from time import sleep, time
|
||||
|
||||
|
||||
INTERVAL = 2
|
||||
cherrypy.config.update({'server.socket_port': 3000,
|
||||
'server.socket_host': "0.0.0.0"})
|
||||
WebSocketPlugin(cherrypy.engine).subscribe()
|
||||
cherrypy.tools.websocket = WebSocketTool()
|
||||
|
||||
|
||||
class CatHandler(Thread):
|
||||
def __init__(self, master):
|
||||
"""
|
||||
Run the image creator subprocess. Every X seconds, delete all images but the newest one
|
||||
"""
|
||||
super().__init__()
|
||||
self.master = master
|
||||
self.proc = None
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
compressed = ""
|
||||
self.startproc()
|
||||
while True:
|
||||
if time() - self.proctime > 15 * 60: # restart proc every 15 minutes
|
||||
self.stopproc()
|
||||
self.startproc()
|
||||
start = time()
|
||||
images = sorted([i.name for i in os.scandir("images")]) # newer images towards end
|
||||
if images:
|
||||
for imname in images[0:-1]:
|
||||
os.unlink(os.path.join("images", imname))
|
||||
newest = os.path.join("images", images[-1])
|
||||
if newest != compressed:
|
||||
subprocess.check_call(["convert", "-strip", "-interlace", "Plane", "-gaussian-blur", "0.05",
|
||||
"-quality", "50%", newest, newest])
|
||||
compressed = newest
|
||||
with open(newest, "rb") as f:
|
||||
self.master.latest = f.read()
|
||||
cherrypy.engine.publish('websocket-broadcast',
|
||||
"data:image/jpeg;base64,{}".format(b64encode(self.master.latest).decode("ascii")))
|
||||
spent = time() - start
|
||||
sleep(max(0.2, INTERVAL - spent))
|
||||
|
||||
def stopproc(self):
|
||||
if not self.proc:
|
||||
raise Exception("No proc to kill")
|
||||
self.proc.kill()
|
||||
self.proc.wait()
|
||||
self.proc = None
|
||||
|
||||
def startproc(self):
|
||||
if self.proc:
|
||||
raise Exception("Proc already running!")
|
||||
# initial cleanout
|
||||
for f in os.scandir("images"):
|
||||
os.unlink(f)
|
||||
self.proc = subprocess.Popen(["imagesnap", "-t", str(INTERVAL)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=os.path.join(os.path.dirname(__file__), "images"))
|
||||
self.proctime = time()
|
||||
|
||||
|
||||
class Root(object):
|
||||
def __init__(self):
|
||||
self.latest = None
|
||||
self.cathandler = CatHandler(self)
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
with open("page.htm") as f:
|
||||
yield f.read()
|
||||
|
||||
@cherrypy.expose
|
||||
def latest_jpg(self):
|
||||
if self.latest:
|
||||
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
|
||||
yield self.latest
|
||||
else:
|
||||
raise cherrypy.HTTPError(404)
|
||||
|
||||
@cherrypy.expose
|
||||
def ws(self):
|
||||
# you can access the class instance through
|
||||
handler = cherrypy.request.ws_handler
|
||||
|
||||
|
||||
def main():
|
||||
cherrypy.quickstart(Root(), '/', config={'/ws': {'tools.websocket.on': True,
|
||||
'tools.websocket.handler_cls': EchoWebSocket}})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CAT</title>
|
||||
<script type='application/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'></script>
|
||||
<script type='application/javascript'>
|
||||
var last_update = Date.now() // milisecond timestamp
|
||||
$(document).ready(function() {
|
||||
var html = $("html");
|
||||
var indicator = $(".indicator");
|
||||
websocket = (window.location.href + "ws").replace("http", "ws"); // Will break if http appears later in the url
|
||||
if (window.WebSocket) {
|
||||
ws = new WebSocket(websocket);
|
||||
}
|
||||
else if (window.MozWebSocket) {
|
||||
ws = MozWebSocket(websocket);
|
||||
}
|
||||
else {
|
||||
console.log('WebSocket Not Supported');
|
||||
return;
|
||||
}
|
||||
window.onbeforeunload = function(e) {
|
||||
ws.close(1000);
|
||||
};
|
||||
ws.onmessage = function (evt) {
|
||||
// console.log(evt.data)
|
||||
html.css("background-image", "url(" + evt.data + ")")
|
||||
console.log("Got message length " + evt.data.length)
|
||||
last_update = Date.now()
|
||||
};
|
||||
ws.onopen = function() {
|
||||
indicator.css("background-color", "#090")
|
||||
console.log("connected")
|
||||
};
|
||||
ws.onclose = function(evt) {
|
||||
console.log("disconnected")
|
||||
indicator.css("background-color", "#F00")
|
||||
};
|
||||
setInterval(function(){
|
||||
indicator.html((Date.now() - last_update)/1000)
|
||||
}, 200)
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
html {
|
||||
background-attachment: fixed;
|
||||
background-clip: border-box;
|
||||
background-image: url(/latest.jpg);
|
||||
background-position-x: 50%;
|
||||
background-position-y: 50%;
|
||||
background-size: cover;
|
||||
}
|
||||
.indicator {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
background-color: #FF0;
|
||||
padding: 15px 0px;
|
||||
line-height: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="indicator">0</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
cheroot==6.0.0
|
||||
CherryPy==13.1.0
|
||||
more-itertools==4.0.1
|
||||
portend==2.2
|
||||
pytz==2017.3
|
||||
six==1.11.0
|
||||
tempora==1.10
|
||||
wheel==0.24.0
|
||||
ws4py==0.4.3
|
Loading…
Reference in New Issue