diff options
| author | Franziska Kunsmann <hi@kunsmann.eu> | 2023-01-11 06:57:11 +0100 |
|---|---|---|
| committer | Franziska Kunsmann <hi@kunsmann.eu> | 2023-01-11 06:57:11 +0100 |
| commit | bb7c63ef130b00003c926b168aee56a3b32eb6e0 (patch) | |
| tree | c8375c19f1bde9864c5e706d1402c533ce02c9d5 | |
initial commit
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | config.toml | 15 | ||||
| -rw-r--r-- | gui.py | 79 | ||||
| -rwxr-xr-x | main.py | 40 | ||||
| -rw-r--r-- | requirements.txt | 4 | ||||
| -rw-r--r-- | switcher.py | 103 |
6 files changed, 242 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7275bb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv/ diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..f2cd17f --- /dev/null +++ b/config.toml @@ -0,0 +1,15 @@ +[atem] +ip = '172.19.138.254' +video_mode = '1080p50' + +[atem.settings] + + +[logging] +# see https://docs.python.org/3/library/logging.html#levels +level = 10 +format = '%(name)25s [%(levelname)-8s] %(message)s' + +[gtk-settings] +gtk-theme-name = "Adwaita" +gtk-application-prefer-dark-theme = true @@ -0,0 +1,79 @@ +import gi + +gi.require_version('Gtk', '3.0') +import logging + +from gi.repository import GObject, Gtk + +BUTTON_SPACING = 10 + + +class PyATEMSwitcherGui(): + def __init__(self, config, switcher): + self.log = logging.getLogger('GUI') + self.config = config + self.window = Gtk.Window() + self.window.connect("destroy", Gtk.main_quit) + self.switcher = switcher + self.switcher.on_connect(self._switcher_connected) + self.switcher.on_connect_attempt(self._switcher_connect_attempt) + self.switcher.on_disconnect(self._switcher_disconnected) + + self.window.set_border_width(BUTTON_SPACING) + + self.header = Gtk.HeaderBar() + self.header.props.title = 'PyATEMSwitcherGui: Idle' + self.window.set_titlebar(self.header) + + self.buttons = {} + self.box = None + + # TODO use connection hooks + self._switcher_connected({}) + + def _button_clicked(self, button, name): + # TODO actually do something + self.header.props.title = f'PyATEMSwitcherGui: {name}' + self.log.info(f'Button {name} was pressed') + + def _switcher_connected(self, params): + self.box = Gtk.FlowBox() + self.box.set_column_spacing(BUTTON_SPACING) + self.box.set_max_children_per_line(2) + self.box.set_row_spacing(BUTTON_SPACING) + self.box.set_selection_mode(Gtk.SelectionMode.NONE) + self.box.set_valign(Gtk.Align.START) + + # TODO get input list from switcher + for i in range(1, 7): + self.buttons[f'input{i}'] = Gtk.Button.new_with_label( + f'input{i}' + ) + self.buttons[f'input{i}'].connect( + 'clicked', + self._button_clicked, + f'input{i}', + ) + self.box.add(self.buttons[f'input{i}']) + + self.window.add(self.box) + + def _switcher_connect_attempt(self, params): + pass + + def _switcher_disconnected(self, params): + if self.box is not None: + self.window.remove(self.box) + self.box = None + self.buttons = {} + + def _switcher_ping(self): + # TODO actually do something here + self.log.debug('_switcher_ping()') + return True + + def main_loop(self): + # TODO connect to switcher + self.window.show_all() + GObject.timeout_add(500, self._switcher_ping) + Gtk.main() @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import gi + +gi.require_version('Gtk', '3.0') + +import logging +from os import environ + +from gi.repository import Gtk +from rtoml import load + +from gui import PyATEMSwitcherGui +from switcher import PyATEMSwitcher + +LOGLEVELS = { + 'debug': logging.DEBUG, + 'error': logging.ERROR, + 'info': logging.INFO, + 'warn': logging.WARNING, +} + + +def main(): + with open(environ.get('PYATEMSWITCHER_CONFIG', 'config.toml')) as f: + config = load(f) + + settings = Gtk.Settings.get_default() + for k, v in config.get('gtk-settings').items(): + settings.set_property(k, v) # TODO find out if we can just pass a dict somewhere + + logging.basicConfig(**config.get('logging', {})) + + switcher = PyATEMSwitcher(config) + + gui = PyATEMSwitcherGui(config, switcher) + gui.main_loop() + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dbf9cfe --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +PyATEMMax==1.0b9 +pycairo==1.23.0 +PyGObject==3.42.2 +rtoml==0.9.0 diff --git a/switcher.py b/switcher.py new file mode 100644 index 0000000..4c34a1c --- /dev/null +++ b/switcher.py @@ -0,0 +1,103 @@ +import logging + +import PyATEMMax +from PyATEMMax.ATEMProtocolEnums import ATEMVideoModeFormats + +VIDEO_FORMATS = { + f[1:] + for f in dir(ATEMVideoModeFormats) + if f.startswith('f') +} + + +class PyATEMSwitcher: + def __init__(self, config): + self.atem = PyATEMMax.ATEMMax() + self.config = config.get('atem', {}) + self.log = logging.getLogger('Switcher') + + self._connect_subscribers = [] + self._connect_attempt_subscribers = [] + self._disconnect_subscribers = [] + + self._validate_config() + + self.atem.registerEvent( + self.atem.atem.events.connect, + self._on_connect, + ) + self.atem.registerEvent( + self.atem.atem.events.connectAttempt, + self._on_connect_attempt, + ) + self.atem.registerEvent( + self.atem.atem.events.disconnect, + self._on_disconnect, + ) + + def _on_connect(self, params): + logging.debug(f'_on_connect({repr(params)})') + self._push_config() + for callback in self._connect_subscribers: + callback(params) + + def _on_connect_attempt(self, params): + logging.debug(f'_on_connect_attempt({repr(params)})') + for callback in self._connect_attempt_subscribers: + callback(params) + + def _on_disconnect(self, params): + logging.debug(f'_on_disconnect({repr(params)})') + for callback in self._disconnect_subscribers: + callback(params) + + def _push_config(self): + conf = self.config.get('settings', {}) + + # TODO setInputLongName + # TODO setInputShortName + # TODO media upload to MP1 + + if 'video_mode' in self.config: + video_mode = getattr( + ATEMVideoModeFormats, + 'f'+self.config['video_format'], + ) + if self.atem.videoMode.format != video_mode: + self.atem.setVideoModeFormat(video_mode) + + def _validate_config(self): + if 'ip' not in self.config: + raise KeyError('Please set ATEM IP in config!') + if ( + 'video_mode' in self.config + and self.config['video_mode'] not in VIDEO_FORMATS + ): + raise ValueError( + f'ATEM video_mode {self.config["video_mode"]} ' + 'is not a valid video mode, must be one of: ' + f'{", ".join(sorted(VIDEO_FORMATS))}' + ) + + def connect(self): + self.log.info('Initiating connection to switcher') + self.atem.connect(self.config['ip']) + + def disconnect(self): + raise NotImplementedError + + def on_connect(self, callback): + self._connect_subscribers.append(callback) + + def on_connect_attempt(self, callback): + self._connect_attempt_subscribers.append(callback) + + def on_disconnect(self, callback): + self._disconnect_subscribers.append(callback) + + def trans(self, input): + self.log.debug(f'hehehehe trans({repr(input)})') + self.atem.setProgramInputVideoSource( + # TODO mixEffect, + input, + ) |
