Files
gig/gig.py

439 lines
15 KiB
Python
Executable File

#!/usr/bin/python3
import argparse
import sounddevice
import soundfile
import pyglet
from pyglet.window import key
import factory
import display
import dmx
import yaml
import time
import dbus
from ola.ClientWrapper import ClientWrapper
from ola.DMXConstants import (DMX_MIN_SLOT_VALUE, DMX_MAX_SLOT_VALUE, DMX_UNIVERSE_SIZE)
class EventRunner:
def __init__(self, gigconfig):
self._gigconfig = gigconfig
self._running = False
self._selected = None
self._playing = None
self._active = []
self._currentitem = None
for item in self._gigconfig['items']:
self._gigconfig['items'][item].set_eventrunner(self)
def set_audiencecontent(self, audiencecontent):
for item in self._gigconfig['items']:
self._gigconfig['items'][item].set_audiencecontent(audiencecontent)
def get_config(self, section):
return self._gigconfig[section]
def select(self, pos):
self._selected = pos
self._playing = None
self.activate('select')
def play(self):
self._playing = self._selected
self._selected = None
self.activate('start')
def advance(self):
if self._playing is not None:
return self.activate()
elif self._selected is not None:
self.play()
return True
return False
def activate(self, marker=None):
newitem = self.get_item()
if self._currentitem != newitem:
selected = self._selected
self.stop()
newitem.reset()
self._currentitem = newitem
if marker is None:
marker = 'select'
elif marker != 'select':
return False
self._selected = selected
now = time.time()
if not self._currentitem.activate(marker, now):
return False
for event in self._currentitem._events:
if event.at is not None and not event.scheduled:
pyglet.clock.schedule_once(self.update, event.at - now, event)
print("Scheduled event {} at {} ({} from now)".format(event, event.at, event.at - now))
event.scheduled = True
self._next_button = self._currentitem.get_next_button()
return True
def stop(self):
pyglet.clock.unschedule(self.update)
self._playing = None
self._selected = None
for event in self._active:
if event is not None:
event.stop()
self._active = []
if self._currentitem is not None:
self._currentitem.reset()
def update(self, dt, event):
event.start()
self._active.append(event)
def notify(self, *args, **kwargs):
for event in self._active:
if event is not None:
event.notify(*args, **kwargs)
def finished(self, event):
# We might get called back here from within a notify or other such construct
for i, e in enumerate(self._active):
if e is event:
self._active[i] = None
def pause(self):
pass
def get_item(self, pos=None):
if pos is None:
pos = self._playing
if pos is None:
pos = self._selected
if pos is None:
return None
item = self._gigconfig['setlist'][pos]
return self._gigconfig['items'][item]
def status(self):
if self._playing is not None:
status = 'Playing {}'.format(self.get_item().get_title())
if self._next_button is not None:
button = "Marker {}".format(self._next_button)
else:
button = "Select next"
elif self._selected is not None:
status = 'Selected {}'.format(self.get_item().get_title())
button = "Play"
else:
status = 'Waiting'
button = "Select"
return {
'selected' : self._selected,
'playing' : self._playing,
'running' : self._running,
'status' : status,
'button' : button,
}
class MainWindow(pyglet.window.Window):
def __init__(self, gigconfig, eventrunner, audiencecontent, *args, **kwargs):
super().__init__(*args, **kwargs)
self._gigconfig = gigconfig
self._eventrunner = eventrunner
self._audiencecontent = audiencecontent
self._audiencecontent.register(self)
self._cursorpos = 0
self._playpos = None
self._selectpos = None
self.on_resize(self.width, self.height)
self.update_cursor(0)
def on_resize(self, width, height):
self._scale = self.height / 600
self.batch = pyglet.graphics.Batch()
self.status = pyglet.text.Label('Status bar',
font_name='Ariel',
font_size=18 * self._scale,
x=self.width//2, y=0,
anchor_x='left', anchor_y='bottom',
batch=self.batch)
self.next_action = pyglet.text.Label('Next:',
font_name='Ariel',
font_size=18 * self._scale,
x=self.width//2, y=40 * self._scale,
anchor_x='left', anchor_y='bottom',
batch=self.batch)
self._spacing = 40 * self._scale
self._cursorspread = 300 * self._scale
self._cursorwidth = 15 * self._scale
self._cursorheight = 20 * self._scale
y_increment = self._spacing
y_counter = self.height - y_increment
# They're really buttons, but...
self.buttons = []
for item in self._gigconfig['setlist']:
self.buttons.append(pyglet.text.Label(self._gigconfig['items'][item].get_title(),
font_name='Ariel',
font_size=14 * self._scale,
width=self._cursorspread,
x=self.width//4, y=y_counter,
anchor_x='center', anchor_y='center',
batch=self.batch))
y_counter -= y_increment
self._cursor1x = self.width//4 - self._cursorspread/2
self._cursor2x = self.width//4 + self._cursorspread/2
self._cursory = self.height//2
self.cursor1 = pyglet.shapes.Triangle(self._cursor1x, self._cursory,
self._cursor1x-self._cursorwidth, self._cursory-self._cursorheight/2,
self._cursor1x-self._cursorwidth, self._cursory+self._cursorheight/2,
(255, 255, 255, 255),
batch=self.batch)
self.cursor2 = pyglet.shapes.Triangle(self._cursor2x, self._cursory,
self._cursor2x+self._cursorwidth, self._cursory-self._cursorheight/2,
self._cursor2x+self._cursorwidth, self._cursory+self._cursorheight/2,
(255, 255, 255, 255),
batch=self.batch)
self.update_objects()
self.audience_update()
def audience_update(self):
self.switch_to()
self._audiencecontent.create(self, self.width // 2, self.height // 2, x=self.width//2, y=self.height//4)
def update_objects(self):
status = self._eventrunner.status()
self._selectpos = status['selected']
self._playpos = status['playing']
self.set_status(status['status'])
self.set_next_action(status['button'])
self.update_cursor()
def update_cursor(self, pos=None):
if pos is not None:
self._cursorpos = pos
y_increment = self._spacing
y_counter = self.height // 2 + y_increment * self._cursorpos
for i, button in enumerate(self.buttons):
button.y = y_counter
if i == self._playpos:
button.color = (0, 255, 0, 255)
elif i == self._selectpos:
button.color = (255, 255, 0, 255)
else:
button.color = (255, 255, 255, 255)
y_counter -= y_increment
def select(self):
if self._playpos == self._cursorpos:
if not self._eventrunner.advance():
if self._cursorpos < len(self.buttons) - 1:
self.update_cursor(self._cursorpos + 1)
self._eventrunner.select(self._cursorpos)
elif self._selectpos == self._cursorpos:
self._eventrunner.play()
else:
self._eventrunner.select(self._cursorpos)
self.update_objects()
def set_status(self, text):
self.status.text = text
def set_next_action(self, text):
self.next_action.text = "Next: {}".format(text)
def action_up(self):
if self._cursorpos > 0:
self.update_cursor(self._cursorpos - 1)
def action_down(self):
if self._cursorpos < len(self.buttons) - 1:
self.update_cursor(self._cursorpos + 1)
def action_select(self):
self.select()
def action_stop(self):
if self._playpos is not None:
self._eventrunner.stop()
elif self._selectpos is not None:
self._eventrunner.stop()
else:
self.action_up()
self.update_objects()
def on_key_press(self, symbol, modifiers):
if symbol == key.UP:
self.action_up()
elif symbol == key.DOWN:
self.action_down()
elif symbol == key.ENTER:
self.action_select()
elif symbol == key.SPACE:
self.action_stop()
else:
super().on_key_press(symbol, modifiers)
def on_draw(self):
self.clear()
self.batch.draw()
self.audiencebatch.draw()
class AudienceContent:
def __init__(self, gigconfig, eventrunner):
self._gigconfig = gigconfig
self._eventrunner = eventrunner
self._objects = []
self._eventrunner.set_audiencecontent(self)
self._display = display.DisplayContent()
def register(self, obj):
self._objects.append(obj)
def update(self):
for obj in self._objects:
obj.audience_update()
def create(self, obj, width, height, x=0, y=0):
self._display.create(obj, width, height, x, y)
def new_content(self, obj):
self._display = obj
self.update()
class AudienceWindow(pyglet.window.Window):
def __init__(self, audiencecontent, *args, **kwargs):
super().__init__(*args, **kwargs)
self._audiencecontent = audiencecontent
self._audiencecontent.register(self)
self._audiencecontent.create(self, self.width, self.height)
def audience_update(self):
self.switch_to()
self._audiencecontent.create(self, self.width, self.height)
def on_resize(self, width, height):
print("New size is: {} x {}".format(width, height))
self._audiencecontent.create(self, width, height)
def on_draw(self):
self.clear()
self.audiencebatch.draw()
def process_config(config):
items = factory.MakeItems(config.get('items'))
setlist = factory.MakeSet(config.get('set'), items)
defaults = config.get('defaults')
fixture_types = factory.MakeFixtureTypes(config.get('fixture_types'))
fixtures = factory.MakeFixtures(config.get('fixtures'), fixture_types)
groups = factory.MakeGroups(config.get('groups'), fixtures)
scenes = factory.MakeScenes(config.get('scenes'), fixtures, groups)
return {
'items' : items,
'setlist' : setlist,
'defaults' : defaults,
'fixture_types' : fixture_types,
'fixtures' : fixtures,
'groups' : groups,
'scenes' : scenes,
}
def load_config(filename):
with open(filename, encoding='utf8') as file:
y = yaml.safe_load(file)
config = process_config(y)
return config
def playback(filename):
data, fs = soundfile.read(filename)
#sounddevice.play(data, fs, device='alsa,hw:CARD=QU16')
sounddevice.play(data, fs, device='QU-16 alsa', mapping=[21,22])
sounddevice.wait()
def main():
parser = argparse.ArgumentParser(prog='gig.py', description='Gig showrunning')
parser.add_argument('filename')
parser.add_argument('-w', '--window', action='store_true', help='Run in a window')
parser.add_argument('-u', '--universe', default=0, type=int, help='DMX universe')
args = parser.parse_args()
config = load_config(args.filename)
display = pyglet.display.get_display()
screens = display.get_screens()
# print(screens)
mainscreen = screens[0]
if len(screens) > 1:
audiencescreen = screens[1]
else:
audiencescreen = None
print(mainscreen)
print(audiencescreen)
eventrunner = EventRunner(gigconfig=config)
audiencecontent = AudienceContent(gigconfig=config, eventrunner=eventrunner)
try:
wrapper = ClientWrapper()
DMX_DATA_SIZE = DMX_UNIVERSE_SIZE
dmx_object = dmx.DMX(args.universe, wrapper, DMX_DATA_SIZE)
except Exception:
dmx_object = None
fixtureoutput = dmx.FixtureOutput(fixtures=[config['fixtures'][x] for x in config['fixtures']], dmx=dmx_object)
pyglet.clock.schedule_interval(fixtureoutput.tick, 1/30.0)
if audiencescreen is not None:
window2 = AudienceWindow(audiencecontent=audiencecontent, fullscreen=False, screen=audiencescreen, resizable=True)
if not args.window:
window2.set_location(audiencescreen.x, audiencescreen.y)
window2.set_size(audiencescreen.width, audiencescreen.height)
window2.set_mouse_position(audiencescreen.x, audiencescreen.y)
window2.set_fullscreen(True)
#window2.set_location(audiencescreen.x, audiencescreen.y)
#event_logger = pyglet.window.event.WindowEventLogger()
#window2.push_handlers(event_logger)
window = MainWindow(gigconfig=config, eventrunner=eventrunner, audiencecontent=audiencecontent, fullscreen=False, screen=mainscreen, resizable=True)
if not args.window:
window.set_location(mainscreen.x, mainscreen.y)
window.set_size(mainscreen.width, mainscreen.height)
window.set_mouse_position(mainscreen.x, mainscreen.y)
window.set_fullscreen(True)
#window.set_location(mainscreen.x, mainscreen.y)
try:
# Let's set up some stuff to inhibit the screensaver
bus = dbus.SessionBus()
saver = bus.get_object('org.freedesktop.ScreenSaver', '/ScreenSaver')
saver_interface = dbus.Interface(saver, dbus_interface='org.freedesktop.ScreenSaver')
# now we can inhibit the screensaver
cookie=saver_interface.Inhibit("gig.py", "Don't blank the screen during the gig")
except Exception:
pass
pyglet.app.run()
if __name__ == "__main__":
main()