Mercurial > code > home > repos > light9
changeset 941:1d9547f90737
vidref can take snapshots and serve them back
Ignore-this: 9e89659abc5f36aa56a08391e84e52e
author | drewp@bigasterisk.com |
---|---|
date | Thu, 13 Jun 2013 00:46:52 +0000 |
parents | 183e3afea4cc |
children | dd896321faee |
files | bin/vidref light9/networking.py light9/vidref/main.py light9/vidref/replay.py light9/vidref/videorecorder.py show/dance2013/networking.n3 |
diffstat | 6 files changed, 83 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- a/bin/vidref Thu Jun 13 00:16:42 2013 +0000 +++ b/bin/vidref Thu Jun 13 00:46:52 2013 +0000 @@ -2,14 +2,16 @@ from run_local import log from twisted.internet import gtk2reactor gtk2reactor.install() -from twisted.internet import reactor +from twisted.internet import reactor, defer import gobject gobject.threads_init() import gtk -import sys, logging, optparse +import sys, logging, optparse, json sys.path.append(".") +from light9 import networking from light9.vidref.main import Gui - +from light9.vidref.replay import snapshotDir +import cyclone.web, cyclone.httpclient, cyclone.websocket # find replay dirs correctly. show multiple # replays. trash. reorder/pin. dump takes that are too short; they're @@ -27,8 +29,38 @@ logging.getLogger("restkit.client").setLevel(logging.WARN) +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 as e: + import traceback + traceback.print_exc() + raise + +class SnapshotPic(cyclone.web.StaticFileHandler): + pass + -start=Gui() +gui = Gui() + +port = networking.vidref.port +reactor.listenTCP(port, cyclone.web.Application(handlers=[ + (r'/snapshot', Snapshot), + (r'/snapshot/(.*)', SnapshotPic, {"path": snapshotDir()}), + ], debug=True, gui=gui)) +log.info("serving on %s" % port) reactor.run()
--- a/light9/networking.py Thu Jun 13 00:16:42 2013 +0000 +++ b/light9/networking.py Thu Jun 13 00:46:52 2013 +0000 @@ -38,3 +38,4 @@ musicPlayer = ServiceAddress(L9['musicPlayer']) keyboardComposer = ServiceAddress(L9['keyboardComposer']) curveCalc = ServiceAddress(L9['curveCalc']) +vidref = ServiceAddress(L9['vidref'])
--- a/light9/vidref/main.py Thu Jun 13 00:16:42 2013 +0000 +++ b/light9/vidref/main.py Thu Jun 13 00:46:52 2013 +0000 @@ -51,6 +51,10 @@ gobject.timeout_add(1000 // framerate, self.updateLoop) + + def snapshot(self): + return self.pipeline.snapshot() + def attachLog(self, textBuffer): """write log lines to this gtk buffer""" class ToBuffer(logging.Handler):
--- a/light9/vidref/replay.py Thu Jun 13 00:16:42 2013 +0000 +++ b/light9/vidref/replay.py Thu Jun 13 00:46:52 2013 +0000 @@ -16,6 +16,9 @@ """ return os.path.join(songDir, str(int(startTime))) +def snapshotDir(): + return os.path.expanduser("~/light9-vidref/snapshot") + class ReplayViews(object): """ the whole list of replay windows. parent is the scrolling area for @@ -160,6 +163,7 @@ replayPanel.pack_start(stack, False, False, 0) parent.pack_start(replayPanel, False, False) + log.debug("packed ReplayView %s" % replayPanel) self.replayPanel = replayPanel def destroy(self):
--- a/light9/vidref/videorecorder.py Thu Jun 13 00:16:42 2013 +0000 +++ b/light9/vidref/videorecorder.py Thu Jun 13 00:46:52 2013 +0000 @@ -4,8 +4,9 @@ import gtk import Image from threading import Thread -from Queue import Queue -from light9.vidref.replay import framerate, songDir, takeDir +from twisted.internet import defer +from Queue import Queue, Empty +from light9.vidref.replay import framerate, songDir, takeDir, snapshotDir log = logging.getLogger() class Pipeline(object): @@ -13,7 +14,29 @@ self.musicTime = musicTime self.liveVideoXid = liveVideoXid self.recordingTo = recordingTo - + self.snapshotRequests = Queue() + + try: + os.makedirs(snapshotDir()) + except OSError: + pass + + def snapshot(self): + """ + returns deferred to the path (which is under snapshotDir()) where + we saved the image. This callback comes from another thread, + but I haven't noticed that being a problem yet. + """ + d = defer.Deferred() + def req(frame): + filename = "%s/%s.jpg" % (snapshotDir(), time.time()) + log.debug("received snapshot; saving in %s", filename) + frame.save(filename) + d.callback(filename) + log.debug("requesting snapshot") + self.snapshotRequests.put(req) + return d + def setInput(self, name): sourcePipe = { "auto": "autovideosrc name=src1", @@ -45,7 +68,7 @@ with gtk.gdk.lock: self.recordingTo.set_text(t) self._lastRecText = t - recSink = VideoRecordSink(self.musicTime, setRec) + recSink = VideoRecordSink(self.musicTime, setRec, self.snapshotRequests) self.pipeline.add(recSink) tee = makeElem("tee") @@ -76,9 +99,10 @@ gst.PAD_ALWAYS, gst.caps_new_any()) - def __init__(self, musicTime, updateRecordingTo): + def __init__(self, musicTime, updateRecordingTo, snapshotRequests): gst.Element.__init__(self) self.updateRecordingTo = updateRecordingTo + self.snapshotRequests = snapshotRequests self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink") self.add_pad(self.sinkpad) self.sinkpad.set_chain_function(self.chainfunc) @@ -96,6 +120,13 @@ args = imagesToSave.get() self.saveImg(*args) imagesToSave.task_done() + try: + req = self.snapshotRequests.get(block=False) + except Empty: + pass + else: + req(args[1]) + self.snapshotRequests.task_done() t = Thread(target=imageSaver) t.setDaemon(True)
--- a/show/dance2013/networking.n3 Thu Jun 13 00:16:42 2013 +0000 +++ b/show/dance2013/networking.n3 Thu Jun 13 00:46:52 2013 +0000 @@ -7,4 +7,5 @@ :musicPlayer <http://localhost:8040/>; :dmxServer <http://localhost:8030/>; :curveCalc <http://localhost:8060/>; - :keyboardComposer <http://localhost:8050/> . \ No newline at end of file + :keyboardComposer <http://localhost:8050/>; + :vidref <http://plus:8053/> . \ No newline at end of file