diff options
Diffstat (limited to 'voctocore/lib/quadmix.py')
-rw-r--r-- | voctocore/lib/quadmix.py | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/voctocore/lib/quadmix.py b/voctocore/lib/quadmix.py new file mode 100644 index 0000000..5115919 --- /dev/null +++ b/voctocore/lib/quadmix.py @@ -0,0 +1,172 @@ +#!/usr/bin/python3 +import math, logging +from gi.repository import GLib, Gst + +from lib.helper import iteratorHelper +from lib.config import Config + +class QuadMix(Gst.Bin): + log = logging.getLogger('QuadMix') + sources = [] + previewbins = [] + + def __init__(self): + super().__init__() + + caps = Gst.Caps.from_string(Config.get('mix', 'monitorcaps')) + self.log.debug('parsing monitorcaps from config: %s', caps.to_string()) + struct = caps.get_structure(0) + + self.monitorSize = [struct.get_int('width')[1], struct.get_int('height')[1]] + + self.bgsrc = Gst.ElementFactory.make('videotestsrc', 'bgsrc') + self.mixer = Gst.ElementFactory.make('videomixer', 'mixer') + self.scale = Gst.ElementFactory.make('videoscale', 'scale') + + self.add(self.bgsrc) + self.add(self.mixer) + self.add(self.scale) + + self.bgsrc.link_filtered(self.mixer, caps) + self.mixer.link_filtered(self.scale, caps) + + self.bgsrc.set_property('pattern', 'solid-color') + self.bgsrc.set_property('foreground-color', 0x808080) + + self.add_pad( + Gst.GhostPad.new('src', self.scale.get_static_pad('src')) + ) + + # I don't know how to create a on-request ghost-pad + def add_source(self, src): + self.log.info('adding source %s', src.get_name()) + self.sources.append(src) + + def finalize(self): + self.log.debug('all sources added, calculating layout') + + # number of placed sources + count = len(self.sources) + + # coordinate of the cell where we place the next video + place = [0, 0] + + # number of cells in the quadmix-monitor + grid = [0, 0] + grid[0] = math.ceil(math.sqrt(count)) + grid[1] = math.ceil(count / grid[0]) + + # size of each cell in the quadmix-monitor + cellSize = ( + self.monitorSize[0] / grid[0], + self.monitorSize[1] / grid[1] + ) + + # report calculation results + self.log.info('showing %u videosources in a %u×%u grid in a %u×%u px window, which gives cells of %u×%u px per videosource', + count, grid[0], grid[1], self.monitorSize[0], self.monitorSize[1], cellSize[0], cellSize[1]) + + # iterate over all video-sources + for idx, videosource in enumerate(self.sources): + # query the video-source caps and extract its size + caps = videosource.get_static_pad('src').query_caps(None) + capsstruct = caps.get_structure(0) + srcSize = ( + capsstruct.get_int('width')[1], + capsstruct.get_int('height')[1], + ) + + # calculate the ideal scale factor and scale the sizes + f = max(srcSize[0] / cellSize[0], srcSize[1] / cellSize[1]) + scaleSize = ( + srcSize[0] / f, + srcSize[1] / f, + ) + + # calculate the top/left coordinate + coord = ( + place[0] * cellSize[0] + (cellSize[0] - scaleSize[0]) / 2, + place[1] * cellSize[1] + (cellSize[1] - scaleSize[1]) / 2, + ) + + self.log.info('placing videosource %u of size %u×%u scaled by %u to %u×%u in a cell %u×%u px cell (%u/%u) at position (%u/%u)', + idx, srcSize[0], srcSize[1], f, scaleSize[0], scaleSize[1], cellSize[0], cellSize[1], place[0], place[1], coord[0], coord[1]) + + # request a pad from the quadmixer and configure x/y position + sinkpad = self.mixer.get_request_pad('sink_%u') + sinkpad.set_property('xpos', round(coord[0])) + sinkpad.set_property('ypos', round(coord[1])) + + # create a sub-preview-bin + previewbin = QuadMixPreview(idx, scaleSize) + self.add(previewbin) + self.previewbins.append(previewbin) + + # link videosource to input of previewbin + videosource.link(previewbin) + + # link the output of the preview-bin to the mixer + previewbin.get_static_pad('src').link(sinkpad) + + # increment grid position + place[0] += 1 + if place[0] >= grid[0]: + place[1] += 1 + place[0] = 0 + + def set_active(self, target): + for idx, previewbin in enumerate(self.previewbins): + previewbin.set_active(target == idx) + +class QuadMixPreview(Gst.Bin): + log = logging.getLogger('QuadMixPreview') + strokeWidth = 5 + + def __init__(self, idx, scaleSize): + super().__init__() + + self.scale = Gst.ElementFactory.make('videoscale', 'scale') + self.cropbox = Gst.ElementFactory.make('videobox', 'cropbox') + self.strokebox = Gst.ElementFactory.make('videobox', 'strokebox') + self.textoverlay = Gst.ElementFactory.make('textoverlay', 'textoverlay') + + self.add(self.scale) + self.add(self.cropbox) + self.add(self.strokebox) + self.add(self.textoverlay) + + caps = Gst.Caps.new_empty_simple('video/x-raw') + caps.set_value('width', round(scaleSize[0])) + caps.set_value('height', round(scaleSize[1])) + + self.strokebox.set_property('fill', 'green') + + self.textoverlay.set_property('color', 0xFFFFFFFF) + self.textoverlay.set_property('halignment', 'left') + self.textoverlay.set_property('valignment', 'top') + self.textoverlay.set_property('xpad', 10) + self.textoverlay.set_property('ypad', 5) + self.textoverlay.set_property('font-desc', 'sans 35') + + self.scale.link_filtered(self.cropbox, caps) + self.cropbox.link(self.strokebox) + self.strokebox.link(self.textoverlay) + + self.set_active(False) + + # Add Ghost Pads + self.add_pad( + Gst.GhostPad.new('sink', self.scale.get_static_pad('sink')) + ) + self.add_pad( + Gst.GhostPad.new('src', self.textoverlay.get_static_pad('src')) + ) + + def set_active(self, active): + self.log.info("switching active-state to %u", active) + for side in ('top', 'left', 'right', 'bottom'): + self.cropbox.set_property(side, self.strokeWidth if active else 0) + self.strokebox.set_property(side, -self.strokeWidth if active else 0) + + def setColor(self, color): + self.strokebox.set_property('fill', color) |