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