#!bin/python """ Camera images of the stage. View live on a web page and also save them to disk. Retrieve images based on the song and time that was playing when they were taken. Also, save snapshot images to a place they can be used again as thumbnails of effects. bin/vidref main light9/vidref/videorecorder.py capture frames and save them light9/vidref/replay.py backend for vidref.js playback element- figures out which frames go with the current song and time light9/vidref/index.html web ui for watching current stage and song playback light9/vidref/setup.html web ui for setup of camera params and frame crop light9/web/vidref.js LitElement for video playback """ from run_local import log from twisted.internet import reactor, defer import logging, optparse, json, base64 import cyclone.web, cyclone.httpclient, cyclone.websocket from light9 import networking from light9.vidref.replay import snapshotDir from light9.vidref import videorecorder from rdfdb.syncedgraph import SyncedGraph from io import BytesIO parser = optparse.OptionParser() parser.add_option("-v", "--verbose", action="store_true", help="logging.DEBUG") (options, args) = parser.parse_args() log.setLevel(logging.DEBUG if options.verbose else logging.INFO) class Snapshot(cyclone.web.RequestHandler): @defer.inlineCallbacks def post(self): # save next pic # return /snapshot/path try: outputFilename = yield self.settings.gui.snapshot() assert outputFilename.startswith(snapshotDir()) out = networking.vidref.path( "snapshot/%s" % outputFilename[len(snapshotDir()):].lstrip('/')) self.write(json.dumps({'snapshot': out})) self.set_header("Location", out) self.set_status(303) except Exception: import traceback traceback.print_exc() raise pipeline = videorecorder.GstSource( '/dev/v4l/by-id/usb-Generic_FULL_HD_1080P_Webcam_200901010001-video-index0') class Live(cyclone.websocket.WebSocketHandler): def connectionMade(self, *args, **kwargs): pipeline.liveImages.subscribe(on_next=self.onFrame) def connectionLost(self, reason): 0 #self.subj.dispose() def onFrame(self, t_img): t, img = t_img if img is None: return output = BytesIO() img.save(output, 'jpeg', quality=80) self.sendMessage( json.dumps({ 'jpeg': base64.b64encode(output.getvalue()).decode('ascii'), 'description': f't={t}', })) class SnapshotPic(cyclone.web.StaticFileHandler): pass class Time(cyclone.web.RequestHandler): def put(self): body = json.loads(self.request.body) t = body['t'] source = body['source'] self.settings.gui.incomingTime(t, source) self.set_status(202) graph = SyncedGraph(networking.rdfdb.url, "vidref") port = networking.vidref.port reactor.listenTCP( port, cyclone.web.Application( handlers=[ (r'/()', cyclone.web.StaticFileHandler, { 'path': 'light9/vidref', 'default_filename': 'vidref.html' }), (r'/setup/()', cyclone.web.StaticFileHandler, { 'path': 'light9/vidref', 'default_filename': 'setup.html' }), (r'/setup/live', Live), (r'/snapshot', Snapshot), (r'/snapshot/(.*)', SnapshotPic, { "path": snapshotDir() }), (r'/time', Time), ], debug=True, )) log.info("serving on %s" % port) reactor.run()