#!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/light9-vidref-live.js LitElement for live video frames light9/web/light9-vidref-playback.js LitElement for video playback """ from run_local import log from twisted.internet import reactor, defer import logging, optparse, json, base64, os, glob import cyclone.web, cyclone.httpclient, cyclone.websocket from light9 import networking, showconfig from light9.vidref import videorecorder from rdflib import URIRef from light9.newtypes import Song from rdfdb.syncedgraph import SyncedGraph from cycloneerr import PrettyErrorHandler from typing import cast 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: snapshotDir = 'todo' 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-Bison_HD_Webcam_200901010001-video-index0' # '/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, cf: videorecorder.CaptureFrame): if cf is None: return self.sendMessage( json.dumps({ 'jpeg': base64.b64encode(cf.asJpeg()).decode('ascii'), 'description': f't={cf.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) class Clips(PrettyErrorHandler, cyclone.web.RequestHandler): def delete(self): clip = URIRef(self.get_argument('uri')) videorecorder.deleteClip(clip) class ReplayMap(PrettyErrorHandler, cyclone.web.RequestHandler): def get(self): song = Song(self.get_argument('song')) clips = [] for vid in glob.glob(os.path.join(videorecorder.songDir(song), b'*.mp4')): pts = [] for line in open(vid.replace(b'.mp4', b'.timing'), 'rb'): _v, vt, _eq, _song, st = line.split() pts.append([float(st), float(vt)]) url = vid[len(os.path.dirname(os.path.dirname(showconfig.root())) ):].decode('ascii') clips.append({ 'uri': videorecorder.takeUri(vid), 'videoUrl': url, 'songToVideo': pts }) clips.sort(key=lambda c: len(cast(list, c['songToVideo']))) clips = clips[-3:] clips.sort(key=lambda c: c['uri'], reverse=True) ret = json.dumps(clips) log.info('replayMap had %s videos; json is %s bytes', len(clips), len(ret)) self.write(ret) graph = SyncedGraph(networking.rdfdb.url, "vidref") outVideos = videorecorder.FramesToVideoFiles( pipeline.liveImages, os.path.join(showconfig.root(), b'video')) port = networking.vidref.port reactor.listenTCP( port, cyclone.web.Application( handlers=[ (r'/()', cyclone.web.StaticFileHandler, { 'path': 'light9/vidref', 'default_filename': 'index.html' }), (r'/setup/()', cyclone.web.StaticFileHandler, { 'path': 'light9/vidref', 'default_filename': 'setup.html' }), (r'/live', Live), (r'/clips', Clips), (r'/replayMap', ReplayMap), (r'/snapshot', Snapshot), (r'/snapshot/(.*)', SnapshotPic, { "path": 'todo', }), (r'/time', Time), ], debug=True, )) log.info("serving on %s" % port) reactor.run()