#!/usr/bin/python3 # # flufigut # # flufigut, the flumotion configuration utility, is a simple tool # that generates flumotion configuration files using pyhton jinja2 # template engine. flufigut generates planet.xml and worker.xml # files from configuration templates and an easy to understand # representation of the flow structure written in json or yaml. # # # Copyright (C) 2018 Christian Pointner # # This file is part of flufigut. # # flufigut is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # any later version. # # flufigut is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with flufigut. If not, see . # import string import random import sys import yaml # from jinja2 import Environment, FileSystemLoader # helper functions ############################################ # def rand_string(size=8, chars=string.ascii_lowercase + string.ascii_uppercase + string.digits): return ''.join(random.choice(chars) for x in range(size)) # a flufigut stream description ############################### # class Description: def __init__(self): self.globals = {} self.inputs = {} self.muxes = {} self.streams = {} self.records = {} def _sanity_check(self): # TODO: add more sanity checks components = {} for _, worker in self.globals['workers'].items(): for c in worker: if c in components: raise Exception("ERROR: component '%s' is assigned to more than one worker!" % c) else: components[c] = 1 def parse(self, config_file): cf = open(config_file, 'r') config = yaml.load(cf) cf.close() self.globals = config['globals'] self.inputs = config['inputs'] self.muxes = config['muxes'] self.streams = config['streams'] if 'records' in config: self.records = config['records'] return self._sanity_check() # a flumtion planet configuration ############################# # class Planet: def __init__(self): self.atmosphere = {} self.flow = {} # # inputs def __set_input_properties(self, comp_name, props, globals): for prop in props.keys(): if prop == 'resolution': self.flow['inputs'][comp_name]['properties']['width'] = globals['resolutions'][props[prop]]['width'] self.flow['inputs'][comp_name]['properties']['height'] = globals['resolutions'][props[prop]]['height'] self.flow['inputs'][comp_name]['properties']['framerate'] = globals['resolutions'][props[prop]]['rate'] else: self.flow['inputs'][comp_name]['properties'][prop] = props[prop] def _generate_inputs(self, inputs, globals): self.flow['inputs'] = {} master_cnt = 0 for source, input in inputs.items(): comp_name = 'input-%s' % source comp_desc = 'capture raw data from %s' % (source) self.flow['inputs'][comp_name] = { 'type': input['type'], 'desc': comp_desc, 'worker': None, 'master': input['master'], 'properties': {}, } if input['master']: master_cnt += 1 self.__set_input_properties(comp_name, input['properties'], globals) if master_cnt == 0: raise Exception("You have not configured any master clock device!") elif master_cnt > 1: raise Exception("You have configured multiple master clock devices!") # # muxes def __generate_audio_resampler(self, mux, format, profile, inputs, globals): source = mux['audio'].split(':')[0] input_samplerate = inputs[source]['properties']['samplerate'] if 'samplerate' not in globals['formats'][format]: return target_samplerate = globals['formats'][format]['samplerate'] if target_samplerate == input_samplerate: return feeder = 'input-%s' % (mux['audio']) comp_name = 'resample-%s-%s' % (source, target_samplerate) comp_desc = 'resample audio from % s to % s Hz' % (source, target_samplerate) self.flow['inputs'][comp_name] = { 'type': 'audio-resample', 'desc': comp_desc, 'worker': None, 'feeder': feeder, 'properties': { 'samplerate': target_samplerate, }, } def __generate_video_resizer(self, mux, format, profile, inputs, globals): source = mux['video'].split(':')[0] input_resolution = inputs[source]['properties']['resolution'] if 'video' not in globals['profiles'][profile]: return if input_resolution == "": raise Exception("format definition needs video but no video input given") target_resolution = globals['profiles'][profile]['video'] if target_resolution == input_resolution: return if globals['resolutions'][target_resolution]['rate'] != globals['resolutions'][input_resolution]['rate']: raise Exception("ERROR: video rate conversion is not yet supported!!!") feeder = 'input-%s' % (mux['video']) comp_name = 'resize-%s-%s' % (source, target_resolution) comp_desc = 'resize video from %s to %sx%s' % (source, globals['resolutions'][target_resolution]['width'], globals['resolutions'][target_resolution]['height']), self.flow['inputs'][comp_name] = { 'type': 'video-resize', 'desc': comp_desc, 'worker': None, 'feeder': feeder, 'properties': { 'width': globals['resolutions'][target_resolution]['width'], 'height': globals['resolutions'][target_resolution]['height'], }, } def __generate_audio_encoder(self, mux, format, profile, inputs, globals): encoder = globals['formats'][format]['audio'] bitrate = globals['profiles'][profile]['audio'] source = mux['audio'].split(':')[0] input_samplerate = inputs[source]['properties']['samplerate'] target_samplerate = input_samplerate if 'samplerate' in globals['formats'][format]: target_samplerate = globals['formats'][format]['samplerate'] feeder = 'input-%s' % (mux['audio']) if target_samplerate != input_samplerate: feeder = 'resample-%s-%s' % (source, target_samplerate) comp_name = 'encode-%s-%s-%i-%i' % (source, encoder, bitrate, target_samplerate) comp_desc = '%s encoder for %i kbit/s @ %i Hz, from %s' % (encoder, bitrate, target_samplerate, source), if bitrate == 0: comp_name = 'encode-%s-%s-%i' % (source, encoder, target_samplerate) comp_desc = '%s encoder @ %i Hz, from %s' % (encoder, target_samplerate, source), if comp_name in self.flow['encoders_audio']: return comp_name self.flow['encoders_audio'][comp_name] = { 'type': '%s-encode' % encoder, 'desc': comp_desc, 'worker': None, 'feeder': feeder, 'properties': { 'bitrate': bitrate, }, } return comp_name def __generate_video_encoder(self, mux, format, profile, inputs, globals): encoder = globals['formats'][format]['video'] target_resolution = globals['profiles'][profile]['video'] bitrate = globals['bitrates'][encoder][target_resolution] source = mux['video'].split(':')[0] input_resolution = inputs[source]['properties']['resolution'] feeder = 'input-%s' % (mux['video']) if target_resolution != input_resolution: feeder = 'resize-%s-%s' % (source, target_resolution) comp_name = 'encode-%s-%s-%s' % (source, encoder, target_resolution) comp_desc = '%s encoder for %sx%s, from %s' % (encoder, globals['resolutions'][target_resolution]['width'], globals['resolutions'][target_resolution]['height'], source), if comp_name in self.flow['encoders_video']: return comp_name self.flow['encoders_video'][comp_name] = { 'type': '%s-encode' % encoder, 'desc': comp_desc, 'worker': None, 'feeder': feeder, 'properties': { 'bitrate': bitrate, }, } return comp_name def __generate_muxer(self, mux_name, format, profile, globals, audio_encoder, video_encoder): muxer = globals['formats'][format]['muxer'] comp_name = 'mux-%s-%s-%s' % (mux_name, format, profile) comp_desc = '%s muxer for %s, profile %s' % (format, mux_name, profile), self.flow['muxers'][comp_name] = { 'type': '%s-mux' % muxer, 'desc': comp_desc, 'worker': None, 'feeder_audio': audio_encoder, 'feeder_video': video_encoder, 'properties': {}, } def _generate_muxes(self, muxes, inputs, globals): self.flow['encoders_audio'] = {} self.flow['encoders_video'] = {} self.flow['muxers'] = {} for mux_name, mux in muxes.items(): for format in mux['formats'].keys(): for profile in mux['formats'][format]: audio_encoder = None video_encoder = None if 'audio' in mux: self.__generate_audio_resampler(mux, format, profile, inputs, globals) audio_encoder = self.__generate_audio_encoder(mux, format, profile, inputs, globals) if 'video' in mux: self.__generate_video_resizer(mux, format, profile, inputs, globals) video_encoder = self.__generate_video_encoder(mux, format, profile, inputs, globals) self.__generate_muxer(mux_name, format, profile, globals, audio_encoder, video_encoder) # # all def generate(self, desc): self._generate_inputs(desc.inputs, desc.globals) self._generate_muxes(desc.muxes, desc.inputs, desc.globals) # Main ######################################################## # if __name__ == '__main__': import traceback import pprint __pp = pprint.PrettyPrinter(indent=4, width=160) if len(sys.argv) <= 1: print("ERROR: No configuration file given") sys.exit(-1) config_file = sys.argv[1] ret = 0 try: d = Description() d.parse(config_file) p = Planet() p.generate(d) print("****************************************************") print("** atmosphere **") print("**") __pp.pprint(p.atmosphere) print("**") print("**************************") print("** planet **") print("**") __pp.pprint(p.flow) except Exception as e: print("ERROR: while running app: %s" % e) print(traceback.format_exc()) sys.exit(1) sys.exit(ret)