Initial commit

This commit is contained in:
Dave Pedu 2017-12-29 09:50:38 -08:00
commit 5617c368b7
4 changed files with 200 additions and 0 deletions

10
README Normal file
View File

@ -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.

106
catpic.py Normal file
View File

@ -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()

75
page.htm Normal file
View File

@ -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>

9
requirements.txt Normal file
View File

@ -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