From 6626e4c78e441ed37d92000de0a6f77e0b337b44 Mon Sep 17 00:00:00 2001 From: Arnei Date: Thu, 30 Oct 2025 13:46:08 +0100 Subject: [PATCH] Add "inputs" support to pyCA Adds support for sending configuration to the agent configuration endpoint in Opencast. Adds a new config option "inputs". It allows users to specifiy for a given event which tracks they would like to show up in the final recording. For example, if the agent is capable of recording both "presenter/source" and "presentation/source", a user may select only "presentation/source" as input. --- etc/pyca.conf | 9 ++++++- pyca/agentstate.py | 4 +++- pyca/config.py | 1 + pyca/ingest.py | 58 +++++++++++++++++++++++++++++++++++++++++----- pyca/utils.py | 27 +++++++++++++++++++++ 5 files changed, 91 insertions(+), 8 deletions(-) diff --git a/etc/pyca.conf b/etc/pyca.conf index c4a78864..a04f63ba 100644 --- a/etc/pyca.conf +++ b/etc/pyca.conf @@ -40,6 +40,13 @@ # Default: sqlite:///pyca.db #database = sqlite:///pyca.db +# Selectable inputs +# A track will only be ingested if the flavor of the track matches one of the +# selected inputs. +# If not specified, all inputs are always selected. +# Default: inputs = '' +#inputs = 'presenter/source, presentation/source' + [capture] @@ -170,7 +177,7 @@ command = 'ffmpeg -nostats -re -f lavfi -r 25 -i testsrc -f lavfi -i si # org.opencastproject.server.url setting of Opencast. # Type: string # Default: https://develop.opencast.org -url = 'https://develop.opencast.org' +url = 'http://localhost:8080' # Analogue of -k, --insecure option in curl. Allows insercure SSL connections # while using HTTPS on the server. diff --git a/pyca/agentstate.py b/pyca/agentstate.py index 0a8b6ca1..032085b1 100644 --- a/pyca/agentstate.py +++ b/pyca/agentstate.py @@ -7,7 +7,8 @@ :license: LGPL – see license.lgpl for more details. ''' -from pyca.utils import set_service_status, update_agent_state, timestamp +from pyca.utils import set_service_status, update_agent_state, timestamp, \ + update_agent_config from pyca.utils import terminate from pyca.config import config from pyca.db import Service, ServiceStatus @@ -34,6 +35,7 @@ def control_loop(): if not terminate(): update_agent_state() + update_agent_config() logger.info('Shutting down agentstate service') set_service_status(Service.AGENTSTATE, ServiceStatus.STOPPED) diff --git a/pyca/config.py b/pyca/config.py index fe5c591e..e0ae8464 100644 --- a/pyca/config.py +++ b/pyca/config.py @@ -20,6 +20,7 @@ cal_lookahead = integer(min=0, default=14) backup_mode = boolean(default=false) database = string(default='sqlite:///pyca.db') +inputs = force_list(default=list()) [capture] directory = string(default='./recordings') diff --git a/pyca/ingest.py b/pyca/ingest.py index 6b585f2f..093c5785 100644 --- a/pyca/ingest.py +++ b/pyca/ingest.py @@ -23,6 +23,49 @@ notify = sdnotify.SystemdNotifier() +def inputs_from_event(event): + ''' Parse inputs from event attachment + ''' + + inputs = [] + for attachment in event.get_data().get('attach'): + data = attachment.get('data') + if (attachment.get('x-apple-filename') == + 'org.opencastproject.capture.agent.properties'): + for prop in data.split('\n'): + if prop.startswith('capture.device.names'): + param = prop.split('=', 1) + inputs = param[1].split(',') + break + return inputs + + +def is_track_selected(event, flavor): + ''' If inputs are configured, check if track was selected + ''' + + # inputs not configured -> add all + inputs_conf = config('agent', 'inputs') + if not inputs_conf: + logger.info('No inputs in config') + return True + + # Get inputs from event attachment + inputs_event = inputs_from_event(event) + + # inputs not in attachment -> add all + if not inputs_event: + logger.info('No inputs in schedule') + return True + + # input is selected + if flavor in inputs_event: + return True + + # input is not selected + return False + + def get_config_params(properties): '''Extract the set of configuration parameters from the properties attached to the schedule @@ -87,12 +130,15 @@ def ingest(event): # add track for (flavor, track) in event.get_tracks(): - logger.info('Adding track (%s -> %s)', flavor, track) - track = track.encode('ascii', 'ignore') - fields = [('mediaPackage', mediapackage), ('flavor', flavor), - ('BODY1', (pycurl.FORM_FILE, track))] - mediapackage = http_request(service_url + '/addTrack', fields, - timeout=0) + if is_track_selected(event, flavor): + logger.info('Adding track (%s -> %s)', flavor, track) + track = track.encode('ascii', 'ignore') + fields = [('mediaPackage', mediapackage), ('flavor', flavor), + ('BODY1', (pycurl.FORM_FILE, track))] + mediapackage = http_request(service_url + '/addTrack', fields, + timeout=0) + else: + logger.info('Not adding track (%s -> %s)', flavor, track) # ingest logger.info('Ingest recording') diff --git a/pyca/utils.py b/pyca/utils.py index 8acdd456..29e186cf 100644 --- a/pyca/utils.py +++ b/pyca/utils.py @@ -261,6 +261,33 @@ def update_agent_state(): register_ca(status=status) +def update_agent_config(): + '''Update the current agent configuration in opencast. + ''' + + if config('agent', 'backup_mode'): + return + service_endpoint = service('capture.admin') + if not service_endpoint: + logger.warning('Missing endpoint for updating agent status.') + return + + inputs = ",".join(config('agent', 'inputs')) + params = [( + 'configuration', + '{\'capture.device.names\': \'' + inputs + '\'}' + )] + + name = urlquote(config('agent', 'name').encode('utf-8'), safe='') + url = f'{service_endpoint[0]}/agents/{name}/configuration' + try: + response = http_request(url, params).decode('utf-8') + if response: + logger.info(response) + except pycurl.error as e: + logger.warning('Could not set configuration of agent %s: %s', name, e) + + def terminate(shutdown=None): '''Mark process as to be terminated. '''