Changeset - 3c523c71da29
[Not reviewed]
default
! ! !
Drew Perttula - 6 years ago 2019-05-25 12:10:51
drewp@bigasterisk.com
pyflakes cleanups and some refactors
Ignore-this: f7372e678699175feb4e628eee3d768c
61 files changed with 286 insertions and 291 deletions:
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
#!bin/python
 
from run_local import log
 
from twisted.internet import reactor
 
import web, _thread, sys, optparse, logging
 
import sys, optparse, logging
 
from rdflib import URIRef
 
import gi
 
gi.require_version('Gst', '1.0')
 
gi.require_version('Gtk', '3.0')
 

	
 
from light9.ascoltami.player import Player
 
from light9.ascoltami.playlist import Playlist, NoSuchSong
 
from light9.ascoltami.webapp import makeWebApp, songUri, songLocation
 
from light9 import networking, showconfig
 

	
 
from gi.repository import GObject, Gst, Gtk
 
from gi.repository import GObject, Gst
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self, graph, show):
 
        self.graph = graph
 
        self.player = Player(onEOS=self.onEOS)
 
        self.show = show
 
        self.playlist = Playlist.fromShow(graph, show)
 

	
 
    def onEOS(self, song):
 
        self.player.pause()
bin/captureDevice
Show inline comments
 
@@ -14,24 +14,25 @@ from greplin import scales
 

	
 
from run_local import log
 
from cycloneerr import PrettyErrorHandler
 

	
 
from light9.namespaces import L9, RDF
 
from light9 import networking, showconfig
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9.paint.capture import writeCaptureDescription
 
from light9.greplin_cyclone import StatsForCyclone
 
from light9.effect.settings import DeviceSettings
 
from light9.collector.collector_client import sendToCollector
 
from rdfdb.patch import Patch
 
from light9.zmqtransport import parseJsonMessage
 

	
 
stats = scales.collection('/webServer', scales.PmfStat('setAttr'))
 

	
 

	
 
class Camera(object):
 

	
 
    def __init__(self, imageUrl):
 
        self.imageUrl = imageUrl
 

	
 
    def takePic(self, uri, writePath):
 
        log.info('takePic %s', uri)
 
        return treq.get(
 
@@ -165,25 +166,26 @@ class Attrs(PrettyErrorHandler, cyclone.
 

	
 
def launch(graph):
 

	
 
    cap = Capture(graph, dev=L9['device/aura5'])
 
    reactor.listenTCP(networking.captureDevice.port,
 
                      cyclone.web.Application(handlers=[
 
                          (r'/()', cyclone.web.StaticFileHandler, {
 
                              "path": "light9/web",
 
                              "default_filename": "captureDevice.html"
 
                          }),
 
                          (r'/stats', StatsForCyclone),
 
                      ]),
 
                      interface='::')
 
                      interface='::',
 
                      cap=cap)
 
    log.info('serving http on %s', networking.captureDevice.port)
 

	
 

	
 
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)
 

	
bin/collector
Show inline comments
 
#!bin/python
 
"""
 
Collector receives device attrs from multiple senders, combines
 
them, and sends output attrs to hardware. The combining part has
 
custom code for some attributes.
 

	
 
Input can be over http or zmq.
 
"""
 

	
 
from run_local import log
 

	
 
from rdflib import URIRef, Literal
 
from twisted.internet import reactor, utils
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPullConnection
 
import json
 
import logging
 
import optparse
 
import time
 
import traceback
 
import cyclone.web, cyclone.websocket
 
from greplin import scales
 

	
 
from cycloneerr import PrettyErrorHandler
 
from light9.collector.output import EnttecDmx, Udmx, DummyOutput
 
from light9 import networking
 
from light9.collector.collector import Collector
 
from light9.namespaces import L9
 
from light9 import networking
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9.collector.weblisteners import WebListeners
 
from light9.greplin_cyclone import StatsForCyclone
 

	
 

	
 
def parseJsonMessage(msg):
 
    body = json.loads(msg)
 
    settings = []
 
    for device, attr, value in body['settings']:
 
        if isinstance(value, str) and value.startswith('http'):
 
            value = URIRef(value)
 
        else:
 
            value = Literal(value)
 
        settings.append((URIRef(device), URIRef(attr), value))
 
    return body['client'], body['clientSession'], settings, body['sendTime']
 

	
 

	
 
def startZmq(port, collector):
 
    stats = scales.collection('/zmqServer', scales.PmfStat('setAttr'))
 

	
 
    zf = ZmqFactory()
 
    addr = 'tcp://*:%s' % port
 
    log.info('creating zmq endpoint at %r', addr)
 
    e = ZmqEndpoint('bind', addr)
 

	
 
    class Pull(ZmqPullConnection):
 
        #highWaterMark = 3
 
        def onPull(self, message):
 
            with stats.setAttr.time():
 
                # todo: new compressed protocol where you send all URIs up
 
                # front and then use small ints to refer to devices and
 
                # attributes in subsequent requests.
 
                client, clientSession, settings, sendTime = parseJsonMessage(
 
                    message[0])
 
                collector.setAttrs(client, clientSession, settings, sendTime)
 

	
 
    s = Pull(zf, e)
 

	
 

	
 
class WebListeners(object):
 

	
 
    def __init__(self):
 
        self.clients = []
 
        self.pendingMessageForDev = {}  # dev: (attrs, outputmap)
 
        self.lastFlush = 0
 

	
 
    def addClient(self, client):
 
        self.clients.append([client, {}])  # seen = {dev: attrs}
 
        log.info('added client %s %s', len(self.clients), client)
 
from light9.namespaces import L9
 
from light9.zmqtransport import parseJsonMessage, startZmq
 
from rdfdb.syncedgraph import SyncedGraph
 

	
 
    def delClient(self, client):
 
        self.clients = [[c, t] for c, t in self.clients if c != client]
 
        log.info('delClient %s, %s left', client, len(self.clients))
 

	
 
    def outputAttrsSet(self, dev, attrs, outputMap):
 
        """called often- don't be slow"""
 

	
 
        self.pendingMessageForDev[dev] = (attrs, outputMap)
 
        try:
 
            self._flush()
 
        except Exception:
 
            traceback.print_exc()
 
            raise
 

	
 
    def _flush(self):
 
        now = time.time()
 
        if now < self.lastFlush + .05 or not self.clients:
 
            return
 
        self.lastFlush = now
 

	
 
        while self.pendingMessageForDev:
 
            dev, (attrs, outputMap) = self.pendingMessageForDev.popitem()
 

	
 
            msg = None  # lazy, since makeMsg is slow
 
from light9.collector.output import EnttecDmx, Udmx, DummyOutput  # noqa
 

	
 
            # this omits repeats, but can still send many
 
            # messages/sec. Not sure if piling up messages for the browser
 
            # could lead to slowdowns in the real dmx output.
 
            for client, seen in self.clients:
 
                if seen.get(dev) == attrs:
 
                    continue
 
                if msg is None:
 
                    msg = self.makeMsg(dev, attrs, outputMap)
 

	
 
                seen[dev] = attrs
 
                client.sendMessage(msg)
 

	
 
    def makeMsg(self, dev, attrs, outputMap):
 
        attrRows = []
 
        for attr, val in list(attrs.items()):
 
            output, index = outputMap[(dev, attr)]
 
            attrRows.append({
 
                'attr': attr.rsplit('/')[-1],
 
                'val': val,
 
                'chan': (output.shortId(), index + 1)
 
            })
 
        attrRows.sort(key=lambda r: r['chan'])
 
        for row in attrRows:
 
            row['chan'] = '%s %s' % (row['chan'][0], row['chan'][1])
 

	
 
        msg = json.dumps({'outputAttrsSet': {
 
            'dev': dev,
 
            'attrs': attrRows
 
        }},
 
                         sort_keys=True)
 
        return msg
 

	
 

	
 
class Updates(cyclone.websocket.WebSocketHandler):
 

	
 
    def connectionMade(self, *args, **kwargs):
 
        log.info('socket connect %s', self)
 
        self.settings.listeners.addClient(self)
 

	
 
    def connectionLost(self, reason):
 
        self.settings.listeners.delClient(self)
 

	
 
    def messageReceived(self, message):
 
