summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranziska Kunsmann <hi@kunsmann.eu>2023-01-11 06:57:11 +0100
committerFranziska Kunsmann <hi@kunsmann.eu>2023-01-11 06:57:11 +0100
commitbb7c63ef130b00003c926b168aee56a3b32eb6e0 (patch)
treec8375c19f1bde9864c5e706d1402c533ce02c9d5
initial commit
-rw-r--r--.gitignore1
-rw-r--r--config.toml15
-rw-r--r--gui.py79
-rwxr-xr-xmain.py40
-rw-r--r--requirements.txt4
-rw-r--r--switcher.py103
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
diff --git a/gui.py b/gui.py
new file mode 100644
index 0000000..52375b6
--- /dev/null
+++ b/gui.py
@@ -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()
diff --git a/main.py b/main.py
new file mode 100755
index 0000000..39317c6
--- /dev/null
+++ b/main.py
@@ -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,
+ )