diff options
Diffstat (limited to 'voctogui')
-rw-r--r-- | voctogui/lib/audioleveldisplay.py | 25 | ||||
-rw-r--r-- | voctogui/lib/audioselector.py | 63 | ||||
-rw-r--r-- | voctogui/lib/toolbar/composition.py | 33 | ||||
-rw-r--r-- | voctogui/lib/toolbar/specialfunctions.py | 33 | ||||
-rw-r--r-- | voctogui/lib/toolbar/streamblank.py | 45 | ||||
-rw-r--r-- | voctogui/lib/ui.py | 240 | ||||
-rw-r--r-- | voctogui/lib/videodisplay.py | 81 | ||||
-rw-r--r-- | voctogui/lib/videopreviews.py | 86 | ||||
-rw-r--r-- | voctogui/lib/warningoverlay.py | 55 | ||||
-rw-r--r-- | voctogui/ui/voctogui.ui | 42 |
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> |