summaryrefslogtreecommitdiff
path: root/voctogui
diff options
context:
space:
mode:
Diffstat (limited to 'voctogui')
-rw-r--r--voctogui/lib/audioleveldisplay.py25
-rw-r--r--voctogui/lib/audioselector.py63
-rw-r--r--voctogui/lib/toolbar/composition.py33
-rw-r--r--voctogui/lib/toolbar/specialfunctions.py33
-rw-r--r--voctogui/lib/toolbar/streamblank.py45
-rw-r--r--voctogui/lib/ui.py240
-rw-r--r--voctogui/lib/videodisplay.py81
-rw-r--r--voctogui/lib/videopreviews.py86
-rw-r--r--voctogui/lib/warningoverlay.py55
-rw-r--r--voctogui/ui/voctogui.ui42
10 files changed, 441 insertions, 262 deletions
diff --git a/voctogui/lib/audioleveldisplay.py b/voctogui/lib/audioleveldisplay.py
new file mode 100644
index 0000000..bc2a38f
--- /dev/null
+++ b/voctogui/lib/audioleveldisplay.py
@@ -0,0 +1,25 @@
+import logging
+from gi.repository import Gst, Gtk
+
+class AudioLevelDisplay(object):
+ """ Displays a Level-Meter of another VideoDisplay into a GtkWidget """
+
+ def __init__(self, drawing_area):
+ self.log = logging.getLogger('AudioLevelDisplay[%s]' % drawing_area.get_name())
+
+ self.drawing_area = drawing_area
+
+ self.levelrms = [0, 0] # Initialize to []
+ self.drawing_area.connect('draw', self.on_draw)
+
+
+ def on_draw(self, widget, cr):
+ cr.set_source_rgb(1, 1, 1)
+ cr.set_line_width(10)
+
+ cr.move_to(15, 0)
+ cr.line_to(15, self.levelrms[0]*-20) # Work with 0+ Channels
+ cr.stroke()
+
+ def level_callback(self, peaks, rms):
+ self.levelrms = rms
diff --git a/voctogui/lib/audioselector.py b/voctogui/lib/audioselector.py
new file mode 100644
index 0000000..8ad84e3
--- /dev/null
+++ b/voctogui/lib/audioselector.py
@@ -0,0 +1,63 @@
+import logging
+from gi.repository import Gst, Gdk, GLib
+
+class AudioSelectorController(object):
+ """ Displays a Level-Meter of another VideoDisplay into a GtkWidget """
+
+ def __init__(self, drawing_area, win, uibuilder):
+ self.log = logging.getLogger('AudioSelectorController')
+
+ self.drawing_area = drawing_area
+ self.win = win
+
+ combo = uibuilder.find_widget_recursive(win, 'combo_audio')
+ combo.connect('changed', self.on_changed)
+ #combo.set_sensitive(True)
+ self.combo = combo
+
+ eventbox = uibuilder.find_widget_recursive(win, 'combo_audio_events')
+ eventbox.connect('button_press_event', self.on_button_press_event)
+ eventbox.set_property('above_child', True)
+ self.eventbox = eventbox
+
+ combo.remove_all()
+ combo.append('moobar', 'Moo Bar')
+ combo.append('moofar', 'Moo Far')
+
+ combo.set_active_id('moobar')
+
+ self.timer_iteration = 0
+
+ def on_button_press_event(self, combo, event):
+ if event.type != Gdk.EventType.DOUBLE_BUTTON_PRESS:
+ return
+
+ self.log.debug('double-clicked, unlocking')
+ self.set_enabled(True)
+ GLib.timeout_add_seconds(5, self.on_disabled_timer, self.timer_iteration)
+
+ def on_disabled_timer(self, timer_iteration):
+ if timer_iteration != self.timer_iteration:
+ self.log.debug('lock-timer fired late, ignoring')
+ return
+
+ self.log.debug('lock-timer fired, locking')
+ self.set_enabled(False)
+ return False
+
+ def set_enabled(self, enable):
+ self.combo.set_sensitive(enable)
+ self.eventbox.set_property('above_child', not enable)
+
+ def is_enabled(self):
+ return self.combo.get_sensitive()
+
+ def on_changed(self, combo):
+ if not self.is_enabled():
+ return
+
+ self.timer_iteration += 1
+
+ value = combo.get_active_text()
+ self.log.debug('changed to %s', value)
+ self.set_enabled(False)
diff --git a/voctogui/lib/toolbar/composition.py b/voctogui/lib/toolbar/composition.py
new file mode 100644
index 0000000..0185b7d
--- /dev/null
+++ b/voctogui/lib/toolbar/composition.py
@@ -0,0 +1,33 @@
+import logging
+from gi.repository import Gtk
+
+class CompositionToolbarController(object):
+ """ Manages Accelerators and Clicks on the Composition Toolbar-Buttons """
+
+ def __init__(self, drawing_area, win, uibuilder):
+ self.log = logging.getLogger('CompositionToolbarController')
+
+ accelerators = Gtk.AccelGroup()
+ win.add_accel_group(accelerators)
+
+ composites = [
+ 'composite-fullscreen',
+ 'composite-picture-in-picture',
+ 'composite-side-by-side-equal',
+ 'composite-side-by-side-preview'
+ ]
+
+ for idx, name in enumerate(composites):
+ key, mod = Gtk.accelerator_parse('F%u' % (idx+1))
+ btn = uibuilder.find_widget_recursive(drawing_area, name)
+ btn.set_name(name)
+
+ # Thanks to http://stackoverflow.com/a/19739855/1659732
+ btn.get_child().add_accelerator('clicked', accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
+ btn.connect('toggled', self.on_btn_toggled)
+
+ def on_btn_toggled(self, btn):
+ if not btn.get_active():
+ return
+
+ self.log.info("on_btn_toggled: %s", btn.get_name())
diff --git a/voctogui/lib/toolbar/specialfunctions.py b/voctogui/lib/toolbar/specialfunctions.py
new file mode 100644
index 0000000..61fb6bf
--- /dev/null
+++ b/voctogui/lib/toolbar/specialfunctions.py
@@ -0,0 +1,33 @@
+import logging
+from gi.repository import Gtk
+
+class SpecialFunctionsToolbarController(object):
+ """ Manages Accelerators and Clicks on the Composition Toolbar-Buttons """
+
+ def __init__(self, drawing_area, win, uibuilder, video_display):
+ self.log = logging.getLogger('SpecialFunctionsToolbarController')
+
+ self.video_display = video_display
+
+ accelerators = Gtk.AccelGroup()
+ win.add_accel_group(accelerators)
+
+ composites = [
+ 'preview_fullscreen',
+ 'preview_freeze',
+ ]
+
+ for idx, name in enumerate(composites):
+ key, mod = Gtk.accelerator_parse('F%u' % (idx+10))
+ btn = uibuilder.find_widget_recursive(drawing_area, name)
+ btn.set_name(name)
+
+ # Thanks to http://stackoverflow.com/a/19739855/1659732
+ childbtn = btn.get_child()
+ childbtn.add_accelerator('clicked', accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
+ childbtn.connect('button-press-event', self.on_btn_event)
+ childbtn.connect('button-release-event', self.on_btn_event)
+
+ def on_btn_event(self, btn, event):
+ self.log.info("on_btn_event: %s @ %s", event.type, btn.get_name())
+ # do sth. to self.video_display here
diff --git a/voctogui/lib/toolbar/streamblank.py b/voctogui/lib/toolbar/streamblank.py
new file mode 100644
index 0000000..074fb80
--- /dev/null
+++ b/voctogui/lib/toolbar/streamblank.py
@@ -0,0 +1,45 @@
+import logging
+from gi.repository import Gtk
+
+class StreamblankToolbarController(object):
+ """ Manages Accelerators and Clicks on the Composition Toolbar-Buttons """
+
+ def __init__(self, drawing_area, win, uibuilder, warning_overlay):
+ self.log = logging.getLogger('StreamblankToolbarController')
+
+ self.warning_overlay = warning_overlay
+
+ blank_sources = ['pause', 'nostream']
+
+
+ livebtn = uibuilder.find_widget_recursive(drawing_area, 'stream_live')
+ blankbtn = uibuilder.find_widget_recursive(drawing_area, 'stream_blank')
+
+ blankbtn_pos = drawing_area.get_item_index(blankbtn)
+
+ livebtn.connect('toggled', self.on_btn_toggled)
+ livebtn.set_name('live')
+
+ for idx, name in enumerate(blank_sources):
+ if idx == 0:
+ new_btn = blankbtn
+ else:
+ new_icon = Gtk.Image.new_from_pixbuf(blankbtn.get_icon_widget().get_pixbuf())
+ new_btn = Gtk.RadioToolButton(group=livebtn)
+ new_btn.set_icon_widget(new_icon)
+ drawing_area.insert(new_btn, blankbtn_pos+1)
+
+ new_btn.set_label("Stream %s" % name)
+ new_btn.connect('toggled', self.on_btn_toggled)
+ new_btn.set_name(name)
+
+ def on_btn_toggled(self, btn):
+ if not btn.get_active():
+ return
+
+ self.log.info("on_btn_toggled: %s", btn.get_name())
+ if btn.get_name() == 'live':
+ self.warning_overlay.disable()
+
+ else:
+ self.warning_overlay.enable(btn.get_name())
diff --git a/voctogui/lib/ui.py b/voctogui/lib/ui.py
index 4106967..0343c86 100644
--- a/voctogui/lib/ui.py
+++ b/voctogui/lib/ui.py
@@ -4,7 +4,17 @@ from gi.repository import Gtk, Gst, Gdk, GLib
from lib.config import Config
from lib.uibuilder import UiBuilder
+
from lib.videodisplay import VideoDisplay
+from lib.audioleveldisplay import AudioLevelDisplay
+from lib.warningoverlay import VideoWarningOverlay
+
+from lib.videopreviews import VideoPreviewsController
+from lib.audioselector import AudioSelectorController
+
+from lib.toolbar.composition import CompositionToolbarController
+from lib.toolbar.streamblank import StreamblankToolbarController
+from lib.toolbar.specialfunctions import SpecialFunctionsToolbarController
class Ui(UiBuilder):
def __init__(self, uifile):
@@ -20,216 +30,54 @@ class Ui(UiBuilder):
# Connect Close-Handler
self.win.connect('delete-event', Gtk.main_quit)
- self.previews = {}
- self.preview_players = {}
-
-
- self.configure_toolbar_accelerators()
- self.configure_video_main()
- self.configure_video_previews()
- self.configure_audio_selector()
- self.configure_streamblank_selector()
-
- def configure_toolbar_accelerators(self):
- accelerators = Gtk.AccelGroup()
- self.win.add_accel_group(accelerators)
-
- composites = [
- 'composite-fullscreen',
- 'composite-picture-in-picture',
- 'composite-side-by-side-equal',
- 'composite-side-by-side-preview'
- ]
-
- for idx, name in enumerate(composites):
- key, mod = Gtk.accelerator_parse('F%u' % (idx+1))
- btn = self.find_widget_recursive(self.win, name)
- btn.set_name(name)
-
- # Thanks to http://stackoverflow.com/a/19739855/1659732
- btn.get_child().add_accelerator('clicked', accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
- btn.connect('toggled', self.composite_btn_toggled)
-
- def composite_btn_toggled(self, btn):
- if not btn.get_active():
- return
-
- self.log.info("composite_btn_toggled: %s", btn.get_name())
-
- def configure_video_main(self):
- self.log.info('Initializing Main Video and Main Audio-Level View')
-
- video = self.find_widget_recursive(self.win, 'video_main')
- audiolevel = self.find_widget_recursive(self.win, 'audiolevel_main')
- self.video_main_player = VideoDisplay(11000, video, audiolevel,
- playaudio=Config.getboolean('mainvideo', 'playaudio'),
- allowoverlay=True)
-
- def configure_video_previews(self):
- self.log.info('Initializing Video Previews')
-
- sources = ['cam1', 'cam2', 'grabber']
- box = self.find_widget_recursive(self.win, 'box_left')
-
- try:
- width = Config.getint('previews', 'width')
- self.log.debug('Preview-Width configured to %u', width)
- except:
- width = 320
- self.log.debug('Preview-Width selected as %u', width)
-
- try:
- height = Config.getint('previews', 'height')
- self.log.debug('Preview-Height configured to %u', width)
- except:
- height = width*9/16
- self.log.debug('Preview-Height calculated to %u', width)
-
-
- # Accelerators
- accelerators = Gtk.AccelGroup()
- self.win.add_accel_group(accelerators)
-
- group_a = None
- group_b = None
-
- for idx, source in enumerate(sources):
- self.log.info('Initializing Video Preview %s', source)
-
- preview = self.get_check_widget('widget_preview', clone=True)
- video = self.find_widget_recursive(preview, 'video')
-
- video.set_size_request(width, height)
- box.pack_start(preview, fill=False, expand=False, padding=0)
- player = VideoDisplay(13000 + idx, video)
+ # Create Audio-Level Display
+ drawing_area = self.find_widget_recursive(self.win, 'audiolevel_main')
+ self.audio_level_display = AudioLevelDisplay(drawing_area)
- self.find_widget_recursive(preview, 'label').set_label(source)
- btn_a = self.find_widget_recursive(preview, 'btn_a')
- btn_b = self.find_widget_recursive(preview, 'btn_b')
- btn_a.set_name("%c %u" % ('a', idx))
- btn_b.set_name("%c %u" % ('b', idx))
+ # Create Main-Video Overlay Controller
+ self.video_warning_overlay = VideoWarningOverlay()
- if not group_a:
- group_a = btn_a
- else:
- btn_a.join_group(group_a)
+ # Create Main-Video Display
+ drawing_area = self.find_widget_recursive(self.win, 'video_main')
+ self.main_video_display = VideoDisplay(drawing_area,
+ port=11000,
+ play_audio=True,
+ draw_callback=self.video_warning_overlay.draw_callback,
+ level_callback=self.audio_level_display.level_callback)
- if not group_b:
- group_b = btn_b
- else:
- btn_b.join_group(group_b)
- btn_a.connect('toggled', self.preview_btn_toggled)
- btn_b.connect('toggled', self.preview_btn_toggled)
+ # Setup Preview Controller
+ drawing_area = self.find_widget_recursive(self.win, 'box_left')
+ self.video_previews_controller = VideoPreviewsController(drawing_area,
+ win=self.win,
+ uibuilder=self)
- key, mod = Gtk.accelerator_parse('%u' % (idx+1))
- btn_a.add_accelerator('activate', accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
+ drawing_area = self.find_widget_recursive(self.win, 'combo_audio')
+ self.audio_selector_controller = AudioSelectorController(drawing_area,
+ win=self.win,
+ uibuilder=self)
- key, mod = Gtk.accelerator_parse('<Ctrl>%u' % (idx+1))
- btn_b.add_accelerator('activate', accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
- self.preview_players[source] = player
- self.previews[source] = preview
+ # Setup Toolbar Controllers
+ toolbar = self.find_widget_recursive(self.win, 'toolbar')
+ self.composition_toolbar_controller = CompositionToolbarController(toolbar,
+ win=self.win,
+ uibuilder=self)
- def preview_btn_toggled(self, btn):
- if not btn.get_active():
- return
+ self.streamblank_toolbar_controller = StreamblankToolbarController(toolbar,
+ win=self.win,
+ uibuilder=self,
+ warning_overlay=self.video_warning_overlay)
- self.log.info('preview_btn_toggled: %s', btn.get_name())
-
- def configure_audio_selector(self):
- self.log.info('Initializing Audio Selector')
-
- combo = self.find_widget_recursive(self.win, 'combo_audio')
- combo.set_sensitive(True)
-
- # FIXME access via combo_audio?
- liststore = self.get_check_widget('liststore_audio')
- liststore.clear()
-
- row = liststore.append()
- liststore.set(row, [0], ['foobar'])
-
- row = liststore.append('')
- liststore.set(row, [0], ['moofar'])
-
- combo.set_active_id('moofar')
-
- def configure_streamblank_selector(self):
- livebtn = self.get_check_widget('stream_live')
- blankbtn = self.get_check_widget('stream_blank')
- toolbar = blankbtn.get_parent()
- pos = toolbar.get_item_index(blankbtn)
-
- self.blink_btn = None
- self.blink_btn_state = False
-
- livebtn.connect('toggled', self.streamblank_button_toggled)
- livebtn.set_name('live')
-
- GLib.timeout_add_seconds(1, self.blink_streamblank_button)
-
- for idx, name in enumerate(['pause', 'nostream']):
- if idx == 0:
- new_btn = blankbtn
- else:
- new_icon = Gtk.Image.new_from_pixbuf(blankbtn.get_icon_widget().get_pixbuf())
- new_btn = Gtk.RadioToolButton(group=livebtn)
- new_btn.set_icon_widget(new_icon)
- toolbar.insert(new_btn, pos+1)
-
- new_btn.set_label("Stream %s" % name)
- new_btn.connect('toggled', self.streamblank_button_toggled)
- new_btn.set_name(name)
-
- def blink_streamblank_button(self):
- self.blink_btn_state = not self.blink_btn_state
- return True
-
- def streamblank_button_toggled(self, btn):
- if not btn.get_active():
- return
-
- if btn.get_name() != 'live':
- self.blink_btn = btn
- self.blink_btn_state = False
- self.streamblank_mode = btn.get_name()
- self.video_main_player.set_overlay_callback(self.draw_streamblank_warning)
- else:
- self.blink_btn = None
- self.video_main_player.set_overlay_callback(None)
-
- def draw_streamblank_warning(self, cairooverlay, cr, timestamp, duration):
- h = 1080/20
- w = 1920
-
- if self.blink_btn_state:
- cr.set_source_rgba(1.0, 0.0, 0.0, 0.8)
- else:
- cr.set_source_rgba(1.0, 0.5, 0.0, 0.8)
-
- cr.rectangle(0, 0, w, h)
- cr.fill()
-
- text = "Stream is Blanked: {}".format(self.streamblank_mode)
-
- cr.set_font_size(h*0.75)
- xbearing, ybearing, txtwidth, txtheight, xadvance, yadvance = cr.text_extents(text)
-
- cr.move_to(w/2 - txtwidth/2, h*0.75)
- cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
- cr.show_text(text)
+ self.special_functions_toolbar_controller = SpecialFunctionsToolbarController(toolbar,
+ win=self.win,
+ uibuilder=self,
+ video_display=self.main_video_display)
def show(self):
- self.log.info('Running Video-Playback Pipelines')
-
- self.video_main_player.run()
- for name, player in self.preview_players.items():
- player.run()
-
self.log.info('Showing Main-Window')
self.win.show_all()
diff --git a/voctogui/lib/videodisplay.py b/voctogui/lib/videodisplay.py
index 42724ac..a2f7f5a 100644
--- a/voctogui/lib/videodisplay.py
+++ b/voctogui/lib/videodisplay.py
@@ -1,52 +1,65 @@
import logging
-from gi.repository import Gst, Gtk
+from gi.repository import Gst
-class VideoDisplay:
+class VideoDisplay(object):
""" Displays a Voctomix-Video-Stream into a GtkWidget """
- def __init__(self, port, videowidget, audiolevelwidget=None, playaudio=False, allowoverlay=False):
- self.log = logging.getLogger('VideoDisplay[%u]' % port)
+ def __init__(self, drawing_area, port, play_audio=False, draw_callback=None, level_callback=None):
+ self.log = logging.getLogger('VideoDisplay[%s]' % drawing_area.get_name())
+ self.drawing_area = drawing_area
+ self.draw_callback = draw_callback
+ self.level_callback = level_callback
+
+ # Setup Server-Connection, Demuxing and Decoding
pipeline = """
videotestsrc !
timeoverlay !
- video/x-raw,width=1920,height=1080 !
+ video/x-raw,width=1920,height=1080,framerate=25/1 !
""".format(
port=port
)
- if allowoverlay:
+
+ # If an overlay is required, add an cairooverlay-Element into the Video-Path
+ if self.draw_callback:
pipeline += """
videoconvert !
cairooverlay name=overlay !
videoconvert !
"""
+ # Video Display
pipeline += """
xvimagesink name=v
"""
- if audiolevelwidget or playaudio:
+
+ # If an Audio-Path is required, add an Audio-Path through a level-Element
+ if self.level_callback or play_audio:
pipeline += """
audiotestsrc wave=blue-noise !
audio/x-raw !
level name=lvl interval=50000000 !
"""
- if playaudio:
+ # If Playback is requested, push fo alsa
+ if play_audio:
pipeline += """
alsasink
"""
+
+ # Otherwise just trash the Audio
else:
pipeline += """
fakesink
"""
- self.log.info('launching gstreamer-pipeline for widget %s "%s":\n%s', videowidget.get_name(), Gtk.Buildable.get_name(videowidget), pipeline)
-
+ self.log.debug('Creating Display-Pipeline:\n%s', pipeline)
self.pipeline = Gst.parse_launch(pipeline)
- videowidget.realize()
- self.xid = videowidget.get_property('window').get_xid()
+ self.drawing_area.realize()
+ self.xid = self.drawing_area.get_property('window').get_xid()
+ self.log.debug('Realized Drawing-Area with xid %u', self.xid)
bus = self.pipeline.get_bus()
bus.add_signal_watch()
@@ -55,41 +68,26 @@ class VideoDisplay:
bus.connect('message::error', self.on_error)
bus.connect("sync-message::element", self.on_syncmsg)
- self.draw_callback = None
-
- if audiolevelwidget:
- self.levelrms = [0, 0]
- self.audiolevelwidget = audiolevelwidget
- self.audiolevelwidget.connect('draw', self.on_level_draw)
+ if self.level_callback:
bus.connect("message::element", self.on_level)
- def run(self):
+ if self.draw_callback:
+ self.pipeline.get_by_name('overlay').connect('draw', self.on_draw)
+
+ self.log.debug('Launching Display-Pipeline')
self.pipeline.set_state(Gst.State.PLAYING)
- def set_overlay_callback(self, callback):
- if callback:
- if not self.draw_callback:
- self.draw_callback = self.pipeline.get_by_name('overlay').connect('draw', callback)
- else:
- print('disconnect')
- self.pipeline.get_by_name('overlay').disconnect(self.draw_callback)
- self.draw_callback = None
def on_syncmsg(self, bus, msg):
if msg.get_structure().get_name() == "prepare-window-handle":
- self.log.info('setting xvimagesink window-handle to %s', self.xid)
+ self.log.info('Setting xvimagesink window-handle to %s', self.xid)
msg.src.set_window_handle(self.xid)
- def on_error(self, bus, msg):
- self.log.error('on_error(): %s', msg.parse_error())
+ def on_error(self, bus, message):
+ self.log.debug('Received Error-Signal on Display-Pipeline')
+ (error, debug) = message.parse_error()
+ self.log.debug('Error-Details: #%u: %s', error.code, debug)
- def on_level_draw(self, widget, cr):
- cr.set_source_rgb(1, 1, 1)
- cr.set_line_width(10)
-
- cr.move_to(15, 0)
- cr.line_to(15, self.levelrms[0]*-20)
- cr.stroke()
def on_level(self, bus, msg):
if msg.src.name != 'lvl':
@@ -98,6 +96,9 @@ class VideoDisplay:
if msg.type != Gst.MessageType.ELEMENT:
return
- self.levelpeaks = msg.get_structure().get_value('peak')
- self.levelrms = msg.get_structure().get_value('rms')
- self.audiolevelwidget.queue_draw()
+ peaks = msg.get_structure().get_value('peak')
+ rms = msg.get_structure().get_value('rms')
+ self.level_callback(peaks, rms)
+
+ def on_draw(self, cairooverlay, cr, timestamp, duration):
+ self.draw_callback(cr, timestamp, duration)
diff --git a/voctogui/lib/videopreviews.py b/voctogui/lib/videopreviews.py
new file mode 100644
index 0000000..d6a18ec
--- /dev/null
+++ b/voctogui/lib/videopreviews.py
@@ -0,0 +1,86 @@
+import logging
+from gi.repository import Gst, Gtk
+
+from lib.videodisplay import VideoDisplay
+
+class VideoPreviewsController(object):
+ """ Displays Video-Previews and selection Buttons for them """
+
+ def __init__(self, drawing_area, win, uibuilder):
+ self.log = logging.getLogger('VideoPreviewsController')
+
+ self.drawing_area = drawing_area
+ self.win = win
+
+ self.sources = ['cam1', 'cam2', 'grabber']
+ self.preview_players = {}
+ self.previews = {}
+
+ try:
+ width = Config.getint('previews', 'width')
+ self.log.debug('Preview-Width configured to %u', width)
+ except:
+ width = 320
+ self.log.debug('Preview-Width selected as %u', width)
+
+ try:
+ height = Config.getint('previews', 'height')
+ self.log.debug('Preview-Height configured to %u', width)
+ except:
+ height = width*9/16
+ self.log.debug('Preview-Height calculated to %u', width)
+
+ # Accelerators
+ accelerators = Gtk.AccelGroup()
+ win.add_accel_group(accelerators)
+
+ group_a = None
+ group_b = None
+
+ for idx, source in enumerate(self.sources):
+ self.log.info('Initializing Video Preview %s', source)
+
+ preview = uibuilder.get_check_widget('widget_preview', clone=True)
+ video = uibuilder.find_widget_recursive(preview, 'video')
+
+ video.set_size_request(width, height)
+ drawing_area.pack_start(preview, fill=False, expand=False, padding=0)
+
+ player = VideoDisplay(video, port=13000 + idx)
+
+ uibuilder.find_widget_recursive(preview, 'label').set_label(source)
+ btn_a = uibuilder.find_widget_recursive(preview, 'btn_a')
+ btn_b = uibuilder.find_widget_recursive(preview, 'btn_b')
+
+ btn_a.set_name("%c %u" % ('a', idx))
+ btn_b.set_name("%c %u" % ('b', idx))
+
+ if not group_a:
+ group_a = btn_a
+ else:
+ btn_a.join_group(group_a)
+
+
+ if not group_b:
+ group_b = btn_b
+ else:
+ btn_b.join_group(group_b)
+
+
+ btn_a.connect('toggled', self.btn_toggled)
+ btn_b.connect('toggled', self.btn_toggled)
+
+ key, mod = Gtk.accelerator_parse('%u' % (idx+1))
+ btn_a.add_accelerator('activate', accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
+
+ key, mod = Gtk.accelerator_parse('<Ctrl>%u' % (idx+1))
+ btn_b.add_accelerator('activate', accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
+
+ self.preview_players[source] = player
+ self.previews[source] = preview
+
+ def btn_toggled(self, btn):
+ if not btn.get_active():
+ return
+
+ self.log.info('btn_toggled: %s', btn.get_name())
diff --git a/voctogui/lib/warningoverlay.py b/voctogui/lib/warningoverlay.py
new file mode 100644
index 0000000..ad86b69
--- /dev/null
+++ b/voctogui/lib/warningoverlay.py
@@ -0,0 +1,55 @@
+import logging
+from gi.repository import GLib
+
+class VideoWarningOverlay(object):
+ """ Displays a Warning-Overlay above the Video-Feed of another VideoDisplay """
+
+ def __init__(self):
+ self.log = logging.getLogger('VideoWarningOverlay')
+
+ self.text = None
+ self.enabled = False
+ self.blink_state = False
+
+ GLib.timeout_add_seconds(1, self.on_blink_callback)
+
+
+ def on_blink_callback(self):
+ self.blink_state = not self.blink_state
+ return True
+
+ def enable(self, text=None):
+ self.text = text
+ self.enabled = True
+
+ def set_text(self, text=None):
+ self.text = text
+
+ def disable(self):
+ self.enabled = False
+
+ def draw_callback(self, cr, timestamp, duration):
+ if not self.enabled:
+ return
+
+ w = 1920
+ h = 1080/20
+
+ if self.blink_state:
+ cr.set_source_rgba(1.0, 0.0, 0.0, 0.8)
+ else:
+ cr.set_source_rgba(1.0, 0.5, 0.0, 0.8)
+
+ cr.rectangle(0, 0, w, h)
+ cr.fill()
+
+ text = "Stream is Blanked"
+ if self.text:
+ text += ": "+self.text
+
+ cr.set_font_size(h*0.75)
+ xbearing, ybearing, txtwidth, txtheight, xadvance, yadvance = cr.text_extents(text)
+
+ cr.move_to(w/2 - txtwidth/2, h*0.75)
+ cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
+ cr.show_text(text)
diff --git a/voctogui/ui/voctogui.ui b/voctogui/ui/voctogui.ui
index 80ca348..1eb64cc 100644
--- a/voctogui/ui/voctogui.ui
+++ b/voctogui/ui/voctogui.ui
@@ -32,20 +32,6 @@
<property name="can_focus">False</property>
<property name="pixbuf">stream-live.svg</property>
</object>
- <object class="GtkListStore" id="liststore_audio">
- <columns>
- <!-- column-name name -->
- <column type="gchararray"/>
- </columns>
- <data>
- <row>
- <col id="0" translatable="yes">cam1</col>
- </row>
- <row>
- <col id="0" translatable="yes">cam2</col>
- </row>
- </data>
- </object>
<object class="GtkWindow" id="window">
<property name="can_focus">False</property>
<property name="default_width">1280</property>
@@ -168,6 +154,7 @@
<object class="GtkToolButton" id="preview_fullscreen">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
<property name="label" translatable="yes">Fullscreen Preview</property>
<property name="use_underline">True</property>
</object>
@@ -180,6 +167,7 @@
<object class="GtkToolButton" id="preview_freeze">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
<property name="label" translatable="yes">Freeze Preview</property>
<property name="use_underline">True</property>
</object>
@@ -225,21 +213,23 @@
</packing>
</child>
<child>
- <object class="GtkComboBox" id="combo_audio">
+ <object class="GtkEventBox" id="combo_audio_events">
<property name="visible">True</property>
- <property name="sensitive">False</property>
<property name="can_focus">False</property>
- <property name="margin_left">5</property>
- <property name="margin_right">5</property>
- <property name="margin_top">5</property>
- <property name="margin_bottom">5</property>
- <property name="model">liststore_audio</property>
- <property name="id_column">0</property>
+ <property name="events">GDK_BUTTON_PRESS_MASK | GDK_STRUCTURE_MASK</property>
<child>
- <object class="GtkCellRendererText" id="cellrenderertext"/>
- <attributes>
- <attribute name="text">0</attribute>
- </attributes>
+ <object class="GtkComboBoxText" id="combo_audio">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_BUTTON_PRESS_MASK | GDK_STRUCTURE_MASK</property>
+ <property name="margin_top">5</property>
+ <property name="margin_bottom">5</property>
+ <items>
+ <item translatable="yes">Foo</item>
+ <item translatable="yes">Bar</item>
+ </items>
+ </object>
</child>
</object>
<packing>