#!env_pi/bin/python from __future__ import division from run_local import log import sys;sys.path.append('/usr/lib/python2.7/dist-packages/') import io, logging, traceback, time import cyclone.web from twisted.internet import reactor, threads from twisted.internet.defer import inlineCallbacks from light9 import prof try: import picamera cameraCls = picamera.PiCamera except ImportError: class cameraCls(object): def __enter__(self): return self def __exit__(self, *a): pass def capture(self, out, *a, **kw): out.write(open('yuv.demo').read()) def capture_continuous(self, *a, **kw): for i in range(1000): time.sleep(1) yield str(i) def setCameraParams(c, arg): res = int(arg('res', 480)) c.resolution = { 480: (640, 480), 1080: (1920, 1080), 1944: (2592, 1944), }[res] c.shutter_speed = int(arg('shutter', 50000)) c.exposure_mode = arg('exposure_mode', 'fixedfps') c.awb_mode = arg('awb_mode', 'off') c.brightness = int(arg('brightness', 50)) c.exposure_compensation= int(arg('exposure_compensation', 0)) c.awb_gains = (float(arg('redgain', 1)), float(arg('bluegain', 1))) c.ISO = int(arg('iso', 250)) c.rotation = int(arg('rotation', '0')) def setupCrop(c, arg): c.crop = (float(arg('x', 0)), float(arg('y', 0)), float(arg('w', 1)), float(arg('h', 1))) rw = rh = int(arg('resize', 100)) # width 1920, showing w=.3 of image, resize=100 -> scale is 100/.3*1920 # scl is [ output px / camera px ] scl1 = rw / (c.crop[2] * c.resolution[0]) scl2 = rh / (c.crop[3] * c.resolution[1]) if scl1 < scl2: # width is the constraint; reduce height to the same scale rh = int(scl1 * c.crop[3] * c.resolution[1]) else: # height is the constraint rw = int(scl2 * c.crop[2] * c.resolution[0]) return rw, rh @prof.logTime def getFrame(c, arg): setCameraParams(c, arg) resize = setupCrop(c, arg) out = io.BytesIO('w') prof.logTime(c.capture)(out, 'jpeg', use_video_port=True, resize=resize) return out.getvalue() class Pic(cyclone.web.RequestHandler): def get(self): try: self.set_header('Content-Type', 'image/jpeg') self.write(getFrame(self.settings.camera, self.get_argument)) except Exception: traceback.print_exc() def captureContinuousAsync(c, resize, onFrame): """ Calls c.capture_continuous is called in another thread. onFrame is called in this reactor thread with each (frameTime, frame) result. Runs until onFrame raises StopIteration. """ def runner(c, resize): stream = io.BytesIO() t = time.time() for nextFrame in c.capture_continuous(stream, 'jpeg', use_video_port=True, resize=resize): t2 = time.time() log.debug(" - framecap got %s bytes in %.1f ms", len(stream.getvalue()), 1000 * (t2 - t)) try: # This is slow, like 13ms. Hopefully # capture_continuous is working on gathering the next # pic during this time instead of pausing. # Instead, we could be stashing frames onto a queue or # something that the main thread can pull when # possible (and toss if it gets behind). threads.blockingCallFromThread(reactor, onFrame, t, stream.getvalue()) except StopIteration: break t3 = time.time() log.debug(" - sending to onFrame took %.1fms", 1000 * (t3 - t2)) stream.truncate() stream.seek(0) t = time.time() return threads.deferToThread(runner, c, resize) class FpsReport(object): def __init__(self): self.frameTimes = [] self.lastFpsLog = 0 def frame(self): now = time.time() self.frameTimes.append(now) if len(self.frameTimes) > 15: del self.frameTimes[:5] if now > self.lastFpsLog + 2 and len(self.frameTimes) > 5: deltas = [(b - a) for a, b in zip(self.frameTimes[:-1], self.frameTimes[1:])] avg = sum(deltas) / len(deltas) log.info("fps: %.1f", 1 / avg) self.lastFpsLog = now class Pics(cyclone.web.RequestHandler): @inlineCallbacks def get(self): try: self.set_header('Content-Type', 'x-application/length-time-jpeg') c = self.settings.camera setCameraParams(c, self.get_argument) resize = setupCrop(c, self.get_argument) self.running = True log.info("connection open from %s", self.request.remote_ip) fpsReport = FpsReport() def onFrame(frameTime, frame): if not self.running: raise StopIteration now = time.time() self.write("%s %s\n" % (len(frame), frameTime)) self.write(frame) self.flush() fpsReport.frame() # another camera request coming in at the same time breaks # the server. it would be nice if this request could # let-go-and-reopen when it knows about another request # coming in yield captureContinuousAsync(c, resize, onFrame) except Exception: traceback.print_exc() def on_connection_close(self, *a, **kw): log.info("connection closed") self.running = False log.setLevel(logging.INFO) with cameraCls() as camera: port = 8001 reactor.listenTCP(port, cyclone.web.Application(handlers=[ (r'/pic', Pic), (r'/pics', Pics), (r'/static/(.*)', cyclone.web.StaticFileHandler, {'path': 'static/'}), (r'/(|gui.js)', cyclone.web.StaticFileHandler, {'path': 'light9/vidref/', 'default_filename': 'index.html'}), ], debug=True, camera=camera)) log.info("serving on %s" % port) reactor.run()