Mercurial > code > home > repos > light9
changeset 1096:087f6cbe4b22
vidrefsetup tool now prepares a url that vidref will use for rpi camera requests
Ignore-this: 3b10bb845aa51811f21f63d4a280d2bd
author | Drew Perttula <drewp@bigasterisk.com> |
---|---|
date | Sun, 08 Jun 2014 09:30:03 +0000 |
parents | f6c5b9b94dae |
children | f7618f29bb89 |
files | bin/picamserve bin/vidref bin/vidrefsetup light9/vidref/main.py light9/vidref/remotepivideo.py light9/vidref/vidref.html |
diffstat | 6 files changed, 280 insertions(+), 14 deletions(-) [+] |
line wrap: on
line diff
--- a/bin/picamserve Sun Jun 08 09:29:45 2014 +0000 +++ b/bin/picamserve Sun Jun 08 09:30:03 2014 +0000 @@ -33,7 +33,7 @@ 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))
--- a/bin/vidref Sun Jun 08 09:29:45 2014 +0000 +++ b/bin/vidref Sun Jun 08 09:30:03 2014 +0000 @@ -9,11 +9,11 @@ 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 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))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/vidrefsetup Sun Jun 08 09:30:03 2014 +0000 @@ -0,0 +1,70 @@ +#!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()
--- a/light9/vidref/main.py Sun Jun 08 09:29:45 2014 +0000 +++ b/light9/vidref/main.py Sun Jun 08 09:30:03 2014 +0000 @@ -17,7 +17,7 @@ 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 @@ 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
--- a/light9/vidref/remotepivideo.py Sun Jun 08 09:29:45 2014 +0000 +++ b/light9/vidref/remotepivideo.py Sun Jun 08 09:30:03 2014 +0000 @@ -7,23 +7,34 @@ 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 @@ return img def _startRequest(self, url): + self._buffer = '' d = treq.get(url) d.addCallback(treq.collect, self._dataReceived) # not sure how to stop this
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/vidref/vidref.html Sun Jun 08 09:30:03 2014 +0000 @@ -0,0 +1,180 @@ +<!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>