summaryrefslogtreecommitdiff
path: root/voctocore/lib/quadmix.py
blob: 5c43a670b436d3390a270dfd5b588a958ad91c54 (plain)
  1. #!/usr/bin/python3
  2. import math, logging
  3. from gi.repository import GLib, Gst
  4. from lib.helper import iteratorHelper
  5. from lib.config import Config
  6. class QuadMix(Gst.Bin):
  7. log = logging.getLogger('QuadMix')
  8. previewbins = []
  9. mixerpads = []
  10. def __init__(self):
  11. super().__init__()
  12. caps = Gst.Caps.from_string(Config.get('mix', 'monitorcaps'))
  13. self.log.debug('parsing monitorcaps from config: %s', caps.to_string())
  14. struct = caps.get_structure(0)
  15. self.monitorSize = [struct.get_int('width')[1], struct.get_int('height')[1]]
  16. self.bgsrc = Gst.ElementFactory.make('videotestsrc', 'bgsrc')
  17. self.mixer = Gst.ElementFactory.make('videomixer', 'mixer')
  18. self.scale = Gst.ElementFactory.make('videoscale', 'scale')
  19. self.add(self.bgsrc)
  20. self.add(self.mixer)
  21. self.add(self.scale)
  22. self.bgsrc.link_filtered(self.mixer, caps)
  23. self.mixer.link_filtered(self.scale, caps)
  24. self.bgsrc.set_property('pattern', 'solid-color')
  25. self.bgsrc.set_property('foreground-color', 0x808080)
  26. self.add_pad(
  27. Gst.GhostPad.new('src', self.scale.get_static_pad('src'))
  28. )
  29. def request_mixer_pad(self):
  30. previewbin = QuadMixPreview()
  31. self.add(previewbin)
  32. self.previewbins.append(previewbin)
  33. srcpad = previewbin.get_static_pad('src')
  34. sinkpad = previewbin.get_static_pad('sink')
  35. mixerpad = self.mixer.get_request_pad('sink_%u')
  36. self.mixerpads.append(mixerpad)
  37. srcpad.link(mixerpad)
  38. ghostpad = Gst.GhostPad.new(mixerpad.get_name(), sinkpad)
  39. self.add_pad(ghostpad)
  40. return ghostpad
  41. def finalize(self):
  42. self.log.debug('all sources added, calculating layout')
  43. # number of placed sources
  44. count = len(self.previewbins)
  45. # coordinate of the cell where we place the next video
  46. place = [0, 0]
  47. # number of cells in the quadmix-monitor
  48. grid = [0, 0]
  49. grid[0] = math.ceil(math.sqrt(count))
  50. grid[1] = math.ceil(count / grid[0])
  51. # size of each cell in the quadmix-monitor
  52. cellSize = (
  53. self.monitorSize[0] / grid[0],
  54. self.monitorSize[1] / grid[1]
  55. )
  56. # report calculation results
  57. 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',
  58. count, grid[0], grid[1], self.monitorSize[0], self.monitorSize[1], cellSize[0], cellSize[1])
  59. # iterate over all video-sources
  60. for idx, previewbin in enumerate(self.previewbins):
  61. # ...
  62. srcpad = previewbin.get_static_pad('src')
  63. mixerpad = self.mixerpads[idx]
  64. # query the video-source caps and extract its size
  65. caps = srcpad.query_caps(None)
  66. capsstruct = caps.get_structure(0)
  67. srcSize = (
  68. capsstruct.get_int('width')[1],
  69. capsstruct.get_int('height')[1],
  70. )
  71. # calculate the ideal scale factor and scale the sizes
  72. f = max(srcSize[0] / cellSize[0], srcSize[1] / cellSize[1])
  73. scaleSize = (
  74. srcSize[0] / f,
  75. srcSize[1] / f,
  76. )
  77. # calculate the top/left coordinate
  78. coord = (
  79. place[0] * cellSize[0] + (cellSize[0] - scaleSize[0]) / 2,
  80. place[1] * cellSize[1] + (cellSize[1] - scaleSize[1]) / 2,
  81. )
  82. 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)',
  83. idx, srcSize[0], srcSize[1], f, scaleSize[0], scaleSize[1], cellSize[0], cellSize[1], place[0], place[1], coord[0], coord[1])
  84. # request a pad from the quadmixer and configure x/y position
  85. mixerpad.set_property('xpos', round(coord[0]))
  86. mixerpad.set_property('ypos', round(coord[1]))
  87. previewbin.set_size(scaleSize)
  88. previewbin.set_idx(idx)
  89. # increment grid position
  90. place[0] += 1
  91. if place[0] >= grid[0]:
  92. place[1] += 1
  93. place[0] = 0
  94. def set_active(self, target):
  95. for idx, previewbin in enumerate(self.previewbins):
  96. previewbin.set_active(target == idx)
  97. class QuadMixPreview(Gst.Bin):
  98. log = logging.getLogger('QuadMixPreview')
  99. strokeWidth = 5
  100. def __init__(self):
  101. super().__init__()
  102. self.scale = Gst.ElementFactory.make('videoscale', 'scale')
  103. self.caps = Gst.ElementFactory.make('capsfilter', 'caps')
  104. self.cropbox = Gst.ElementFactory.make('videobox', 'cropbox')
  105. self.strokebox = Gst.ElementFactory.make('videobox', 'strokebox')
  106. self.textoverlay = Gst.ElementFactory.make('textoverlay', 'textoverlay')
  107. self.add(self.scale)
  108. self.add(self.caps)
  109. self.add(self.cropbox)
  110. self.add(self.strokebox)
  111. self.add(self.textoverlay)
  112. self.strokebox.set_property('fill', 'green')
  113. self.textoverlay.set_property('color', 0xFFFFFFFF)
  114. self.textoverlay.set_property('halignment', 'left')
  115. self.textoverlay.set_property('valignment', 'top')
  116. self.textoverlay.set_property('xpad', 10)
  117. self.textoverlay.set_property('ypad', 5)
  118. self.textoverlay.set_property('font-desc', 'sans 35')
  119. self.scale.link(self.caps)
  120. self.caps.link(self.cropbox)
  121. self.cropbox.link(self.strokebox)
  122. self.strokebox.link(self.textoverlay)
  123. self.set_active(False)
  124. # Add Ghost Pads
  125. self.add_pad(
  126. Gst.GhostPad.new('sink', self.scale.get_static_pad('sink'))
  127. )
  128. self.add_pad(
  129. Gst.GhostPad.new('src', self.textoverlay.get_static_pad('src'))
  130. )
  131. def set_size(self, scaleSize):
  132. caps = Gst.Caps.new_empty_simple('video/x-raw')
  133. caps.set_value('width', round(scaleSize[0]))
  134. caps.set_value('height', round(scaleSize[1]))
  135. self.caps.set_property('caps', caps)
  136. def set_idx(self, idx):
  137. self.textoverlay.set_property('text', str(idx))
  138. def set_active(self, active):
  139. self.log.info("switching active-state to %u", active)
  140. for side in ('top', 'left', 'right', 'bottom'):
  141. self.cropbox.set_property(side, self.strokeWidth if active else 0)
  142. self.strokebox.set_property(side, -self.strokeWidth if active else 0)
  143. def set_color(self, color):
  144. self.strokebox.set_property('fill', color)