Initial code commit

This commit is contained in:
2025-08-05 01:10:28 +01:00
parent 8052597e84
commit 8fa467cb93
7 changed files with 1073 additions and 1 deletions

398
gig.py Executable file
View 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()