@@ -154,28 +52,26 @@ class Attrs(PrettyErrorHandler, cyclone.
 
        with stats.setAttr.time():
 
            client, clientSession, settings, sendTime = parseJsonMessage(
 
                self.request.body)
 
            self.settings.collector.setAttrs(client, clientSession, settings,
 
                                             sendTime)
 
            self.set_status(202)
 

	
 

	
 
def launch(graph, doLoadTest=False):
 
    try:
 
        # todo: drive outputs with config files
 
        outputs = [
 
            # EnttecDmx(L9['output/dmxA/'], '/dev/dmx3', 80),
 
            Udmx(L9['output/dmxA/'], bus=5, numChannels=80),
 
            #DummyOutput(L9['output/dmxA/'], 80),
 
            Udmx(L9['output/dmxB/'], bus=7, numChannels=500),
 
            DummyOutput(L9['output/dmxA/'], 80),
 
            DummyOutput(L9['output/dmxB/'], 510),
 
        ]
 
    except Exception:
 
        log.error("setting up outputs:")
 
        traceback.print_exc()
 
        raise
 
    listeners = WebListeners()
 
    c = Collector(graph, outputs, listeners)
 

	
 
    startZmq(networking.collectorZmq.port, c)
 

	
 
    reactor.listenTCP(networking.collector.port,
 
                      cyclone.web.Application(handlers=[
bin/collector_loadtest.py
Show inline comments
 
import sys
 
sys.path.append('bin')
 
from run_local import log
 
from light9.collector.collector_client import sendToCollector, sendToCollectorZmq
 
from light9.collector.collector_client import sendToCollector
 
from light9.namespaces import L9, DEV
 
from twisted.internet import reactor
 
import time
 
import logging
 
log.setLevel(logging.DEBUG)
 

	
 

	
 
def loadTest():
 
    print("scheduling loadtest")
 
    n = 2500
 
    times = [None] * n
 
    session = "loadtest%s" % time.time()
bin/effecteval
Show inline comments
 
#!bin/python
 

	
 
from run_local import log
 
from twisted.internet import reactor
 
from twisted.internet.defer import inlineCallbacks, returnValue
 
import cyclone.web, cyclone.websocket, cyclone.httpclient
 
import sys, optparse, logging, subprocess, json, itertools
 
import sys, optparse, logging, json, itertools
 
from rdflib import URIRef, Literal
 

	
 
sys.path.append('/usr/lib/pymodules/python2.7/')  # for numpy, on rpi
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For numpy
 
from light9 import networking, showconfig
 
from light9.effecteval.effect import EffectNode
 
from light9.effect.edit import getMusicStatus, songNotePatch
 
from light9.effecteval.effectloop import makeEffectLoop
 
from light9.greplin_cyclone import StatsForCyclone
 
from light9.namespaces import L9
 
from rdfdb.patch import Patch
 
from rdfdb.syncedgraph import SyncedGraph
 
from greplin import scales
 

	
 
from cycloneerr import PrettyErrorHandler
 
from light9.coffee import StaticCoffee
 

	
 

	
 
class EffectEdit(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        self.set_header('Content-Type', 'text/html')
 
        self.write(open("light9/effecteval/effect.html").read())
 

	
 
    def delete(self):
 
        graph = self.settings.graph
 
        uri = URIRef(self.get_argument('uri'))
 
        with graph.currentState(tripleFilter=(None, L9['effect'], uri)) as g:
 
@@ -192,27 +193,27 @@ class EffectEval(PrettyErrorHandler, cyc
 

	
 
        node = EffectNode(self.settings.graph, uri)
 
        outSub = node.eval(songTime)
 
        self.write(json.dumps(outSub.get_dmx_list()))
 

	
 

	
 
# Completely not sure where the effect background loop should
 
# go. Another process could own it, and get this request repeatedly:
 
class SongEffectsEval(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        song = URIRef(self.get_argument('song'))
 
        effects = effectsForSong(self.settings.graph, song)
 
        effects = effectsForSong(self.settings.graph, song) # noqa
 
        raise NotImplementedError
 
        self.write(maxDict(effectDmxDict(e) for e in effects))
 
        self.write(maxDict(effectDmxDict(e) for e in effects)) # noqa
 
        # return dmx dict for all effects in the song, already combined
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self, show, outputWhere):
 
        self.show = show
 
        self.outputWhere = outputWhere
 
        self.graph = SyncedGraph(networking.rdfdb.url, "effectEval")
 
        self.graph.initiallySynced.addCallback(self.launch).addErrback(
 
            log.error)
 

	
 
@@ -250,37 +251,24 @@ class App(object):
 
            (r'/effect/eval', EffectEval),
 
            (r'/songEffects', SongEffects),
 
            (r'/songEffects/eval', SongEffectsEval),
 
            (r'/stats', StatsForCyclone),
 
        ],
 
                                                  debug=True,
 
                                                  graph=self.graph,
 
                                                  stats=self.stats)
 
        reactor.listenTCP(networking.effectEval.port, self.cycloneApp)
 
        log.info("listening on %s" % networking.effectEval.port)
 

	
 

	
 
class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def initialize(self, src):
 
        super(StaticCoffee, self).initialize()
 
        self.src = src
 

	
 
    def get(self):
 
        self.set_header('Content-Type', 'application/javascript')
 
        self.write(
 
            subprocess.check_output(
 
                ['/usr/bin/coffee', '--compile', '--print', self.src]))
 

	
 

	
 
if __name__ == "__main__":
 
    parser = optparse.OptionParser()
 
    parser.add_option(
 
        '--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
        default=showconfig.showUri())
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    parser.add_option("--twistedlog",
 
                      action="store_true",
bin/homepageConfig
Show inline comments
 
#!bin/python
 
from run_local import log
 
from rdflib import RDF, URIRef
 
from light9 import networking, showconfig
 
from light9 import showconfig
 
from light9.namespaces import L9
 
from urllib.parse import urlparse
 
from urllib.parse import splitport
 

	
 
from rdfdb.syncedgraph import SyncedGraph
 
from twisted.internet import reactor
 

	
 
log.info('generating config')
 
graph = showconfig.getGraph()
 

	
 
netHome = graph.value(showconfig.showUri(), L9['networking'])
 
webServer = graph.value(netHome, L9['webServer'])
 
if not webServer:
 
    raise ValueError('no %r :webServer' % netHome)
 
print("listen %s;" % splitport(urlparse(webServer).netloc)[1])
 

	
 

	
 
def location(path, server):
 
    print("""
 
    location /%(path)s/ {
bin/inputdemo
Show inline comments
 
#!bin/python
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For gtk
 
from twisted.internet import gtk3reactor
 
gtk3reactor.install()
 
from twisted.internet import reactor
 
from rdflib import URIRef
 
import optparse, logging, urllib.request, urllib.parse, urllib.error, time
 
import optparse, logging, time
 
from gi.repository import Gtk
 
from run_local import log
 
from light9 import showconfig, networking
 
from light9 import networking
 
from light9 import clientsession
 
from rdfdb.syncedgraph import SyncedGraph
 
import cyclone.httpclient
 
from light9.curvecalc.client import sendLiveInputPoint
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self):
 
        parser = optparse.OptionParser()
 
        parser.set_usage("%prog [opts] [curve uri]")
 
        parser.add_option("--debug", action="store_true", help="log at DEBUG")
 
        clientsession.add_option(parser)
 
        opts, args = parser.parse_args()
 

	
bin/inputquneo
Show inline comments
 
#!bin/python
 
"""
 
read Quneo midi events, write to curvecalc and maybe to effects
 
"""
 

	
 
from run_local import log
 
import logging, urllib.request, urllib.parse, urllib.error
 
import cyclone.web, cyclone.httpclient
 
from rdflib import URIRef
 
from twisted.internet import reactor, task
 
from light9.curvecalc.client import sendLiveInputPoint
 
from light9.namespaces import L9, RDF, RDFS
 
from light9.namespaces import L9, RDF
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9 import networking
 

	
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For pygame
 
import pygame.midi
 

	
 
curves = {
 
    23: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-2'),
 
    24: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-3'),
 
    25: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-4'),
 
    6: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-5'),
 
@@ -100,15 +100,16 @@ class WatchMidi(object):
 

	
 
                    if status == 128:  #noteoff
 
                        for d in group:
 
                            sendLiveInputPoint(curves[d], 0)
 
                        self.noteIsOn[group] = False
 

	
 

	
 
def main():
 
    log.setLevel(logging.DEBUG)
 
    graph = SyncedGraph(networking.rdfdb.url, "inputQuneo")
 
    wm = WatchMidi(graph)
 
    reactor.run()
 
    del wm
 

	
 

	
 
main()
bin/keyboardcomposer
Show inline comments
 
@@ -333,34 +333,34 @@ class KeyboardComposer(tk.Frame, SubClie
 
                                anchor='c')
 
            keylabel.pack(side=tk.LEFT, expand=1, fill=tk.X)
 
            col += 1
 

	
 
        keyhintrow.pack(fill=tk.X, expand=0)
 
        self.keyhints = keyhintrow
 

	
 
    def setup_key_nudgers(self, tkobject):
 
        for d, keys in list(nudge_keys.items()):
 
            for key in keys:
 
                # lowercase makes full=0
 
                keysym = "<KeyPress-%s>" % key
 
                tkobject.bind(keysym, \
 
                    lambda evt, num=keys.index(key), d=d: \
 
                        self.got_nudger(num, d))
 
                tkobject.bind(keysym,
 
                              lambda evt, num=keys.index(key), d=d: self.
 
                              got_nudger(num, d))
 

	
 
                # uppercase makes full=1
 
                keysym = "<KeyPress-%s>" % key.upper()
 
                keysym = keysym.replace('SEMICOLON', 'colon')
 
                tkobject.bind(keysym, \
 
                    lambda evt, num=keys.index(key), d=d: \
 
                        self.got_nudger(num, d, full=1))
 
                tkobject.bind(keysym,
 
                              lambda evt, num=keys.index(key), d=d: self.
 
                              got_nudger(num, d, full=1))
 

	
 
        # Row changing:
 
        # Page dn, C-n, and ] do down
 
        # Page up, C-p, and ' do up
 
        for key in '<Prior> <Next> <Control-n> <Control-p> ' \
 
                   '<Key-bracketright> <Key-apostrophe>'.split():
 
            tkobject.bind(key, self.change_row_cb)
 

	
 
    def change_row_cb(self, event):
 
        diff = 1
 
        if event.keysym in ('Prior', 'p', 'bracketright'):
 
            diff = -1
bin/listsongs
Show inline comments
 
#!bin/python
 
"""for completion, print the available song uris on stdout
 

	
 
in .zshrc:
 

	
 
function _songs { local expl;  _description files expl 'songs';  compadd "$expl[@]" - `${LIGHT9_SHOW}/../../bin/listsongs` }
 
compdef _songs curvecalc
 
"""
 

	
 
from run_local import log
 
from run_local import log  # noqa
 
from twisted.internet import reactor
 
from rdflib import RDF
 
from light9 import networking
 
from light9.namespaces import L9
 
from rdfdb.syncedgraph import SyncedGraph
 

	
 
graph = SyncedGraph(networking.rdfdb.url, "listsongs")
 

	
 

	
 
@graph.initiallySynced.addCallback
 
def printSongs(result):
 
    with graph.currentState() as current:
bin/musicPad
Show inline comments
 
#!bin/python
 
"""
 
rewrite all the songs with silence at the start and end
 
"""
 
import sys, wave, logging, os
 
sys.path.append(".")
 
from light9 import showconfig
 
from light9.namespaces import L9
 
from light9.ascoltami.playlist import Playlist
 
logging.basicConfig(level=logging.INFO)
 
log = logging.getLogger()
 

	
 
introPad = 4
 
postPad = 9  # 5 + autostop + 4
 

	
 
playlist = Playlist.fromShow(showconfig.getGraph(), showconfig.showUri())
 
for p in playlist.allSongPaths():
 
    log.info("read %s", p)
 
    inputWave = wave.open(p, 'r')
 

	
bin/musictime
Show inline comments
 
#!/usr/bin/env python
 
import run_local
 
import run_local  # noqa
 
import light9.networking
 

	
 
import tkinter as tk
 
import time
 
import restkit, jsonlib
 

	
 

	
 
class MusicTime:
 

	
 
    def __init__(self, url):
 
        self.player = restkit.Resource(url)
 

	
bin/paintserver
Show inline comments
 
@@ -4,25 +4,25 @@ from run_local import log
 
import json
 
from twisted.internet import reactor
 
from light9.greplin_cyclone import StatsForCyclone
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9 import networking, showconfig
 
from greplin import scales
 
import optparse, sys, logging
 
import cyclone.web
 
from rdflib import URIRef
 
from light9 import clientsession
 
import light9.paint.solve
 
from cycloneerr import PrettyErrorHandler
 
from light9.namespaces import RDF, L9, DEV
 
from light9.namespaces import L9, DEV
 
import imp
 

	
 

	
 
class Solve(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def post(self):
 
        painting = json.loads(self.request.body)
 
        with self.settings.stats.solve.time():
 
            img = self.settings.solver.draw(painting)
 
            sample, sampleDist = self.settings.solver.bestMatch(
 
                img, device=DEV['aura2'])
 
            with self.settings.graph.currentState() as g:
bin/picamserve
Show inline comments
 
@@ -153,25 +153,24 @@ class Pics(cyclone.web.RequestHandler):
 
            c = self.settings.camera
 
            setCameraParams(c, self.get_argument)
 
            resize = setupCrop(c, self.get_argument)
 

	
 
            self.running = True
 
            log.info("connection open from %s", self.request.remote_ip)
 
            fpsReport = FpsReport()
 

	
 
            def onFrame(frameTime, frame):
 
                if not self.running:
 
                    raise StopIteration
 

	
 
                now = time.time()
 
                self.write("%s %s\n" % (len(frame), frameTime))
 
                self.write(frame)
 
                self.flush()
 

	
 
                fpsReport.frame()
 

	
 
            # another camera request coming in at the same time breaks
 
            # the server. it would be nice if this request could
 
            # let-go-and-reopen when it knows about another request
 
            # coming in
 
            yield captureContinuousAsync(c, resize, onFrame)
 
        except Exception:
bin/rdfdb
Show inline comments
 
#!bin/python
 
import run_local
 
import run_local  # noqa
 
import os
 
from light9 import networking, showconfig
 
import rdfdb.service
 

	
 
rdfdb.service.main(
 
    dirUriMap={
 
        os.environ['LIGHT9_SHOW'].rstrip('/') + '/': showconfig.showUri() + '/'
 
    },
 
    prefixes={
 
        'show': showconfig.showUri() + '/',
 
        '': 'http://light9.bigasterisk.com/',
 
        'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
bin/subserver
Show inline comments
 
#!bin/python
 
"""
 
live web display of all existing subs with pictures, mainly for
 
dragging them into CC or Timeline
 
"""
 
from run_local import log
 
import sys, optparse, logging, json, subprocess, datetime
 
import 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
 
from rdflib import URIRef, Literal
 
import pyjade.utils
 
from rdfdb.syncedgraph import SyncedGraph
 
from rdfdb.patch import Patch
 
from light9.namespaces import L9, DCTERMS
 
from light9 import networking, showconfig
 

	
 
from cycloneerr import PrettyErrorHandler
 

	
 

	
 
class Static(PrettyErrorHandler, cyclone.web.StaticFileHandler):
 

	
 
    def get(self, path, *args, **kw):
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
 
import cyclone.web, cyclone.httpclient, cyclone.websocket
 
from light9 import networking, showconfig
 
from light9 import networking
 
from light9.vidref.main import Gui
 
from light9.vidref.replay import snapshotDir
 
from 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()
 

	
 
@@ -33,25 +32,25 @@ class Snapshot(cyclone.web.RequestHandle
 
        # 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:
 
        except Exception:
 
            import traceback
 
            traceback.print_exc()
 
            raise
 

	
 

	
 
class SnapshotPic(cyclone.web.StaticFileHandler):
 
    pass
 

	
 

	
 
class Time(cyclone.web.RequestHandler):
 

	
 
    def put(self):
bin/vidrefsetup
Show inline comments
 
#!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 optparse, logging
 
from twisted.internet import reactor
 
import cyclone.web, cyclone.httpclient, cyclone.websocket
 
from rdflib import RDF, URIRef, Literal
 
import pyjade.utils
 
from rdflib import URIRef
 
from rdfdb.syncedgraph import SyncedGraph
 
from rdfdb.patch import Patch
 
from light9.namespaces import L9, DCTERMS
 
from light9.namespaces import L9
 
from light9 import networking, showconfig
 

	
 
from cycloneerr import PrettyErrorHandler
 

	
 

	
 
class RedirToCamera(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        return self.redirect(
 
            networking.picamserve.path('pic?' + self.request.query))
 

	
 

	
bin/wavecurve
Show inline comments
 
#!bin/python
 
import optparse
 
import run_local
 
from run_local import log
 
from light9.wavepoints import simp
 

	
 

	
 
def createCurve(inpath, outpath, t):
 
    print("reading %s, writing %s" % (inpath, outpath))
 
    points = simp(inpath.replace('.ogg', '.wav'), seconds_per_average=t)
 

	
 
    f = file(outpath, 'w')
 
    f = open(outpath, 'w')
 
    for time_val in points:
 
        print("%s %s" % time_val, file=f)
 
    log.info(r'Wrote {outpath}')
 

	
 

	
 
parser = optparse.OptionParser(usage="""%prog inputSong.wav outputCurve
 

	
 
You probably just want -a
 

	
 
""")
 
parser.add_option("-t",
 
                  type="float",
 
                  default=.01,
 
                  help="seconds per sample (default .01, .07 is smooth)")
 
parser.add_option("-a",
 
                  "--all",
 
                  action="store_true",
 
                  help="make standard curves for all songs")
 
options, args = parser.parse_args()
 

	
 
if options.all:
 
    from light9 import showconfig
 
    from light9.namespaces import L9
 
    from rdflib import RDF
 
    from light9.ascoltami.playlist import Playlist
 
    graph = showconfig.getGraph()
 

	
 
    playlist = Playlist.fromShow(showconfig.getGraph(), showconfig.showUri())
 
    for song in playlist.allSongs():
 
        inpath = showconfig.songOnDisk(song)
 
        for curveName, t in [('music', .01), ('smooth_music', .07)]:
 
            outpath = showconfig.curvesDir() + "/%s-%s" % (
 
                showconfig.songFilenameFromURI(song), curveName)
 
            createCurve(inpath, outpath, t)
 
else:
 
    inpath, outpath = args
lib/ipython_view.py
Show inline comments
 
@@ -12,29 +12,30 @@ All rights reserved. This program and th
 
available under the terms of the BSD which accompanies this distribution, and 
 
is available at U{http://www.opensource.org/licenses/bsd-license.php}
 
"""
 
# this file is a modified version of source code from the Accerciser project
 
# http://live.gnome.org/accerciser
 
 
 
from gi.repository import Gtk
 
from gi.repository import Gdk
 
import re
 
import sys
 
import os
 
from gi.repository import Pango
 
from StringIO import StringIO
 
from io import StringIO
 
from functools import reduce
 
 
 
try:
 
	import IPython
 
except Exception,e:
 
except Exception as e:
 
	raise "Error importing IPython (%s)" % str(e)
 
 
 
ansi_colors =  {'0;30': 'Black',
 
                '0;31': 'Red',
 
                '0;32': 'Green',
 
                '0;33': 'Brown',
 
                '0;34': 'Blue',
 
                '0;35': 'Purple',
 
                '0;36': 'Cyan',
 
                '0;37': 'LightGray',
 
                '1;30': 'DarkGray',
 
                '1;31': 'DarkRed',
 
@@ -139,29 +140,29 @@ class IterableIPShell:
 
    else:
 
      completed = line
 
    return completed, possibilities
 
 
 
  def _commonPrefix(self, str1, str2):
 
    for i in range(len(str1)):
 
      if not str2.startswith(str1[:i+1]):
 
        return str1[:i]
 
    return str1
 
 
 
  def shell(self, cmd,verbose=0,debug=0,header=''):
 
    stat = 0
 
    if verbose or debug: print header+cmd
 
    if verbose or debug: print(header+cmd)
 
    # flush stdout so we don't mangle python's buffering
 
    if not debug:
 
      input, output = os.popen4(cmd)
 
      print output.read()
 
      print(output.read())
 
      output.close()
 
      input.close()
 
 
 
class ConsoleView(Gtk.TextView):
 
  def __init__(self):
 
    Gtk.TextView.__init__(self)
 
    self.modify_font(Pango.FontDescription('Mono'))
 
    self.set_cursor_visible(True)
 
    self.text_buffer = self.get_buffer()
 
    self.mark = self.text_buffer.create_mark('scroll_mark', 
 
                                             self.text_buffer.get_end_iter(),
 
                                             False)
light9/Fadable.py
Show inline comments
 
# taken from SnackMix -- now that's reusable code
 
from tkinter.tix import *
 
import time
 

	
 

	
 
class Fadable:
 
    """Fading mixin: must mix in with a Tk widget (or something that has
 
    'after' at least) This is currently used by VolumeBox and MixerTk.
 
    It's probably too specialized to be used elsewhere, but could possibly
 
    work with an Entry or a Meter, I guess.  (Actually, this is used by
 
    KeyboardComposer and KeyboardRecorder now too.)
 

	
 
    var is a Tk variable that should be used to set and get the levels.
 
    If use_fades is true, it will use fades to move between levels.
light9/FlyingFader.py
Show inline comments
 
from tkinter.tix import *
 
from time import time, sleep
 

	
 
from tkinter import tix
 
from time import time
 

	
 
class Mass:
 

	
 
    def __init__(self):
 
        self.x = 0  # position
 
        self.xgoal = 0  # position goal
 

	
 
        self.v = 0  # velocity
 
        self.maxspeed = .8  # maximum speed, in position/second
 
        self.maxaccel = 3  # maximum acceleration, in position/second^2
 
        self.eps = .03  # epsilon - numbers within this much are considered the same
 

	
 
@@ -56,72 +55,70 @@ class Mass:
 
        self.v = min(max(self.v, -self.maxspeed),
 
                     self.maxspeed)  # clamp velocity
 

	
 
        #print "x=%+.03f v=%+.03f a=%+.03f %f" % (self.x,self.v,self.maxaccel,self.xgoal)
 

	
 
    def goto(self, newx):
 
        self.xgoal = newx
 

	
 
    def ismoving(self):
 
        return not self._stopped
 

	
 

	
 
class FlyingFader(Frame):
 
class FlyingFader(tix.Frame):
 

	
 
    def __init__(self,
 
                 master,
 
                 variable,
 
                 label,
 
                 fadedur=1.5,
 
                 font=('Arial', 8),
 
                 labelwidth=12,
 
                 **kw):
 
        Frame.__init__(self, master)
 
        tix.Frame.__init__(self, master)
 
        self.name = label
 
        self.variable = variable
 

	
 
        self.mass = Mass()
 

	
 
        self.config({'bd': 1, 'relief': 'raised'})
 
        scaleopts = {
 
            'variable': variable,
 
            'showvalue': 0,
 
            'from': 1.0,
 
            'to': 0,
 
            'res': 0.001,
 
            'width': 20,
 
            'length': 200,
 
            'orient': 'vert'
 
        }
 
        scaleopts.update(kw)
 
        if scaleopts['orient'] == 'vert':
 
            side1 = TOP
 
            side2 = BOTTOM
 
            side2 = tix.BOTTOM
 
        else:
 
            side1 = RIGHT
 
            side2 = LEFT
 
            side2 = tix.LEFT
 

	
 
        self.scale = Scale(self, **scaleopts)
 
        self.vlabel = Label(self, text="0.0", width=6, font=font)
 
        self.label = Label(self,
 
        self.scale = tix.Scale(self, **scaleopts)
 
        self.vlabel = tix.Label(self, text="0.0", width=6, font=font)
 
        self.label = tix.Label(self,
 
                           text=label,
 
                           font=font,
 
                           anchor='w',
 
                           width=labelwidth)  #wraplength=40, )
 

	
 
        self.oldtrough = self.scale['troughcolor']
 

	
 
        self.scale.pack(side=side2, expand=1, fill=BOTH, anchor='c')
 
        self.vlabel.pack(side=side2, expand=0, fill=X)
 
        self.label.pack(side=side2, expand=0, fill=X)
 
        self.scale.pack(side=side2, expand=1, fill=tix.BOTH, anchor='c')
 
        self.vlabel.pack(side=side2, expand=0, fill=tix.X)
 
        self.label.pack(side=side2, expand=0, fill=tix.X)
 

	
 
        for k in range(1, 10):
 
            self.scale.bind(
 
                "<Key-%d>" % k, lambda evt, k=k: self.newfade(k / 10.0, evt))
 

	
 
        self.scale.bind("<Key-0>", lambda evt: self.newfade(1.0, evt))
 
        self.scale.bind("<grave>", lambda evt: self.newfade(0, evt))
 

	
 
        self.scale.bind("<1>", self.cancelfade)
 
        self.scale.bind("<2>", self.cancelfade)
 
        self.scale.bind("<3>", self.mousefade)
 

	
 
@@ -141,25 +138,25 @@ class FlyingFader(Frame):
 
        target = float(self.tk.call(self.scale, 'get', evt.x, evt.y))
 
        self.newfade(target, evt)
 

	
 
    def ismoving(self):
 
        return self.fadevel != 0 or self.fadeacc != 0
 

	
 
    def newfade(self, newlevel, evt=None, length=None):
 

	
 
        # these are currently unused-- Mass needs to accept a speed input
 
        mult = 1
 
        if evt.state & 8 and evt.state & 4: mult = 0.25  # both
 
        elif evt.state & 8: mult = 0.5  # alt
 
        elif evt.state & 4: mult = 2  # control
 
        elif evt.state & 4: mult = 2  # control # noqa
 

	
 
        self.mass.x = self.variable.get()
 
        self.mass.goto(newlevel)
 

	
 
        self.gofade()
 

	
 
    def gofade(self):
 
        self.mass.update()
 
        self.variable.set(self.mass.x)
 

	
 
        if not self.mass.ismoving():
 
            self.scale['troughcolor'] = self.oldtrough
 
@@ -195,31 +192,28 @@ class FlyingFader(Frame):
 
        self.scale.set(val)
 

	
 

	
 
def colorfade(scale, lev):
 
    low = (255, 255, 255)
 
    high = (0, 0, 0)
 
    out = [int(l + lev * (h - l)) for h, l in zip(high, low)]
 
    col = "#%02X%02X%02X" % tuple(out)
 
    scale.config(troughcolor=col)
 

	
 

	
 
if __name__ == '__main__':
 
    root = Tk()
 
    root = tix.Tk()
 
    root.tk_focusFollowsMouse()
 

	
 
    FlyingFader(root, variable=DoubleVar(), label="suck").pack(side=LEFT,
 
                                                               expand=1,
 
                                                               fill=BOTH)
 
    FlyingFader(root, variable=DoubleVar(), label="moof").pack(side=LEFT,
 
    FlyingFader(root, variable=tix.DoubleVar(),
 
                label="suck").pack(side=tix.LEFT, expand=1, fill=tix.BOTH)
 
    FlyingFader(root, variable=tix.DoubleVar(),
 
                label="moof").pack(side=tix.LEFT, expand=1, fill=tix.BOTH)
 
    FlyingFader(root, variable=tix.DoubleVar(),
 
                label="zarf").pack(side=tix.LEFT, expand=1, fill=tix.BOTH)
 
    FlyingFader(root,
 
                variable=tix.DoubleVar(),
 
                label="long name goes here.  got it?").pack(side=tix.LEFT,
 
                                                               expand=1,
 
                                                               fill=BOTH)
 
    FlyingFader(root, variable=DoubleVar(), label="zarf").pack(side=LEFT,
 
                                                               expand=1,
 
                                                               fill=BOTH)
 
    FlyingFader(root,
 
                variable=DoubleVar(),
 
                label="long name goes here.  got it?").pack(side=LEFT,
 
                                                            expand=1,
 
                                                            fill=BOTH)
 
                                                            fill=tix.BOTH)
 

	
 
    root.mainloop()
light9/Patch.py
Show inline comments
 
import os
 
from rdflib import RDF
 
from light9.namespaces import L9
 
from light9 import showconfig
 

	
 

	
 
def resolve_name(channelname):
 
    "Ensure that we're talking about the primary name of the light."
 
    return get_channel_name(get_dmx_channel(channelname))
 

	
 

	
 
def get_all_channels():
 
    """returns primary names for all channels (sorted)"""
light9/Submaster.py
Show inline comments
 
import os, logging, time
 
import logging
 
from rdflib import Graph, RDF
 
from rdflib import RDFS, Literal, BNode
 
from light9.namespaces import L9, XSD
 
from light9.TLUtility import dict_scale, dict_max
 
from light9 import showconfig
 
from light9.Patch import resolve_name, get_dmx_channel, get_channel_uri, reload_data
 
from light9.Patch import resolve_name, get_dmx_channel, get_channel_uri
 
from louie import dispatcher
 
from .rdfdb.patch import Patch
 
from rdfdb.patch import Patch
 
log = logging.getLogger('submaster')
 

	
 

	
 
class Submaster(object):
 
    """mapping of channels to levels"""
 

	
 
    def __init__(self, name, levels):
 
        """this sub has a name just for debugging. It doesn't get persisted.
 
        See PersistentSubmaster.
 

	
 
        levels is a dict
 
        """
 
@@ -64,25 +64,25 @@ class Submaster(object):
 
    def ident(self):
 
        return (self.name, tuple(sorted(self.levels.items())))
 

	
 
    def __repr__(self):
 
        items = sorted(list(getattr(self, 'levels', {}).items()))
 
        levels = ' '.join(["%s:%.2f" % item for item in items])
 
        return "<'%s': [%s]>" % (getattr(self, 'name', 'no name yet'), levels)
 

	
 
    def __cmp__(self, other):
 
        # not sure how useful this is
 
        if not isinstance(other, Submaster):
 
            return -1
 
        return cmp(self.ident(), other.ident())
 
        return cmp(self.ident(), other.ident()) # noqa
 

	
 
    def __hash__(self):
 
        return hash(self.ident())
 

	
 
    def get_dmx_list(self):
 
        leveldict = self.get_levels()  # gets levels of sub contents
 

	
 
        levels = []
 
        for k, v in list(leveldict.items()):
 
            if v == 0:
 
                continue
 
            try:
 
@@ -180,25 +180,25 @@ class PersistentSubmaster(Submaster):
 
                         "and level %r" % (self.uri, lev, chan, val))
 
                continue
 
            log.debug("   new val %r", val)
 
            if val == 0:
 
                continue
 
            name = self.graph.label(chan)
 
            if not name:
 
                log.error("sub %r has channel %r with no name- "
 
                          "leaving out that channel" % (self.name, chan))
 
                continue
 
            try:
 
                self.levels[name] = float(val)
 
            except:
 
            except Exception:
 
                log.error("name %r val %r" % (name, val))
 
                raise
 

	
 
    def _saveContext(self):
 
        """the context in which we should save all the lightLevel triples for
 
        this sub"""
 
        typeStmt = (self.uri, RDF.type, L9['Submaster'])
 
        with self.graph.currentState(tripleFilter=typeStmt) as current:
 
            try:
 
                log.debug(
 
                    "submaster's type statement is in %r so we save there" %
 
                    list(current.contextsForStatement(typeStmt)))
 
@@ -317,29 +317,29 @@ class Submasters(object):
 
            log.debug("found sub %s", s)
 
            if s not in self.submasters:
 
                sub = self.submasters[s] = PersistentSubmaster(self.graph, s)
 
                dispatcher.send("new submaster", sub=sub)
 
            current.add(s)
 
        for s in set(self.submasters.keys()) - current:
 
            del self.submasters[s]
 
            dispatcher.send("lost submaster", subUri=s)
 
        log.info("findSubs finished, %s subs", len(self.submasters))
 

	
 
    def get_all_subs(self):
 
        "All Submaster objects"
 
        l = sorted(list(self.submasters.items()))
 
        l = [x[1] for x in l]
 
        v = sorted(list(self.submasters.items()))
 
        v = [x[1] for x in v]
 
        songs = []
 
        notsongs = []
 
        for s in l:
 
        for s in v:
 
            if s.name and s.name.startswith('song'):
 
                songs.append(s)
 
            else:
 
                notsongs.append(s)
 
        combined = notsongs + songs
 

	
 
        return combined
 

	
 
    def get_sub_by_uri(self, uri):
 
        return self.submasters[uri]
 

	
 
    def get_sub_by_name(self, name):
light9/TLUtility.py
Show inline comments
 
@@ -47,25 +47,25 @@ def enumerate(*collections):
 
        yield [
 
            i,
 
        ] + [next(iterator) for iterator in iters]
 
        i += 1
 

	
 

	
 
def dumpobj(o):
 
    """Prints all the object's non-callable attributes"""
 
    print(repr(o))
 
    for a in [x for x in dir(o) if not callable(getattr(o, x))]:
 
        try:
 
            print("  %20s: %s " % (a, getattr(o, a)))
 
        except:
 
        except Exception:
 
            pass
 
    print("")
 

	
 

	
 
def dict_filter_update(d, **newitems):
 
    """Adds a set of new keys and values to dictionary 'd' if the values are
 
    true:
 

	
 
    >>> some_dict = {}
 
    >>> dict_filter_update(some_dict, a=None, b=0, c=1, e={}, s='hello')
 
    >>> some_dict
 
    {'c': 1, 's': 'hello'}
 
@@ -181,25 +181,25 @@ def dict_max(*dicts):
 
    ({'a' : 5, 'b' : 9}, {'a' : 10, 'b' : 4})
 
      returns ==> {'a' : 10, 'b' : 9}
 
    """
 
    newdict = {}
 
    for d in dicts:
 
        for k, v in list(d.items()):
 
            newdict[k] = max(v, newdict.get(k, 0))
 
    return newdict
 

	
 

	
 
def dict_scale(d, scl):
 
    """scales all values in dict and returns a new dict"""
 
    return dict([(k, v * scl) for k, v in list(d.items())])
 
    return dict([(k, v * scl) for k, v in d.items()])
 

	
 

	
 
def dict_subset(d, dkeys, default=0):
 
    """Subset of dictionary d: only the keys in dkeys.  If you plan on omitting
 
    keys, make sure you like the default."""
 
    newd = {}  # dirty variables!
 
    for k in dkeys:
 
        newd[k] = d.get(k, default)
 
    return newd
 

	
 

	
 
# functions specific to Timeline
light9/ascoltami/player.py
Show inline comments
 
#!/usr/bin/python
 
"""
 
alternate to the mpd music player, for ascoltami
 
"""
 

	
 
import time, logging, traceback
 
from gi.repository import GObject, Gst
 
from twisted.internet import reactor, task
 
from gi.repository import Gst
 
from twisted.internet import task
 

	
 
log = logging.getLogger()
 

	
 

	
 
class Player(object):
 

	
 
    def __init__(self, autoStopOffset=4, onEOS=None):
 
        """autoStopOffset is the number of seconds before the end of
 
        song before automatically stopping (which is really pausing).
 
        onEOS is an optional function to be called when we reach the
 
        end of a stream (for example, can be used to advance the song).
 
        It is called with one argument which is the URI of the song that
 
        just finished."""
 
        self.autoStopOffset = autoStopOffset
 
        self.playbin = self.pipeline = Gst.ElementFactory.make('playbin', None)
 

	
 
        self.playStartTime = 0
 
        self.lastWatchTime = 0
 
        self.autoStopTime = 0
 
        self.lastSetSongUri = None
 
        self.onEOS = onEOS
 

	
 
        task.LoopingCall(self.watchTime).start(.050)
 

	
 
        bus = self.pipeline.get_bus()
 
        #bus = self.pipeline.get_bus()
 
        # not working- see notes in pollForMessages
 
        #self.watchForMessages(bus)
 

	
 
    def watchTime(self):
 
        try:
 
            self.pollForMessages()
 

	
 
            t = self.currentTime()
 
            log.debug("watch %s < %s < %s", self.lastWatchTime,
 
                      self.autoStopTime, t)
 
            if self.lastWatchTime < self.autoStopTime < t:
 
                log.info("autostop")
 
                self.pause()
 

	
 
            self.lastWatchTime = t
 
        except:
 
        except Exception:
 
            traceback.print_exc()
 

	
 
    def watchForMessages(self, bus):
 
        """this would be nicer than pollForMessages but it's not working for
 
        me. It's like add_signal_watch isn't running."""
 
        bus.add_signal_watch()
 

	
 
        def onEos(*args):
 
            print("onEos", args)
 
            if self.onEOS is not None:
 
                self.onEOS(self.getSong())
 

	
light9/ascoltami/playlist.py
Show inline comments
 
@@ -10,31 +10,31 @@ class NoSuchSong(ValueError):
 
class Playlist(object):
 

	
 
    def __init__(self, graph, playlistUri):
 
        self.graph = graph
 
        self.songs = list(graph.items(playlistUri))
 

	
 
    def nextSong(self, currentSong):
 
        """Returns the next song in the playlist or raises NoSuchSong if 
 
        we are at the end of the playlist."""
 
        try:
 
            currentIndex = self.songs.index(currentSong)
 
        except IndexError:
 
            raise ValueError("%r is not in the current playlist (%r)." % \
 
            raise ValueError("%r is not in the current playlist (%r)." %
 
                (currentSong, self.playlistUri))
 

	
 
        try:
 
            nextSong = self.songs[currentIndex + 1]
 
        except IndexError:
 
            raise NoSuchSong("%r is the last item in the playlist." % \
 
            raise NoSuchSong("%r is the last item in the playlist." %
 
                             currentSong)
 

	
 
        return nextSong
 

	
 
    def allSongs(self):
 
        """Returns a list of all song URIs in order."""
 
        return self.songs
 

	
 
    def allSongPaths(self):
 
        """Returns a list of the filesystem paths to all songs in order."""
 
        paths = []
 
        for song in self.songs:
light9/ascoltami/webapp.py
Show inline comments
 
import json, socket, subprocess, cyclone.web, os
 
import json, socket, subprocess, cyclone.web
 
from twisted.python.util import sibpath
 
from twisted.python.filepath import FilePath
 
from light9.namespaces import L9
 
from light9.showconfig import getSongsFromShow, songOnDisk
 
from rdflib import URIRef
 
from web.contrib.template import render_genshi
 
render = render_genshi([sibpath(__file__, ".")], auto_reload=True)
 

	
 
from cycloneerr import PrettyErrorHandler
 

	
 
_songUris = {}  # locationUri : song
 

	
 

	
 
def songLocation(graph, songUri):
 
@@ -131,25 +130,25 @@ class output(PrettyErrorHandler, cyclone
 

	
 
    def post(self):
 
        d = json.loads(self.request.body)
 
        subprocess.check_call(["bin/movesinks", str(d['sink'])])
 

	
 

	
 
class goButton(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def post(self):
 
        """
 
        if music is playing, this silently does nothing.
 
        """
 
        graph, player = self.settings.app.graph, self.settings.app.player
 
        player = self.settings.app.player
 

	
 
        if player.isAutostopped():
 
            player.resume()
 
        elif player.isPlaying():
 
            pass
 
        else:
 
            player.resume()
 

	
 
        self.set_header("Content-Type", "text/plain")
 
        self.write("ok")
 

	
 

	
light9/coffee.py
Show inline comments
 
new file 100644
 
from cycloneerr import PrettyErrorHandler
 
import cyclone.web
 
import subprocess
 

	
 

	
 
class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    """
 
    e.g.
 

	
 
            (r'/effect\.js', StaticCoffee, {
 
                'src': 'light9/effecteval/effect.coffee'
 
            }),
 
    """ # noqa
 

	
 
    def initialize(self, src):
 
        super(StaticCoffee, self).initialize()
 
        self.src = src
 

	
 
    def get(self):
 
        self.set_header('Content-Type', 'application/javascript')
 
        self.write(
 
            subprocess.check_output(
 
                ['/usr/bin/coffee', '--compile', '--print', self.src]))
light9/collector/collector.py
Show inline comments
 
import time
 
import logging
 
from rdflib import Literal
 
from light9.namespaces import L9, RDF
 
from light9.collector.output import setListElem
 
from light9.collector.device import toOutputAttrs, resolve
 

	
 
# types only
 
from rdflib import Graph, URIRef
 
from typing import List, Dict, Tuple, Any, TypeVar, Generic
 
from typing import List, Dict, Tuple, TypeVar, Generic, Optional
 
from light9.collector.output import Output
 
from light9.collector.weblisteners import WebListeners
 

	
 
ClientType = TypeVar('ClientType')
 
ClientSessionType = TypeVar('ClientSessionType')
 

	
 
log = logging.getLogger('collector')
 

	
 

	
 
def outputMap(graph, outputs):
 
    # type: (Graph, List[Output]) -> Dict[Tuple[URIRef, URIRef], Tuple[Output, int]]
 
    """From rdf config graph, compute a map of
 
       (device, outputattr) : (output, index)
 
    that explains which output index to set for any device update.
 
    """
 
    ret = {}
 

	
 
    outputByUri = {}  # universeUri : output
 
    outputByUri: Dict[URIRef, Output] = {}  # universeUri : output
 
    for out in outputs:
 
        outputByUri[out.uri] = out
 

	
 
    for dc in graph.subjects(RDF.type, L9['DeviceClass']):
 
        log.info('mapping DeviceClass %s', dc)
 
        for dev in graph.subjects(RDF.type, dc):
 
            log.info('  mapping device %s', dev)
 
            universe = graph.value(dev, L9['dmxUniverse'])
 
            try:
 
                output = outputByUri[universe]
 
            except Exception:
 
                log.warn('dev %r :dmxUniverse %r', dev, universe)
 
@@ -41,26 +42,29 @@ def outputMap(graph, outputs):
 
            dmxBase = int(graph.value(dev, L9['dmxBase']).toPython())
 
            for row in graph.objects(dc, L9['attr']):
 
                outputAttr = graph.value(row, L9['outputAttr'])
 
                offset = int(graph.value(row, L9['dmxOffset']).toPython())
 
                index = dmxBase + offset - 1
 
                ret[(dev, outputAttr)] = (output, index)
 
                log.debug('    map %s to %s,%s', outputAttr, output, index)
 
    return ret
 

	
 

	
 
class Collector(Generic[ClientType, ClientSessionType]):
 

	
 
    def __init__(self, graph, outputs, listeners=None, clientTimeoutSec=10):
 
        # type: (Graph, List[Output], List[Listener], float) -> None
 
    def __init__(self,
 
                 graph: Graph,
 
                 outputs: List[Output],
 
                 listeners: Optional[WebListeners] = None,
 
                 clientTimeoutSec: float = 10):
 
        self.graph = graph
 
        self.outputs = outputs
 
        self.listeners = listeners
 
        self.clientTimeoutSec = clientTimeoutSec
 
        self.initTime = time.time()
 
        self.allDevices = set()
 

	
 
        self.graph.addHandler(self.rebuildOutputMap)
 

	
 
        # client : (session, time, {(dev,devattr): latestValue})
 
        self.lastRequest = {
 
        }  # type: Dict[Tuple[ClientType, ClientSessionType], Tuple[float, Dict[Tuple[URIRef, URIRef], float]]]
light9/collector/collector_test.py
Show inline comments
 
import unittest
 
import datetime, time
 
from freezegun import freeze_time
 
from rdflib import Namespace, URIRef
 
from rdflib import Namespace
 

	
 
from light9.namespaces import L9, DEV
 
from light9.collector.collector import Collector, outputMap
 
from rdfdb.mock_syncedgraph import MockSyncedGraph
 

	
 
UDMX = Namespace('http://light9.bigasterisk.com/output/udmx/')
 
DMX0 = Namespace('http://light9.bigasterisk.com/output/dmx0/')
 

	
 
PREFIX = '''
 
   @prefix : <http://light9.bigasterisk.com/> .
 
        @prefix dev: <http://light9.bigasterisk.com/device/> .
 
        @prefix udmx: <http://light9.bigasterisk.com/output/udmx/> .
light9/collector/device.py
Show inline comments
 
import logging
 
import math
 
from light9.namespaces import L9, RDF, DEV
 
from light9.namespaces import L9
 
from rdflib import Literal
 
from webcolors import hex_to_rgb, rgb_to_hex
 
from colormath.color_objects import sRGBColor, CMYColor
 
import colormath.color_conversions
 

	
 
log = logging.getLogger('device')
 

	
 

	
 
class Device(object):
 
    pass
 

	
 

	
light9/collector/output.py
Show inline comments
 
from rdflib import URIRef
 
import sys
 
import time
 
import usb.core
 
import logging
 
from twisted.internet import task, threads, reactor
 
from twisted.internet import threads, reactor
 
from greplin import scales
 
log = logging.getLogger('output')
 

	
 

	
 
# eliminate this: lists are always padded now
 
def setListElem(outList, index, value, fill=0, combine=lambda old, new: new):
 
    if len(outList) < index:
 
        outList.extend([fill] * (index - len(outList)))
 
    if len(outList) <= index:
 
        outList.append(value)
 
    else:
 
        outList[index] = combine(outList[index], value)
light9/collector/weblisteners.py
Show inline comments
 
new file 100644
 
import logging, traceback, time, json
 
log = logging.getLogger('weblisteners')
 

	
 
class WebListeners(object):
 

	
 
    def __init__(self):
 
        self.clients = []
 
        self.pendingMessageForDev = {}  # dev: (attrs, outputmap)
 
        self.lastFlush = 0
 

	
 
    def addClient(self, client):
 
        self.clients.append([client, {}])  # seen = {dev: attrs}
 
        log.info('added client %s %s', len(self.clients), client)
 

	
 
    def delClient(self, client):
 
        self.clients = [[c, t] for c, t in self.clients if c != client]
 
        log.info('delClient %s, %s left', client, len(self.clients))
 

	
 
    def outputAttrsSet(self, dev, attrs, outputMap):
 
        """called often- don't be slow"""
 

	
 
        self.pendingMessageForDev[dev] = (attrs, outputMap)
 
        try:
 
            self._flush()
 
        except Exception:
 
            traceback.print_exc()
 
            raise
 

	
 
    def _flush(self):
 
        now = time.time()
 
        if now < self.lastFlush + .05 or not self.clients:
 
            return
 
        self.lastFlush = now
 

	
 
        while self.pendingMessageForDev:
 
            dev, (attrs, outputMap) = self.pendingMessageForDev.popitem()
 

	
 
            msg = None  # lazy, since makeMsg is slow
 

	
 
            # this omits repeats, but can still send many
 
            # messages/sec. Not sure if piling up messages for the browser
 
            # could lead to slowdowns in the real dmx output.
 
            for client, seen in self.clients:
 
                if seen.get(dev) == attrs:
 
                    continue
 
                if msg is None:
 
                    msg = self.makeMsg(dev, attrs, outputMap)
 

	
 
                seen[dev] = attrs
 
                client.sendMessage(msg)
 

	
 
    def makeMsg(self, dev, attrs, outputMap):
 
        attrRows = []
 
        for attr, val in attrs.items():
 
            output, index = outputMap[(dev, attr)]
 
            attrRows.append({
 
                'attr': attr.rsplit('/')[-1],
 
                'val': val,
 
                'chan': (output.shortId(), index + 1)
 
            })
 
        attrRows.sort(key=lambda r: r['chan'])
 
        for row in attrRows:
 
            row['chan'] = '%s %s' % (row['chan'][0], row['chan'][1])
 

	
 
        msg = json.dumps({'outputAttrsSet': {
 
            'dev': dev,
 
            'attrs': attrRows
 
        }},
 
                         sort_keys=True)
 
        return msg
light9/curvecalc/client.py
Show inline comments
 
"""
 
client code for talking to curvecalc
 
"""
 
import cyclone.httpclient
 
from light9 import networking
 
import urllib.request, urllib.parse, urllib.error
 
from run_local import log
 

	
 

	
 
def sendLiveInputPoint(curve, value):
 
    f = cyclone.httpclient.fetch(networking.curveCalc.path('liveInputPoint'),
 
                                 method='POST',
 
                                 timeout=1,
 
                                 postdata=urllib.parse.urlencode({
 
                                     'curve':
 
                                     curve,
 
                                     'value':
 
                                     str(value),
 
                                 }))
light9/curvecalc/curve.py
Show inline comments
 
import glob, time, logging, ast, os
 
import logging, ast, os
 
from bisect import bisect_left, bisect
 
import louie as dispatcher
 
from twisted.internet import reactor
 
from rdflib import Literal
 
from light9 import showconfig
 
from light9.namespaces import L9, RDF, RDFS
 
from rdfdb.patch import Patch
 

	
 
log = logging.getLogger()
 
# todo: move to config, consolidate with ascoltami, musicPad, etc
 
introPad = 4
 
postPad = 4
 
@@ -36,25 +36,25 @@ class Curve(object):
 
            self._muted = val
 
            dispatcher.send('mute changed', sender=self)
 

	
 
        return locals()
 

	
 
    muted = property(**muted())
 

	
 
    def toggleMute(self):
 
        self.muted = not self.muted
 

	
 
    def load(self, filename):
 
        self.points[:] = []
 
        for line in file(filename):
 
        for line in open(filename):
 
            x, y = line.split()
 
            self.points.append((float(x), ast.literal_eval(y)))
 
        self.points.sort()
 
        dispatcher.send("points changed", sender=self)
 

	
 
    def set_from_string(self, pts):
 
        self.points[:] = []
 
        vals = pts.split()
 
        pairs = list(zip(vals[0::2], vals[1::2]))
 
        for x, y in pairs:
 
            self.points.append((float(x), ast.literal_eval(y)))
 
        self.points.sort()
 
@@ -66,25 +66,25 @@ class Curve(object):
 
            if isinstance(x, str):  # markers
 
                return x
 
            return "%.4g" % x
 

	
 
        return ' '.join(
 
            "%s %s" % (outVal(p[0]), outVal(p[1])) for p in self.points)
 

	
 
    def save(self, filename):
 
        # this is just around for markers, now
 
        if filename.endswith('-music') or filename.endswith('_music'):
 
            print("not saving music track")
 
            return
 
        f = file(filename, 'w')
 
        f = open(filename, 'w')
 
        for p in self.points:
 
            f.write("%s %r\n" % p)
 
        f.close()
 

	
 
    def eval(self, t, allow_muting=True):
 
        if self.muted and allow_muting:
 
            return 0
 
        if not self.points:
 
            raise ValueError("curve has no points")
 
        i = bisect_left(self.points, (t, None)) - 1
 

	
 
        if i == -1:
light9/curvecalc/curveedit.py
Show inline comments
 
"""
 
this may be split out from curvecalc someday, since it doesn't
 
need to be tied to a gui """
 
import cgi, time
 
import cgi
 
from twisted.internet import reactor
 
import cyclone.web, cyclone.httpclient, cyclone.websocket
 
from rdflib import URIRef
 
from cycloneerr import PrettyErrorHandler
 
from run_local import log
 
from louie import dispatcher
 

	
 

	
 
def serveCurveEdit(port, hoverTimeResponse, curveset):
 
    """
 
    /hoverTime requests actually are handled by the curvecalc gui
 
    """
 
    curveEdit = CurveEdit(curveset)
 

	
 
    class HoverTime(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
        def get(self):
light9/curvecalc/curveview.py
Show inline comments
 
import math, logging, traceback
 
import math, logging
 
from gi.repository import Gtk
 
from gi.repository import Gdk
 
from gi.repository import GooCanvas
 
import louie as dispatcher
 
from rdflib import Literal
 
from twisted.internet import reactor
 
from light9.curvecalc.zoomcontrol import RegionZoom
 
from light9.curvecalc import cursors
 
from light9.curvecalc.curve import introPad, postPad
 
from lib.goocanvas_compat import Points, polyline_new_line
 
import imp
 

	
 
log = logging.getLogger()
 
print("curveview.py toplevel")
 

	
 

	
 
def vlen(v):
 
    return math.sqrt(v[0] * v[0] + v[1] * v[1])
 

	
 

	
 
@@ -1267,26 +1266,26 @@ class Curvesetview(object):
 
        raise ValueError("couldn't find curveRow named %r" % name)
 

	
 
    def set_featured_curves(self, curveNames):
 
        """bring these curves to the top of the stack"""
 
        for n in curveNames[::-1]:
 
            self.curvesVBox.reorder_child(
 
                self.curveRow_from_name(n).box, Gtk.PACK_START)
 

	
 
    def onKeyPress(self, widget, event):
 
        if not self.live:  # workaround for old instances living past reload()
 
            return
 

	
 
        r = self.row_under_mouse()
 
        key = event.string
 
        #r = self.row_under_mouse()
 
        #key = event.string
 
        pass  # no handlers right now
 

	
 
    def row_under_mouse(self):
 
        x, y = self.curvesVBox.get_pointer()
 
        for r in self.allCurveRows:
 
            inRowX, inRowY = self.curvesVBox.translate_coordinates(r.box, x, y)
 
            alloc = r.box.get_allocation()
 
            if 0 <= inRowX < alloc.width and 0 <= inRowY < alloc.height:
 
                return r
 
        raise ValueError("no curveRow is under the mouse")
 

	
 
    def focus_entry(self):
light9/curvecalc/output.py
Show inline comments
 
import time, logging
 
from twisted.internet import reactor
 
from light9 import Submaster, dmxclient
 
from light9.namespaces import L9
 
from light9.curvecalc.subterm import Subterm
 

	
 
from louie import dispatcher
 
log = logging.getLogger("output")
 

	
 

	
 
class Output(object):
 
    lastsendtime = 0
 
    lastsendlevs = None
 

	
 
    def __init__(self, graph, session, music, curveset, currentSubterms):
 
        self.graph, self.session, self.music = graph, session, music
 
        self.currentSubterms = currentSubterms
light9/curvecalc/subterm.py
Show inline comments
 
import math, os, random, logging
 
from rdflib import Graph, URIRef, RDF, RDFS, Literal
 
import logging
 
from rdflib import Literal
 
from louie import dispatcher
 
import light9.Effects
 
from light9 import Submaster, showconfig, prof
 
from light9 import Submaster
 
from light9.Patch import get_dmx_channel
 
from rdfdb.patch import Patch
 
from light9.namespaces import L9
 
log = logging.getLogger()
 

	
 

	
 
class Expr(object):
 
    """singleton, provides functions for use in subterm expressions,
 
    e.g. chases"""
 

	
 
    def __init__(self):
 
        self.effectGlobals = light9.Effects.configExprGlobals()
light9/curvecalc/zoomcontrol.py
Show inline comments
 
from gi.repository import Gtk
 
from gi.repository import GObject
 
from gi.repository import GooCanvas
 
import louie as dispatcher
 
from light9.curvecalc import cursors
 
from lib.goocanvas_compat import Points, polyline_new_line
 
from twisted.internet import reactor
 

	
 

	
 
class ZoomControl(object):
 
    """
 
    please pack .widget
 
    """
 

	
light9/dmxchanedit.py
Show inline comments
 
@@ -9,25 +9,25 @@ proposal for new focus and edit system:
 
- numbers drag up and down (like today)
 
- if you drag a number in a selected row, all the selected numbers change
 
- if you start dragging a number in an unselected row, your row becomes the new selection and then the edit works
 

	
 

	
 
proposal for new attribute system:
 
- we always want to plan some attributes for each light: where to center; what stage to cover; what color gel to apply; whether the light is burned out
 
- we have to stop packing these into the names. Names should be like 'b33' or 'blue3' or just '44'. maybe 'blacklight'.
 

	
 
"""
 

	
 
import tkinter as tk
 
from rdflib import RDF, Literal
 
from rdflib import RDF
 
import math, logging
 
from decimal import Decimal
 
from light9.namespaces import L9
 
log = logging.getLogger('dmxchanedit')
 
stdfont = ('Arial', 7)
 

	
 

	
 
def gradient(lev, low=(80, 80, 180), high=(255, 55, 50)):
 
    out = [int(l + lev * (h - l)) for h, l in zip(high, low)]
 
    col = "#%02X%02X%02X" % tuple(out)
 
    return col
 

	
light9/dmxclient.py
Show inline comments
 
@@ -63,14 +63,14 @@ def outputlevels(levellist, twisted=0, c
 
        except xmlrpc.client.Fault as e:
 
            log.error("outputlevels had xml fault: %s" % e)
 
            time.sleep(1)
 
    else:
 
        _dmx.send(clientid, levellist)
 
        return defer.succeed(None)
 

	
 

	
 
dummy = os.getenv('DMXDUMMY')
 
if dummy:
 
    print("dmxclient: DMX is in dummy mode.")
 

	
 
    def outputlevels(*args, **kw):
 
    def outputlevels(*args, **kw): # noqa
 
        pass
light9/effect/effecteval.py
Show inline comments
 
from rdflib import URIRef, Literal
 
from light9.namespaces import L9, RDF, DEV
 
from rdflib import Literal
 
from light9.namespaces import L9, DEV
 
from webcolors import rgb_to_hex, hex_to_rgb
 
from colorsys import hsv_to_rgb
 
from decimal import Decimal
 
import math
 
import traceback
 
from noise import pnoise1
 
import logging
 
import time
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.scale import scale
 
import random
 
random.seed(0)
 
print("reload effecteval")
 

	
 
log = logging.getLogger('effecteval')
 

	
 

	
 
def literalColor(rnorm, gnorm, bnorm):
 
    return Literal(
 
        rgb_to_hex([int(rnorm * 255),
 
@@ -126,25 +123,24 @@ def effect_animRainbow(effectSettings, s
 
        out.update({
 
            (dev, L9['rx']):
 
            lerp(.27, .7, (n - 1) / 4) + .2 * math.sin(ang + n),
 
            (dev, L9['ry']):
 
            lerp(.46, .52, (n - 1) / 4) + .5 * math.cos(ang + n),
 
        })
 
    return out
 

	
 

	
 
def effect_auraSparkles(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    print(effectSettings)
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5 + 1):
 
        scl = strength * ((int(songTime * 10) % n) < 1)
 
        col = literalColorHsv((songTime + (n / 5)) % 1, 1, scl)
 

	
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .95,
 
        })
 
        ang = songTime * 4
light9/effect/sequencer.py
Show inline comments
 
@@ -35,25 +35,27 @@ class Note(object):
 

	
 
    def __init__(self, graph, uri, effectevalModule, simpleOutputs):
 
        g = self.graph = graph
 
        self.uri = uri
 
        self.effectEval = effectevalModule.EffectEval(
 
            graph, g.value(uri, L9['effectClass']), simpleOutputs)
 
        self.baseEffectSettings = {}  # {effectAttr: value}
 
        for s in g.objects(uri, L9['setting']):
 
            settingValues = dict(g.predicate_objects(s))
 
            ea = settingValues[L9['effectAttr']]
 
            self.baseEffectSettings[ea] = settingValues[L9['value']]
 

	
 
        floatVal = lambda s, p: float(g.value(s, p).toPython())
 
        def floatVal(s, p):
 
            return float(g.value(s, p).toPython())
 

	
 
        originTime = floatVal(uri, L9['originTime'])
 
        self.points = []
 
        for curve in g.objects(uri, L9['curve']):
 
            self.points.extend(
 
                self.getCurvePoints(curve, L9['strength'], originTime))
 
        self.points.sort()
 

	
 
    def getCurvePoints(self, curve, attr, originTime):
 
        points = []
 
        po = list(self.graph.predicate_objects(curve))
 
        if dict(po).get(L9['attr'], None) != attr:
 
            return []
light9/effect/settings.py
Show inline comments
 
"""
 
Data structure and convertors for a table of (device,attr,value)
 
rows. These might be effect attrs ('strength'), device attrs ('rx'),
 
or output attrs (dmx channel).
 
"""
 
import decimal
 
import numpy
 
from rdflib import URIRef, Literal
 
from light9.namespaces import RDF, L9, DEV
 
from rdfdb.patch import Patch
 
from light9.namespaces import RDF, L9
 
import logging
 
log = logging.getLogger('settings')
 
from light9.collector.device import resolve
 

	
 

	
 
def parseHex(h):
 
    if h[0] != '#': raise ValueError(h)
 
    return [int(h[i:i + 2], 16) for i in (1, 3, 5)]
 

	
 

	
 
def parseHexNorm(h):
 
    return [x / 255 for x in parseHex(h)]
light9/effect/settings_test.py
Show inline comments
 
import unittest
 
from rdflib import Literal
 
from rdfdb.patch import Patch
 
from rdfdb.localsyncedgraph import LocalSyncedGraph
 
from light9.namespaces import RDF, L9, DEV
 
from light9.namespaces import L9, DEV
 
from light9.effect.settings import DeviceSettings
 

	
 

	
 
class TestDeviceSettings(unittest.TestCase):
 

	
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(
 
            files=['test/cam/lightConfig.n3', 'test/cam/bg.n3'])
 

	
 
    def testToVectorZero(self):
 
        ds = DeviceSettings(self.graph, [])
 
        self.assertEqual([0] * 30, ds.toVector())
light9/effecteval/effectloop.py
Show inline comments
 
import time, json, logging, traceback
 
import numpy
 
import serial
 
from twisted.internet import reactor, threads
 
from twisted.internet import reactor
 
from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 
from twisted.internet.error import TimeoutError
 
from rdflib import URIRef, Literal
 
from rdflib import URIRef
 
import cyclone.httpclient
 
from light9.namespaces import L9, RDF, RDFS
 
from light9.namespaces import L9, RDF
 
from light9.effecteval.effect import EffectNode
 
from light9 import Effects
 
from light9 import networking
 
from light9 import Submaster
 
from light9 import dmxclient
 
from light9 import prof
 
log = logging.getLogger('effectloop')
 

	
 

	
 
class EffectLoop(object):
 
    """maintains a collection of the current EffectNodes, gets time from
 
    music player, sends dmx"""
 

	
 
    def __init__(self, graph, stats):
 
        self.graph, self.stats = graph, stats
 
        self.currentSong = None
 
        self.currentEffects = [
 
        ]  # EffectNodes for the current song plus the submaster ones
light9/gtkpyconsole.py
Show inline comments
 
from lib.ipython_view import IPythonView
 
import gi
 
import gi # noqa
 
from gi.repository import Gtk
 
from gi.repository import Pango
 

	
 

	
 
def togglePyConsole(self, item, user_ns):
 
    """
 
    toggles a toplevel window with an ipython console inside.
 

	
 
    self is an object we can stick the pythonWindow attribute on
 

	
 
    item is a checkedmenuitem
 

	
light9/namespaces.py
Show inline comments
 
from rdflib import Namespace, RDF, RDFS
 
from rdflib import Namespace, RDF, RDFS  # noqa
 

	
 

	
 
# Namespace was showing up in profiles
 
class FastNs(object):
 

	
 
    def __init__(self, base):
 
        self.ns = Namespace(base)
 
        self.cache = {}
 

	
 
    def __getitem__(self, term):
 
        if term not in self.cache:
 
            self.cache[term] = self.ns[term]
light9/paint/solve.py
Show inline comments
 
from light9.namespaces import RDF, L9, DEV
 
from light9.namespaces import L9, DEV
 
from PIL import Image
 
import numpy
 
import scipy.misc, scipy.ndimage, scipy.optimize
 
import cairo
 
import logging
 

	
 
from light9.effect.settings import DeviceSettings, parseHex, toHex
 
from light9.effect.settings import DeviceSettings, parseHex
 

	
 
log = logging.getLogger('solve')
 

	
 
# numpy images in this file are (x, y, c) layout.
 

	
 

	
 
def numpyFromCairo(surface):
 
    w, h = surface.get_width(), surface.get_height()
 
    a = numpy.frombuffer(surface.get_data(), numpy.uint8)
 
    a.shape = h, w, 4
 
    a = a.transpose((1, 0, 2))
 
    return a[:w, :h, :3]
 
@@ -142,25 +142,25 @@ class Solver(object):
 
            ctx.set_source_rgb(r / 255, g / 255, b / 255)
 
            ctx.stroke()
 

	
 
        #surface.write_to_png('/tmp/surf.png')
 
        return numpyFromCairo(surface)
 

	
 
    def bestMatch(self, img, device=None):
 
        """the one sample that best matches this image"""
 
        #img = self._blur(img)
 
        results = []
 
        dist = ImageDist(img)
 
        if device is None:
 
            items = list(self.samples.items())
 
            items = self.samples.items()
 
        else:
 
            items = self.samplesForDevice[device]
 
        for uri, img2 in sorted(items):
 
            if img.shape != img2.shape:
 
                log.warn("mismatch %s %s", img.shape, img2.shape)
 
                continue
 
            results.append((dist.distanceTo(img2), uri, img2))
 
        results.sort()
 
        topDist, topUri, topImg = results[0]
 
        print('tops2')
 
        for row in results[:4]:
 
            print('%.5f' % row[0], row[1][-20:], self.sampleSettings[row[1]])
 
@@ -211,36 +211,36 @@ class Solver(object):
 
        best light DeviceSettings to match the image
 
        """
 
        pic0 = self.draw(painting).astype(numpy.float)
 
        pic0Blur = self._blur(pic0)
 
        saveNumpy('/tmp/sample_paint_%s.png' % len(painting['strokes']),
 
                  pic0Blur)
 
        sampleDist = {}
 
        dist = ImageDist(pic0Blur)
 
        for sample, picSample in sorted(self.blurredSamples.items()):
 
            #saveNumpy('/tmp/sample_%s.png' % sample.split('/')[-1],
 
            #          f(picSample))
 
            sampleDist[sample] = dist.distanceTo(picSample)
 
        results = sorted([(d, uri) for uri, d in list(sampleDist.items())])
 
        results = sorted([(d, uri) for uri, d in sampleDist.items()])
 

	
 
        sample = results[0][1]
 

	
 
        # this is wrong; some wrong-alignments ought to be dimmer than full
 
        brightest0 = brightest(pic0)
 
        brightestSample = brightest(self.samples[sample])
 
        #brightestSample = brightest(self.samples[sample])
 

	
 
        if max(brightest0) < 1 / 255:
 
            return DeviceSettings(self.graph, [])
 

	
 
        scale = brightest0 / brightestSample
 
        #scale = brightest0 / brightestSample
 

	
 
        s = DeviceSettings.fromResource(self.graph, sample)
 
        # missing color scale, but it was wrong to operate on all devs at once
 
        return s
 

	
 
    def solveBrute(self, painting):
 
        pic0 = self.draw(painting).astype(numpy.float)
 

	
 
        colorSteps = 2
 
        colorStep = 1. / colorSteps
 

	
 
        # use toVector then add ranges
 
@@ -291,25 +291,25 @@ class Solver(object):
 

	
 
    def simulationLayers(self, settings):
 
        """
 
        how should a simulation preview approximate the light settings
 
        (device attribute values) by combining photos we have?
 
        """
 
        assert isinstance(settings, DeviceSettings)
 
        layers = []
 

	
 
        for dev, devSettings in settings.byDevice():
 
            requestedColor = devSettings.getValue(dev, L9['color'])
 
            candidatePics = []  # (distance, path, picColor)
 
            for sample, s in list(self.sampleSettings.items()):
 
            for sample, s in self.sampleSettings.items():
 
                path = self.path[sample]
 
                otherDevSettings = s.ofDevice(dev)
 
                if not otherDevSettings:
 
                    continue
 
                dist = devSettings.distanceTo(otherDevSettings)
 
                log.info('  candidate pic %s %s dist=%s', sample, path, dist)
 
                candidatePics.append((dist, path, s.getValue(dev, L9['color'])))
 
            candidatePics.sort()
 
            # we could even blend multiple top candidates, or omit all
 
            # of them if they're too far
 
            bestDist, bestPath, bestPicColor = candidatePics[0]
 
            log.info('  device best d=%g path=%s color=%s', bestDist, bestPath,
light9/paint/solve_test.py
Show inline comments
 
import unittest
 
import numpy.testing
 
from . import solve
 
from rdflib import Namespace
 
from light9.namespaces import RDF, L9, DEV
 
from light9.namespaces import L9, DEV
 
from rdfdb.localsyncedgraph import LocalSyncedGraph
 
from light9.effect.settings import DeviceSettings
 

	
 

	
 
class TestSolve(unittest.TestCase):
 

	
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(
 
            files=['test/cam/lightConfig.n3', 'test/cam/bg.n3'])
 
        self.solver = solve.Solver(self.graph,
 
                                   imgSize=(100, 48),
 
                                   sessions=[L9['session0']])
light9/showconfig.py
Show inline comments
 
import logging, warnings
 
from twisted.python.filepath import FilePath
 
from os import path, getenv
 
from rdflib import Graph
 
from rdflib import URIRef
 
from .namespaces import MUS, L9
 
from .namespaces import L9
 
log = logging.getLogger('showconfig')
 

	
 
_config = None  # graph
 

	
 

	
 
def getGraph():
 
    warnings.warn(
 
        "code that's using showconfig.getGraph should be "
 
        "converted to use the sync graph",
 
        stacklevel=2)
 
    global _config
 
    if _config is None:
light9/subclient.py
Show inline comments
 
from light9.collector.collector_client import sendToCollector
 
from twisted.internet import reactor, task
 
from twisted.internet import reactor
 
import traceback
 
import time
 
import logging
 
log = logging.getLogger()
 

	
 

	
 
class SubClient:
 

	
 
    def __init__(self):
 
        """assumed that your init saves self.graph"""
 
        pass  # we may later need init code for network setup
 

	
 
@@ -29,16 +29,16 @@ class SubClient:
 

	
 
        def err(e):
 
            log.warn('subclient loop: %r', e)
 
            reactor.callLater(2, self.send_levels_loop)
 

	
 
        d = self._send_sub()
 
        d.addCallbacks(done, err)
 

	
 
    def _send_sub(self):
 
        try:
 
            with self.graph.currentState() as g:
 
                outputSettings = self.get_output_settings(_graph=g)
 
        except:
 
        except Exception:
 
            traceback.print_exc()
 
            return
 
        return sendToCollector('subclient', self.session, outputSettings)
light9/uihelpers.py
Show inline comments
 
@@ -24,25 +24,25 @@ def bindkeys(root, key, func):
 
    root.bind(key, func)
 
    for w in root.winfo_children():
 
        w.bind(key, func)
 

	
 

	
 
def toplevel_savegeometry(tl, name):
 
    try:
 
        geo = tl.geometry()
 
        if not geo.startswith("1x1"):
 
            f = open(".light9-window-geometry-%s" % name.replace(' ', '_'), 'w')
 
            f.write(tl.geometry())
 
        # else the window never got mapped
 
    except Exception as e:
 
    except Exception:
 
        # it's ok if there's no saved geometry
 
        pass
 

	
 

	
 
def toplevelat(name, existingtoplevel=None, graph=None, session=None):
 
    tl = existingtoplevel or Toplevel()
 
    tl.title(name)
 

	
 
    lastSaved = [None]
 
    setOnce = [False]
 
    graphSetTime = [0]
 

	
light9/vidref/main.py
Show inline comments
 
@@ -70,25 +70,25 @@ class Gui(object):
 
            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()
 
        try:
 
            with gtk.gdk.lock:
 
                self.replayViews.update(position)
 
        except:
 
        except Exception:
 
            traceback.print_exc()
 
        return True
 

	
 
    def getInputs(self):
 
        return ['auto', 'dv', 'video0']
 

	
 
    def on_liveVideoEnabled_toggled(self, widget):
 
        self.pipeline.setLiveVideo(widget.get_active())
 

	
 
    def on_liveFrameRate_value_changed(self, widget):
 
        print(widget.get_value())
 

	
light9/vidref/musictime.py
Show inline comments
 
import restkit, time, json, logging
 
import time, json, logging
 
from light9 import networking
 
from twisted.internet import reactor
 
from cyclone.httpclient import fetch
 
from restkit.errors import ResourceNotFound
 
import http_parser.http
 
log = logging.getLogger()
 

	
 

	
 
class MusicTime(object):
 
    """
 
    fetch times from ascoltami in a background thread; return times
 
    upon request, adjusted to be more precise with the system clock
 
    """
 

	
 
    def __init__(self,
 
                 period=.2,
 
                 onChange=lambda position: None,
 
@@ -20,25 +18,24 @@ class MusicTime(object):
 
        """period is the seconds between http time requests.
 

	
 
        We call onChange with the time in seconds and the total time
 

	
 
        The choice of period doesn't need to be tied to framerate,
 
        it's more the size of the error you can tolerate (since we
 
        make up times between the samples, and we'll just run off the
 
        end of a song)
 
        """
 
        self.period = period
 
        self.hoverPeriod = .05
 
        self.onChange = onChange
 
        self.musicResource = restkit.Resource(networking.musicPlayer.url)
 

	
 
        self.position = {}
 
        # driven by our pollCurvecalcTime and also by Gui.incomingTime
 
        self.lastHoverTime = None  # None means "no recent value"
 
        self.pollMusicTime()
 
        if pollCurvecalc:
 
            self.pollCurvecalcTime()
 

	
 
    def getLatest(self, frameTime=None):
 
        """
 
        dict with 't' and 'song', etc.
 

	
 
@@ -116,15 +113,18 @@ class MusicTime(object):
 
        def eb(err):
 
            if self.lastHoverTime:
 
                log.warn("talking to curveCalc: %s", err.getErrorMessage())
 
            self.lastHoverTime = None
 
            reactor.callLater(2, self.pollCurvecalcTime)
 

	
 
        d = fetch(networking.curveCalc.path("hoverTime"))
 
        d.addCallback(cb)
 
        d.addErrback(eb)  # note this includes errors in cb()
 

	
 
    def sendTime(self, t):
 
        """request that the player go to this time"""
 
        self.musicResource.post("time",
 
                                payload=json.dumps({"t": t}),
 
                                headers={"content-type": "application/json"})
 
        fetch(
 
            method=b'POST',
 
            url=networking.musicPlayer.path('time'),
 
            body=json.dumps({"t": t}),
 
            headers={b"content-type": [b"application/json"]},
 
        )
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, showconfig
 
from light9.vidref.replay import songDir, takeDir, snapshotDir
 
from light9 import showconfig
 
from light9.namespaces import L9
 
from PIL import Image
 
from io import StringIO
 
log = logging.getLogger('remotepi')
 

	
 

	
 
class Pipeline(object):
 

	
 
    def __init__(self, liveVideo, musicTime, recordingTo, graph):
 
        self.musicTime = musicTime
 
        self.recordingTo = recordingTo
 

	
light9/vidref/videorecorder.py
Show inline comments
 
@@ -149,25 +149,25 @@ class VideoRecordSink(gst.Element):
 
    def chainfunc(self, pad, buffer):
 
        position = self.musicTime.getLatest()
 

	
 
        # if music is not playing and there's no pending snapshot
 
        # request, we could skip the image conversions here.
 

	
 
        try:
 
            cap = buffer.caps[0]
 
            #print "cap", (cap['width'], cap['height'])
 
            img = Image.fromstring('RGB', (cap['width'], cap['height']),
 
                                   buffer.data)
 
            self.imagesToSave.put((position, img, buffer.timestamp))
 
        except:
 
        except Exception:
 
            traceback.print_exc()
 

	
 
        return gst.FLOW_OK
 

	
 
    def saveImg(self, position, img, bufferTimestamp):
 
        if not position['song']:
 
            return
 

	
 
        t1 = time.time()
 
        outDir = takeDir(songDir(position['song']), position['started'])
 
        outFilename = "%s/%08.03f.jpg" % (outDir, position['t'])
 
        if os.path.exists(outFilename):  # we're paused on one time
light9/wavepoints.py
Show inline comments
 
import wave, audioop
 

	
 

	
 
def simp(filename, seconds_per_average=0.001):
 
    """smaller seconds_per_average means fewer data points"""
 
    wavefile = wave.open(filename, 'rb')
 
    print("# gnuplot data for %s, seconds_per_average=%s" % \
 
    print("# gnuplot data for %s, seconds_per_average=%s" %
 
        (filename, seconds_per_average))
 
    print(
 
        "# %d channels, samplewidth: %d, framerate: %s, frames: %d\n# Compression type: %s (%s)"
 
        % wavefile.getparams())
 

	
 
    framerate = wavefile.getframerate()  # frames / second
 

	
 
    frames_to_read = int(framerate * seconds_per_average)
 
    print("# frames_to_read=%s" % frames_to_read)
 

	
 
    time_and_max = []
 
    values = []
 
    count = 0
 
    while True:
 
        fragment = wavefile.readframes(frames_to_read)
 
        if not fragment:
 
            break
 

	
 
        # other possibilities:
light9/zmqtransport.py
Show inline comments
 
new file 100644
 
import json
 
from rdflib import URIRef, Literal
 
from greplin import scales
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPullConnection
 
import logging
 

	
 
log = logging.getLogger('zmq')
 

	
 

	
 
def parseJsonMessage(msg):
 
    body = json.loads(msg)
 
    settings = []
 
    for device, attr, value in body['settings']:
 
        if isinstance(value, str) and value.startswith('http'):
 
            value = URIRef(value)
 
        else:
 
            value = Literal(value)
 
        settings.append((URIRef(device), URIRef(attr), value))
 
    return body['client'], body['clientSession'], settings, body['sendTime']
 

	
 

	
 
def startZmq(port, collector):
 
    stats = scales.collection('/zmqServer', scales.PmfStat('setAttr'))
 

	
 
    zf = ZmqFactory()
 
    addr = 'tcp://*:%s' % port
 
    log.info('creating zmq endpoint at %r', addr)
 
    e = ZmqEndpoint('bind', addr)
 

	
 
    class Pull(ZmqPullConnection):
 
        #highWaterMark = 3
 
        def onPull(self, message):
 
            with stats.setAttr.time():
 
                # todo: new compressed protocol where you send all URIs up
 
                # front and then use small ints to refer to devices and
 
                # attributes in subsequent requests.
 
                client, clientSession, settings, sendTime = parseJsonMessage(
 
                    message[0])
 
                collector.setAttrs(client, clientSession, settings, sendTime)
 

	
 
    Pull(zf, e)
0 comments (0 inline, 0 general)