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 278 insertions and 12 deletions:
0 comments (0 inline, 0 general)
bin/picamserve
Show inline comments
 
@@ -30,13 +30,13 @@ def setCameraParams(c, arg):
 
        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)))
bin/vidref
Show inline comments
 
@@ -6,17 +6,17 @@ 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()
 
@@ -47,17 +47,21 @@ class Snapshot(cyclone.web.RequestHandle
 
            traceback.print_exc()
 
            raise
 

	
 
class SnapshotPic(cyclone.web.StaticFileHandler):
 
    pass
 

	
 
graph = SyncedGraph("vidref")
 
        
 
gui = Gui()
 
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
 
@@ -14,13 +14,13 @@ from light9.vidref.replay import ReplayV
 
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)
 
@@ -48,13 +48,13 @@ class Gui(object):
 
                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
 
        
 

	
light9/vidref/remotepivideo.py
Show inline comments
 
@@ -4,39 +4,51 @@ like videorecorder.py, but talks to a bi
 
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):
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)