Initial code commit
This commit is contained in:
398
gig.py
Executable file
398
gig.py
Executable file
@@ -0,0 +1,398 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
|
||||
import sounddevice
|
||||
import soundfile
|
||||
|
||||
import pyglet
|
||||
from pyglet.window import key
|
||||
|
||||
import factory
|
||||
import display
|
||||
|
||||
import yaml
|
||||
import time
|
||||
|
||||
import dbus
|
||||
|
||||
class EventRunner:
|
||||
def __init__(self, gigconfig):
|
||||
self._gigconfig = gigconfig
|
||||
self._running = False
|
||||
self._selected = None
|
||||
self._playing = None
|
||||
self._active = []
|
||||
self._currentitem = None
|
||||
|
||||
def set_audiencecontent(self, audiencecontent):
|
||||
for item in self._gigconfig['items']:
|
||||
self._gigconfig['items'][item].set_audiencecontent(audiencecontent)
|
||||
|
||||
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:
|
||||
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 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['items'])
|
||||
setlist = factory.MakeSet(config['set'], items)
|
||||
preshow = factory.MakeSet(config['preshow'], items)
|
||||
if 'defaults' in config:
|
||||
defaults = config['defaults']
|
||||
else:
|
||||
defaults = {}
|
||||
return {'items' : items, 'setlist' : setlist, 'preshow' : preshow}
|
||||
|
||||
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')
|
||||
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)
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user