Changeset - 087f6cbe4b22
[Not reviewed]
default
0 4 2
Drew Perttula - 11 years ago 2014-06-08 09:30:03
drewp@bigasterisk.com
vidrefsetup tool now prepares a url that vidref will use for rpi camera requests
Ignore-this: 3b10bb845aa51811f21f63d4a280d2bd
6 files changed with 280 insertions and 14 deletions:
0 comments (0 inline, 0 general)
bin/picamserve
Show inline comments
 
@@ -24,25 +24,25 @@ except ImportError:
 

	
 
def setCameraParams(c, arg):
 
    res = int(arg('res', 480))
 
    c.resolution = {
 
        480: (640, 480),
 
        1080: (1920, 1080),
 
        1944: (2592, 1944),
 
    }[res]
 
    c.shutter_speed = int(arg('shutter', 50000))
 
    c.exposure_mode = arg('exposure_mode', 'fixedfps')
 
    c.awb_mode = arg('awb_mode', 'off')
 
    c.brightness = int(arg('brightness', 50))
 
    
 
    c.exposure_compensation= int(arg('exposure_compensation', 0))
 
    c.awb_gains = (float(arg('redgain', 1)), float(arg('bluegain', 1)))
 
    c.ISO = int(arg('iso', 250))
 

	
 
def setupCrop(c, arg):
 
    c.crop = (float(arg('x', 0)), float(arg('y', 0)),
 
              float(arg('w', 1)), float(arg('h', 1)))
 
    rw = rh = int(arg('resize', 100))
 
    # width 1920, showing w=.3 of image, resize=100 -> scale is 100/.3*1920
 
    # scl is [ output px / camera px ]
 
    scl1 = rw / (c.crop[2] * c.resolution[0])
 
    scl2 = rh / (c.crop[3] * c.resolution[1])
 
    if scl1 < scl2:
bin/vidref
Show inline comments
 
#!bin/python
 
from run_local import log
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For gtk
 
from twisted.internet import gtk2reactor
 
gtk2reactor.install()
 
from twisted.internet import reactor, defer
 
import gobject
 
gobject.threads_init()
 
import gtk
 
import sys, logging, optparse, json
 
sys.path.append(".")
 
from light9 import networking
 
import cyclone.web, cyclone.httpclient, cyclone.websocket
 
from light9 import networking, showconfig
 
from light9.vidref.main import Gui
 
from light9.vidref.replay import snapshotDir
 
import cyclone.web, cyclone.httpclient, cyclone.websocket
 
from light9.rdfdb.syncedgraph import SyncedGraph
 

	
 
 # 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 if options.verbose else logging.INFO)
 
@@ -41,24 +41,28 @@ class Snapshot(cyclone.web.RequestHandle
 
            
 
            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
 

	
 
        
 
gui = Gui()
 
graph = SyncedGraph("vidref")
 

	
 
gui = Gui(graph)
 

	
 
port = networking.vidref.port
 
reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
    (r'/()', cyclone.web.StaticFileHandler,
 
     {'path': 'light9/vidref', 'default_filename': 'vidref.html'}),
 
    (r'/static/(.*)', cyclone.web.StaticFileHandler, {'path': 'static/'}),
 
    (r'/snapshot', Snapshot),
 
    (r'/snapshot/(.*)', SnapshotPic, {"path": snapshotDir()}),
 
    ], debug=True, gui=gui))
 
log.info("serving on %s" % port)
 

	
 
reactor.run()
 

	
bin/vidrefsetup
Show inline comments
 
new file 100644
 
#!bin/python
 
""" this should be part of vidref, but I haven't worked out sharing
 
camera captures with a continuous camera capture yet """
 

	
 
from run_local import log
 
import sys, optparse, logging, json, subprocess, datetime
 
from dateutil.tz import tzlocal
 
from twisted.internet import reactor, defer
 
import cyclone.web, cyclone.httpclient, cyclone.websocket
 
from rdflib import RDF, URIRef, Literal
 
import pyjade.utils
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
from light9.rdfdb.patch import Patch
 
from light9.namespaces import L9, DCTERMS
 
from light9 import networking, showconfig
 

	
 
from lib.cycloneerr import PrettyErrorHandler
 

	
 
