Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial implementation of program/preview #356

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ end_of_line = lf
insert_final_newline = true
charset = utf-8

[*.ui]
indent_size = 2

[*.py]
indent_style = space
indent_size = 4
Expand Down
5 changes: 5 additions & 0 deletions vocto/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,9 @@ def getSRTServerEnabled(self):
def getPreviewsEnabled(self):
return self.getboolean('previews', 'enabled', fallback=False)

def getPreviewMixEnabled(self):
return self.getboolean('mix', 'preview', fallback=False)

def getAVRawOutputEnabled(self):
return self.getboolean('avrawoutput', 'enabled', fallback=True)

Expand Down Expand Up @@ -478,6 +481,8 @@ def getOverlayUserAutoOff(self):

def _getInternalSources(self):
sources = ["mix"]
if self.getPreviewMixEnabled():
sources += ["premix"]
if self.getBlinderEnabled():
sources += ["blinder", "mix-blinded"]
for source in self.getLiveSources():
Expand Down
2 changes: 2 additions & 0 deletions vocto/port.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class Port(object):
SOURCES_PREVIEW = SOURCES_OUT+OFFSET_PREVIEW
LIVE_OUT = 15000
LIVE_PREVIEW = LIVE_OUT+OFFSET_PREVIEW
PREMIX_OUT = 21000
PREMIX_PREVIEW = PREMIX_OUT+OFFSET_PREVIEW
LOCALPLAYOUT_OUT = 19000

def __init__(self, name, source=None, audio=None, video=None):
Expand Down
2 changes: 2 additions & 0 deletions voctocore/lib/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ def transition(self, command):
def best(self, command):
"""tests if transition to the composite described by command is possible.
"""
if self.pipeline.vpremix is not None:
self.pipeline.vpremix.setComposite(command)
transition = self.pipeline.vmix.testTransition(command)
if transition:
return OkResponse('best','transition', *transition)
Expand Down
15 changes: 15 additions & 0 deletions voctocore/lib/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from lib.sources import spawn_source
from lib.srtserver import SRTServerSink
from lib.videomix import VideoMix
from lib.videopremix import VideoPreMix

from vocto.debug import gst_generate_dot
from vocto.port import Port
Expand Down Expand Up @@ -72,6 +73,12 @@ def __init__(self):
self.vmix = VideoMix()
self.bins.append(self.vmix)

self.vpremix = None
if Config.getPreviewMixEnabled():
self.log.info('Creating Videopremixer')
self.vpremix = VideoPreMix()
self.bins.append(self.vpremix)

