Changeset - 1d9547f90737
[Not reviewed]
default
0 6 0
drewp@bigasterisk.com - 12 years ago 2013-06-13 00:46:52
drewp@bigasterisk.com
vidref can take snapshots and serve them back
Ignore-this: 9e89659abc5f36aa56a08391e84e52e
6 files changed with 83 insertions and 10 deletions:
0 comments (0 inline, 0 general)
bin/vidref
Show inline comments
 
#!bin/python
 
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
 
 # just from seeking
 

	
 
parser = optparse.OptionParser()
 
parser.add_option("-v", "--verbose", action="store_true",
 
                  help="logging.DEBUG")
 
(options, args) = parser.parse_args()
 

	
 

	
 
log.setLevel(logging.DEBUG)
 
# limit the stdout one, but leave debug messages for the gtk logger
 
log.handlers[0].setLevel(logging.DEBUG if options.verbose else logging.WARN)
 
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()
 

	
light9/networking.py
Show inline comments
 
@@ -29,12 +29,13 @@ class ServiceAddress(object):
 

	
 
    @property
 
    def url(self):
 
        return self._url()
 

	
 
    def path(self, more):
 
        return self.url + str(more)
 

	
 
dmxServer = ServiceAddress(L9['dmxServer'])
 
musicPlayer = ServiceAddress(L9['musicPlayer'])
 
keyboardComposer = ServiceAddress(L9['keyboardComposer'])
 
curveCalc = ServiceAddress(L9['curveCalc'])
 
vidref = ServiceAddress(L9['vidref'])
light9/vidref/main.py
Show inline comments
 
@@ -42,24 +42,28 @@ class Gui(object):
 
        self.pipeline = Pipeline(vid3.window.xid, self.musicTime,
 
                                 self.recordingTo)
 

	
 
        vid3.props.width_request = 360
 
        vid3.props.height_request = 220
 
        wtree.get_object("frame1").props.height_request = 220
 
        
 

	
 
        self.pipeline.setInput('v4l') # auto seems to not search for dv
 

	
 
        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):
 
            def emit(self, record):
 
                textBuffer.set_text(record.getMessage())
 

	
 
        h = ToBuffer()
 
        h.setLevel(logging.INFO)
 
        log.addHandler(h)
 

	
 
    def updateLoop(self):
 
        position = self.musicTime.getLatest()
light9/vidref/replay.py
Show inline comments
 
@@ -7,24 +7,27 @@ log = logging.getLogger()
 
framerate = 15
 

	
 
def songDir(song):
 
    safeUri = song.split('://')[-1].replace('/','_')
 
    return os.path.expanduser("~/light9-vidref/play-%s" % safeUri)
 

	
 
def takeDir(songDir, startTime):
 
    """
 
    startTime: unix seconds (str ok)
 
    """
 
    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
 
    these windows to be added
 
    """
 
    def __init__(self, parent):
 
        # today, parent is the vbox the replay windows should appear in
 
        self.parent = parent
 
        self.lastStart = None
 

	
 
        self.views = []
 
     
 
@@ -151,24 +154,25 @@ class ReplayView(object):
 
                pin.props.draw_indicator = True
 
                rows.append(pin)
 

	
 
            stack = gtk.VBox()
 
            stack.set_visible(True)
 
            for r in rows:
 
                stack.add(r)
 
                stack.set_child_packing(r, False, False, 0, gtk.PACK_START)
 
            
 
            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):
 
        self.replayPanel.destroy()
 
        self.enabled = False
 
        
 
    def updatePic(self, position, lag=.2):
 

	
 
        # this should skip updating off-screen widgets! maybe that is
 
        # done by declaring the widget dirty and then reacting to a
 
        # paint message if one comes
 

	
light9/vidref/videorecorder.py
Show inline comments
 
import pygst
 
pygst.require("0.10")
 
import gst, gobject, time, logging, os, traceback
 
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):
 
    def __init__(self, liveVideoXid, musicTime, recordingTo):
 
        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",
 
            "testpattern" : "videotestsrc name=src1",
 
            "dv": "dv1394src name=src1 ! dvdemux ! dvdec",
 
            "v4l": "v4l2src device=/dev/video0 name=src1" ,
 
            }[name]
 

	
 
        cam = (sourcePipe + " ! "
 
              "videorate ! video/x-raw-yuv,framerate=%s/1 ! "
 
              "videoscale ! video/x-raw-yuv,width=640,height=480;video/x-raw-rgb,width=320,height=240 ! "
 
              "videocrop left=160 top=180 right=120 bottom=80 ! "
 
@@ -36,25 +59,25 @@ class Pipeline(object):
 
            self.pipeline.add(e)
 
            return e
 
        
 
        sink = makeElem("xvimagesink")
 
        def setRec(t):
 
            # if you're selecting the text while gtk is updating it,
 
            # you can get a crash in xcb_io
 
            if getattr(self, '_lastRecText', None) == t:
 
                return
 
            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")
 
        
 
        caps = makeElem("capsfilter")
 
        caps.set_property('caps', gst.caps_from_string('video/x-raw-rgb'))
 

	
 
        gst.element_link_many(self.pipeline.get_by_name("vid"), tee, sink)
 
        gst.element_link_many(tee, makeElem("ffmpegcolorspace"), caps, recSink)
 
        sink.set_xwindow_id(self.liveVideoXid)
 
        self.pipeline.set_state(gst.STATE_PLAYING)        
 

	
 
@@ -67,44 +90,52 @@ class Pipeline(object):
 
            self.pipeline.get_by_name("src1").seek_simple(
 
                gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0 * gst.SECOND)
 
        else:
 
            self.pipeline.set_state(gst.STATE_READY)
 
                                                   
 

	
 
class VideoRecordSink(gst.Element):
 
    _sinkpadtemplate = gst.PadTemplate ("sinkpadtemplate",
 
                                        gst.PAD_SINK,
 
                                        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)
 
        self.lastTime = 0
 
        
 
        self.musicTime = musicTime
 

	
 
        self.imagesToSave = Queue()
 
        self.startBackgroundImageSaver(self.imagesToSave)
 
        
 
    def startBackgroundImageSaver(self, imagesToSave):
 
        """do image saves in another thread to not block gst"""
 
        def imageSaver():
 
            while True:
 
                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)
 
        t.start()
 

	
 
    def chainfunc(self, pad, buffer):
 
        position = self.musicTime.getLatest()
 

	
 
        if not position['song']:
 
            print "no song" # todo: this prints too much when the player has no song
 
            return gst.FLOW_OK
 

	
show/dance2013/networking.n3
Show inline comments
 
@prefix : <http://light9.bigasterisk.com/> .
 
@prefix show: <http://light9.bigasterisk.com/show/> .
 
@prefix sh: <http://light9.bigasterisk.com/show/dance2013/> .
 

	
 
show:dance2013 :networking sh:netHome .
 
sh:netHome  
 
  :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
0 comments (0 inline, 0 general)