Mercurial > code > home > repos > light9
diff bin/attic/vidref @ 2376:4556eebe5d73
topdir reorgs; let pdm have its src/ dir; separate vite area from light9/
author | drewp@bigasterisk.com |
---|---|
date | Sun, 12 May 2024 19:02:10 -0700 |
parents | bin/vidref@9aa046cc9b33 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/attic/vidref Sun May 12 19:02:10 2024 -0700 @@ -0,0 +1,188 @@ +#!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 typing import cast +import logging, optparse, json, base64, os, glob + +from light9.metrics import metrics, metricsRoute + +from rdflib import URIRef +from twisted.internet import reactor, defer +import cyclone.web, cyclone.httpclient, cyclone.websocket + +from cycloneerr import PrettyErrorHandler +from light9 import networking, showconfig +from light9.newtypes import Song +from light9.vidref import videorecorder +from rdfdb.syncedgraph import SyncedGraph + +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) + metrics('live_clients').offset(1) + + def connectionLost(self, reason): + #self.subj.dispose() + metrics('live_clients').offset(-1) + + def onFrame(self, cf: videorecorder.CaptureFrame): + if cf is None: return + + with metrics('live_websocket_frame_fps').time(): + self.sendMessage( + json.dumps({ + 'jpeg': base64.b64encode(cf.asJpeg()).decode('ascii'), + 'description': f't={cf.t}', + })) + + +class SnapshotPic(cyclone.web.StaticFileHandler): + pass + + +class Time(PrettyErrorHandler, cyclone.web.RequestHandler): + + def put(self): + body = json.loads(self.request.body) + t = body['t'] + for listener in TimeStream.time_stream_listeners: + listener.sendMessage(json.dumps({ + 'st': t, + 'song': body['song'], + })) + self.set_status(202) + + +class TimeStream(cyclone.websocket.WebSocketHandler): + time_stream_listeners = [] + + def connectionMade(self, *args, **kwargs): + TimeStream.time_stream_listeners.append(self) + + def connectionLost(self, reason): + TimeStream.time_stream_listeners.remove(self) + + +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 = [] + videoPaths = glob.glob( + os.path.join(videorecorder.songDir(song), b'*.mp4')) + for vid in videoPaths: + 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[-int(self.get_argument('maxClips', '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), + (r'/time/stream', TimeStream), + metricsRoute(), + ], + debug=True, + )) +log.info("serving on %s" % port) + +reactor.run()