for idx, background in enumerate(Config.getBackgroundSources()):
# create background source
source = spawn_source(
Expand All @@ -84,6 +91,10 @@ def __init__(self):
dest = AVRawOutput('mix', Port.MIX_OUT, use_audio_mix=True)
self.bins.append(dest)
self.ports.append(Port('mix', dest))
if Config.getPreviewMixEnabled():
dest = AVRawOutput('premix', Port.PREMIX_OUT, use_audio_mix=True)
self.bins.append(dest)
self.ports.append(Port('premix', dest))

# add localui
if Config.getProgramOutputEnabled():
Expand All @@ -96,6 +107,10 @@ def __init__(self):
dest = AVPreviewOutput('mix', Port.MIX_PREVIEW, use_audio_mix=True)
self.bins.append(dest)
self.ports.append(Port('preview-mix', dest))
if Config.getPreviewMixEnabled():
dest = AVPreviewOutput('premix', Port.PREMIX_PREVIEW, use_audio_mix=True)
self.bins.append(dest)
self.ports.append(Port('preview-premix', dest))

# create blinding sources and mixer
if Config.getBlinderEnabled():
Expand Down
121 changes: 121 additions & 0 deletions voctocore/lib/previewscene.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
import logging
import gi
gi.require_version('GstController', '1.0')
from gi.repository import Gst, GstController
from vocto.transitions import Frame, L, T, R, B

class PreviewScene:
""" Scene is the adaptor between the gstreamer compositor
and voctomix frames.
With commit() you add frames at a specified play time
"""
log = logging.getLogger('PreviewScene')

def __init__(self, sources, pipeline, fps, start_sink, cropping=True):
""" initialize with a gstreamer pipeline and names
of the sources to manage
"""
# frames to apply from
self.frames = dict()
# binding pads to apply to
self.pads = dict()
self.cpads = dict() if cropping else None
# time per frame
self.frame_time = int(Gst.SECOND / fps)

def bind(pad, prop):
""" adds a binding to a gstreamer property
pad's property
"""
# set up a new control source
cs = GstController.InterpolationControlSource()
# stop control source's internal interpolation
cs.set_property(
'mode', GstController.InterpolationMode.NONE)
# create control binding
cb = GstController.DirectControlBinding.new_absolute(
pad, prop, cs)
# add binding to pad
pad.add_control_binding(cb)
# return binding
return cs

# walk all sources
for idx, source in enumerate(sources):
# initially invisible
self.frames[source] = None
# get mixer pad from pipeline
mixerpad = (pipeline
.get_by_name('videopremixer')
.get_static_pad('sink_%s' % (idx + start_sink)))
# add dictionary of binds to all properties
# we vary for this source
self.pads[source] = {
'xpos': bind(mixerpad, 'xpos'),
'ypos': bind(mixerpad, 'ypos'),
'width': bind(mixerpad, 'width'),
'height': bind(mixerpad, 'height'),
'alpha': bind(mixerpad, 'alpha'),
'zorder': bind(mixerpad, 'zorder'),
}
# get mixer and cropper pad from pipeline
if self.cpads is not None:
cropperpad = (pipeline
.get_by_name("precropper-%s" % source))
self.cpads[source] = {
'croptop': bind(cropperpad, 'top'),
'cropleft': bind(cropperpad, 'left'),
'cropbottom': bind(cropperpad, 'bottom'),
'cropright': bind(cropperpad, 'right')
}
# ready to initialize gstreamer
self.dirty = False

def commit(self, source, frames):
''' commit multiple frames to the current gstreamer scene '''
self.log.debug("Commit %d frame(s) to source %s", len(frames), source)
self.frames[source] = frames
self.dirty = True

def set(self, source, frame):
''' commit single frame to the current gstreamer scene '''
self.log.debug("Set frame to source %s", source)
self.frames[source] = [frame]
self.dirty = True

def push(self, at_time=0):
''' apply all committed frames to GStreamer pipeline '''
# get pad for given source
for source, frames in self.frames.items():
if not frames:
frames = [Frame(zorder=-1,alpha=0)]
self.log.info("Pushing %d frame(s) to source '%s' at time %dms", len(
frames), source, at_time / Gst.MSECOND)
# reset time
time = at_time
# get GStreamer property pad for this source
pad = self.pads[source]
cpad = self.cpads[source] if self.cpads else None
self.log.debug(" %s", Frame.str_title())
# apply all frames of this source to GStreamer pipeline
for idx, frame in enumerate(frames):
self.log.debug("%2d: %s", idx, frame)
cropped = frame.cropped()
alpha = frame.float_alpha()
# transmit frame properties into mixing pipeline
pad['xpos'].set(time, cropped[L])
pad['ypos'].set(time, cropped[T])
pad['width'].set(time, cropped[R] - cropped[L])
pad['height'].set(time, cropped[B] - cropped[T])
pad['alpha'].set(time, alpha)
pad['zorder'].set(time, frame.zorder if alpha != 0 else -1)
if cpad:
cpad['croptop'].set(time, frame.crop[T])
cpad['cropleft'].set(time, frame.crop[L])
cpad['cropbottom'].set(time, frame.crop[B])
cpad['cropright'].set(time, frame.crop[R])
# next frame time
time += self.frame_time
self.frames[source] = None
self.dirty = False
Loading