diff --git a/bin/vidref b/bin/vidref
--- a/bin/vidref
+++ b/bin/vidref
@@ -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 @@ log.handlers[0].setLevel(logging.DEBUG i
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()
diff --git a/light9/networking.py b/light9/networking.py
--- a/light9/networking.py
+++ b/light9/networking.py
@@ -38,3 +38,4 @@ dmxServer = ServiceAddress(L9['dmxServer
musicPlayer = ServiceAddress(L9['musicPlayer'])
keyboardComposer = ServiceAddress(L9['keyboardComposer'])
curveCalc = ServiceAddress(L9['curveCalc'])
+vidref = ServiceAddress(L9['vidref'])
diff --git a/light9/vidref/main.py b/light9/vidref/main.py
--- a/light9/vidref/main.py
+++ b/light9/vidref/main.py
@@ -51,6 +51,10 @@ class Gui(object):
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):
diff --git a/light9/vidref/replay.py b/light9/vidref/replay.py
--- a/light9/vidref/replay.py
+++ b/light9/vidref/replay.py
@@ -16,6 +16,9 @@ def takeDir(songDir, startTime):
"""
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 @@ class ReplayView(object):
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):
diff --git a/light9/vidref/videorecorder.py b/light9/vidref/videorecorder.py
--- a/light9/vidref/videorecorder.py
+++ b/light9/vidref/videorecorder.py
@@ -4,8 +4,9 @@ import gst, gobject, time, logging, os,
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 @@ class Pipeline(object):
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 @@ class Pipeline(object):
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 @@ class VideoRecordSink(gst.Element):
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 @@ class VideoRecordSink(gst.Element):
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)
diff --git a/show/dance2013/networking.n3 b/show/dance2013/networking.n3
--- a/show/dance2013/networking.n3
+++ b/show/dance2013/networking.n3
@@ -7,4 +7,5 @@ sh:netHome
:musicPlayer ;
:dmxServer ;
:curveCalc ;
- :keyboardComposer .
\ No newline at end of file
+ :keyboardComposer ;
+ :vidref .
\ No newline at end of file