summaryrefslogtreecommitdiff
path: root/voctocore/lib/quadmix.py
diff options
context:
space:
mode:
Diffstat (limited to 'voctocore/lib/quadmix.py')
-rw-r--r--voctocore/lib/quadmix.py172
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)