class RedirToCamera(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        return self.redirect(networking.picamserve.path(
 
            'pic?' + self.request.query))
 
        
 
class UrlToCamera(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        self.set_header('Content-Type', 'text/plain')
 
        self.write(networking.picamserve.path('pic'))
 
                   
 
class VidrefCamRequest(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        graph = self.settings.graph
 
        show = showconfig.showUri()
 
        with graph.currentState(tripleFilter=(show, None, None)) as g:
 
            ret = g.value(show, L9['vidrefCamRequest'])
 
            if ret is None:
 
                self.send_error(404)
 
            self.redirect(ret)
 
            
 
    def put(self):
 
        graph = self.settings.graph
 
        show = showconfig.showUri()
 
        graph.patchObject(context=URIRef(show + '/vidrefConfig'),
 
                          subject=show,
 
                          predicate=L9['vidrefCamRequest'],
 
                          newObject=URIRef(self.get_argument('uri')))
 
        self.send_error(202)
 

	
 
def main():
 
    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)
 
    graph = SyncedGraph("vidrefsetup")
 

	
 
    # deliberately conflict with vidref since they can't talk at once to cam
 
    port = networking.vidref.port 
 

	
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
        (r'/pic', RedirToCamera),
 
        (r'/picUrl', UrlToCamera),
 
        (r'/vidrefCamRequest', VidrefCamRequest),
 
        (r'/static/(.*)', cyclone.web.StaticFileHandler, {'path': 'static/'}),
 
        (r'/()', cyclone.web.StaticFileHandler, {'path': 'light9/vidref/', 'default_filename': 'vidref.html'}),
 
        ], debug=True, graph=graph))
 
    log.info("serving on %s" % port)
 
    reactor.run()
 

	
 
main()
light9/vidref/main.py
Show inline comments
 
@@ -8,25 +8,25 @@ gst-launch dv1394src ! dvdemux name=d ! 
 
"""
 
import gobject, logging, traceback
 
import gtk
 
from twisted.python.util import sibpath
 
from light9 import networking
 
from light9.vidref.replay import ReplayViews, framerate
 
from light9.vidref.musictime import MusicTime
 
from light9.vidref.videorecorder import Pipeline
 
from light9.vidref import remotepivideo
 
log = logging.getLogger()
 

	
 
class Gui(object):
 
    def __init__(self):
 
    def __init__(self, graph):
 
        wtree = gtk.Builder()
 
        wtree.add_from_file(sibpath(__file__, "vidref.glade"))
 
        mainwin = wtree.get_object("MainWindow")
 
        mainwin.connect("destroy", gtk.main_quit)
 
        wtree.connect_signals(self)
 
        mainwin.set_size_request(920, 815)
 
        gtk.rc_parse("theme/marble-ice/gtk-2.0/gtkrc")
 

	
 
        self.recordingTo = wtree.get_object('recordingTo')
 
        self.musicScale = wtree.get_object("musicScale")
 
        self.musicScale.connect("value-changed", self.onMusicScaleValue)
 
        # tiny race here if onMusicScaleValue tries to use musicTime right away
 
@@ -42,25 +42,25 @@ class Gui(object):
 
        vid3 = wtree.get_object("vid3")
 

	
 
        if 0:
 
            self.pipeline = Pipeline(
 
                liveVideoXid=vid3.window.xid,
 
                musicTime=self.musicTime,
 
                recordingTo=self.recordingTo)
 
        else:
 
            self.pipeline = remotepivideo.Pipeline(
 
                liveVideo=vid3,
 
                musicTime=self.musicTime,
 
                recordingTo=self.recordingTo,
 
                picsUrl=networking.picamserve.path('pics?res=1080&resize=450&x=0&y=.3&w=1&h=.5&awb_mode=auto&exposure_mode=auto'))
 
                graph=graph)
 

	
 
        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):
light9/vidref/remotepivideo.py
Show inline comments
 
"""
 
like videorecorder.py, but talks to a bin/picamserve instance
 
