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
 
@@ -33,7 +33,7 @@ def setCameraParams(c, arg):
 
    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))
 

	
bin/vidref
Show inline comments
 
@@ -9,11 +9,11 @@ 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
 
@@ -50,11 +50,15 @@ class Snapshot(cyclone.web.RequestHandle
 
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))
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
 
@@ -17,7 +17,7 @@ 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")
 
@@ -51,7 +51,7 @@ class Gui(object):
 
                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
light9/vidref/remotepivideo.py
Show inline comments
 
@@ -7,23 +7,34 @@ 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()
 
@@ -34,6 +45,7 @@ class Pipeline(object):
 
        return img
 
        
 
    def _startRequest(self, url):
 
        self._buffer = ''
 
        d = treq.get(url)
 
        d.addCallback(treq.collect, self._dataReceived)
 
        # not sure how to stop this
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)