"""
 
import os, time, logging
 
import gtk
 
import numpy
 
import treq
 
from twisted.internet import defer
 
from light9.vidref.replay import framerate, songDir, takeDir, snapshotDir
 
from light9 import prof
 
from light9 import prof, showconfig
 
from light9.namespaces import L9
 
from PIL import Image
 
from StringIO import StringIO
 
log = logging.getLogger('remotepi')
 

	
 
class Pipeline(object):
 
    def __init__(self, liveVideo, musicTime, recordingTo, picsUrl):
 
    def __init__(self, liveVideo, musicTime, recordingTo, graph):
 
        self.musicTime = musicTime
 
        self.recordingTo = recordingTo
 

	
 
        self.liveVideo = self._replaceLiveVideoWidget(liveVideo)
 
        
 
        self._startRequest(picsUrl)
 
        self._buffer = ''
 
        self._snapshotRequests = []
 
        self.graph = graph
 
        self.graph.addHandler(self.updateCamUrl)
 

	
 
        self._snapshotRequests = []
 

	
 
    def updateCamUrl(self):
 
        show = showconfig.showUri()
 
        self.picsUrl = self.graph.value(show, L9['vidrefCamRequest'])
 
        log.info("picsUrl now %r", self.picsUrl)
 
        if not self.picsUrl:
 
            return
 
        
 
        # this cannot yet survive being called a second time
 
        self._startRequest(str(self.picsUrl.replace('/pic', '/pics')) +
 
                           '&res=1080&resize=450')
 
        
 
    def _replaceLiveVideoWidget(self, liveVideo):
 
        aspectFrame = liveVideo.get_parent()
 
        liveVideo.destroy()
 
        img = gtk.Image()
 
        img.set_visible(True)
 
        #img.set_size_request(320, 240)
 
        aspectFrame.add(img)
 
        return img
 
        
 
    def _startRequest(self, url):
 
        self._buffer = ''
 
        d = treq.get(url)
 
        d.addCallback(treq.collect, self._dataReceived)
 
        # not sure how to stop this
 
        return d
 

	
 
    def _dataReceived(self, chunk):
 
        self._buffer += chunk
 
        if len(self._buffer) < 100:
 
            return
 
        i = self._buffer.index('\n')
 
        size, frameTime = self._buffer[:i].split()
 
        size = int(size)
light9/vidref/vidref.html
Show inline comments
 
new file 100644
 
<!doctype html>
 
<html>
 
  <head>
 
    <title>vidref</title>
 
    <meta charset="utf-8" />
 
    <link rel="stylesheet" href="static/tapmodo-Jcrop-1902fbc/css/jquery.Jcrop.min.css" type="text/css">
 
    <style>
 
     body {
 
       background: black;
 
       color: rgb(170, 170, 170);
 
       font-family: sans-serif; 
 
     }
 
     a {
 
       color: rgb(163, 163, 255);
 
     }
 
     input[type=range] { width: 400px; }
 
     .smallUrl { font-size: 60%; }
 

	
 
     .jcrop-holder {
 
       position: absolute !important;
 
       top: 0 !important;
 
       background-color: initial !important;
 
     }
 
    </style>
 
  </head>
 
  <body>
 
    <h1>video setup</h1>
 
    
 
    <div>Camera view</div>
 
    <div>
 
      <div style="position: relative; display: inline-block">
 
        <img id="cam" src="pic?resize=500&awb_mode=auto&exposure_mode=auto&shutter=100000">
 
        <div id="cover" style="position: absolute; left: 0; top: 0; right: 0; bottom: 0;"></div>
 
      </div>
 
    </div>
 
   
 
    <fieldset>
 
      <legend>set these</legend>
 
      <div><label>shutter <input type="range" min="1" max="100000" data-bind="value: params.shutter, valueUpdate: 'input'"></label></div>
 
      <div><label>brightness <span data-bind="text: params.brightness"></span> <input type="range" min="0" max="100" step="1" data-bind="value: params.brightness, valueUpdate: 'input'"></label></div>
 
      <div><label>exposure_mode
 
        <select data-bind="value: params.exposure_mode">
 
          <option>auto</option>
 
          <option>fireworks</option>
 
          <option>verylong</option>
 
          <option>fixedfps</option>
 
          <option>backlight</option>
 
          <option>antishake</option>
 
          <option>snow</option>
 
          <option>sports</option>
 
          <option>nightpreview</option>
 
          <option>night</option>
 
          <option>beach</option>
 
          <option>spotlight</option>
 
        </select>           
 
      </label></div>
 
      <div><label>exposure_compensation <span data-bind="text: params.exposure_compensation"></span> <input type="range" min="-25" max="25" step="1" data-bind="value: params.exposure_compensation, valueUpdate: 'input'"></label></div>
 
      <div><label>awb_mode
 
        <select data-bind="value: params.awb_mode">
 
          <option>horizon</option>
 
          <option>off</option>
 
          <option>cloudy</option>
 
          <option>shade</option>
 
          <option>fluorescent</option>
 
          <option>tungsten</option>
 
          <option>auto</option>
 
          <option>flash</option>
 
          <option>sunlight</option>
 
          <option>incandescent</option>
 
        </select>
 
      </label></div>
 
      <div><label>redgain <input type="range" min="0" max="8" step=".1" data-bind="value: params.redgain, valueUpdate: 'input'"></label></div>
 
      <div><label>bluegain <input type="range" min="0" max="8" step=".1" data-bind="value: params.bluegain, valueUpdate: 'input'"></label></div>
 
      <div><label>iso <input type="range" min="100" max="800" step="20" list="isos" data-bind="value: params.iso, valueUpdate: 'input'"></label></div>
 
      <datalist id="isos">
 
        <option>100</option>
 
        <option>200</option>
 
        <option>320</option>
 
        <option>400</option>
 
        <option>500</option>
 
        <option>640</option>
 
        <option>800</option>
 
      </datalist>
 
      <div>See <a href="http://picamera.readthedocs.org/en/release-1.4/api.html#picamera.PiCamera.ISO">picamera attribute docs</a></div>
 
    </fieldset>
 

	
 
    <div>Resulting url: <a class="smallUrl" data-bind="attr: {href: currentUrl}, text: currentUrl"></a></div>
 

	
 
    <div>Resulting crop image:</div>
 
    <div><img id="cropped"></div>
 

	
 
    
 
    <script src="static/knockout-3.1.0.js"></script>
 
    <script src="static/jquery-2.1.1.min.js"></script>
 
    <script src="static/underscore-min.js"></script>
 
    <script src="static/tapmodo-Jcrop-1902fbc/js/jquery.Jcrop.js"></script>
 
    <script>
 
     jQuery(function () {
 
       var model = {
 
         baseUrl: ko.observable(),
 
         crop: ko.observable({x: 0, y: 0, w: 1, h: 1}),
 
         params: {
 
           shutter: ko.observable(50000),
 
           exposure_mode: ko.observable('auto'),
 
           awb_mode: ko.observable('auto'),
 
           brightness: ko.observable(50),
 
           redgain: ko.observable(1),
 
           bluegain: ko.observable(1),
 
           iso: ko.observable(250),
 
           exposure_compensation: ko.observable(0),
 
         }
 
       };
 
       model.currentUrl = ko.computed(assembleCamUrlWithCrop);
 

	
 
       function getBaseUrl() {
 
         $.ajax({
 
           url: 'picUrl',
 
           success: model.baseUrl
 
         });
 
       }
 
       
 
       function imageUpdatesForever(model, img, onFirstLoad) {
 
         var everLoaded = false;
 
         function onLoad(ev) {
 
           if (ev.type == 'load' && !everLoaded) {
 
             everLoaded = true;
 
             onFirstLoad();
 
           }
 
           
 
           var src = assembleCamUrl() + '&t=' + (+new Date());
 
           img.src = src;
 

	
 
           $("#cropped").attr({src: assembleCamUrlWithCrop()});
 
         }
 
         img.addEventListener('load', onLoad);
 
         img.addEventListener('error', onLoad);
 
         
 
         onLoad({type: '<startup>'})
 
       }
 
       
 
       function assembleCamUrl() {
 
         if (!model.baseUrl()) {
 
           return '#';
 
         }
 
         return model.baseUrl() + '?resize=500&' + $.param(ko.toJS(model.params));
 
       }
 
       
 
       function assembleCamUrlWithCrop() {
 
         return assembleCamUrl() + '&' + $.param(model.crop());
 
       }
 
       
 
       getBaseUrl();
 
       
 
       imageUpdatesForever(model, document.getElementById('cam'), function onFirstLoad() {
 
         var crop = $('#cover').Jcrop({onChange: function (c) {
 
           var size = this.getBounds();
 
           model.crop({x: c.x / size[0], y: c.y / size[1], w: c.w / size[0], h: c.h / size[1]});
 
         }}, function() {
 
           this.setSelect([50, 50, 100, 100]);
 
         });
 
       });
 

	
 
       var putVidrefCamRequest = _.debounce(
 
           function(uri) {
 
             $.ajax({
 
               type: 'PUT',
 
               url: 'vidrefCamRequest',
 
               data: {uri: uri}
 
             });
 
           }, 1000);
 
       ko.computed(function saver() {
 
         var uri = assembleCamUrlWithCrop();
 
         putVidrefCamRequest(uri);
 
       });
 

	
 
       ko.applyBindings(model);
 
     });
 
    </script>
 
  </body>
 
</html>
0 comments (0 inline, 0 general)